Help us understand the problem. What is going on with this article?

PythonでGUIアプリの作成

はじめに

Pythonでコードを書いて、いつもタミナルからコマンドを叩いて実行しています。自分としては、コマンド叩くのが楽ですが、お客様の事を考えるとGUIのほうが使いやすいと思います。そのために、Tkinterを使っていたのですが、レイアウトの調整が面倒でした。
Electronが好きなのでPythonでも同じことしたいな、html(実際はJavascript)からPythonの関数呼びだしたいなという思いがあり、検索してみるとEelと出会いました。簡単に実装ができ、とても満足しています。さらに、Pyinstallerを使う事で一つの実行ファイルを作ることもできます。デモ用のアプリをお客様に渡したいときは大変役に立ちます。

アプリの概要

今回はクライアントサイドで指定したNTPサーバーをPython側に渡し、Python側でそのサーバーに問い合わせを行い、結果をクライアントに返すアプリを作ります。

アプリのデザインが以下のようになります。

Let's Code

① フロント開発

まずは、フロントの部分を実装するために以下の構成のフォルダーを作り、空のファイルを作ります。一般のウエブページを作った時の全く同じです。イメージ「img.jpg」はこちらよりダウンロードし、「image」フォルダーにおいてください。

web
|
|----html
|    |----index.html
|
|----css
|    |----index.css
|
|----js
|    |----index.js
|
|----image
     |----img.jpg

僕はこの構成が好きですが、別にCSSやJSすべてhtmlに書いても動作します。
「index.html」の中身が以下のようになります。上のheaaderとsectionの部分がなくても同じ動きをしますが、イメージの表示の仕方を学ぶために入れておきます。とはいえ、いつものHTMLの書き方と全く同じ書き方です。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- 外部スタイルシートの読み込み -->
    <link rel="stylesheet" href="../css/index.css">
    <title>eelチュートリアル</title>
</head>

<body>
    <div class="box">
        <header>
            <img src="../image/img.jpg" alt="Himalaya">
            <span class="title">Himalaya</span>
        </header>

        <section>
            <h3>eelチュートリアル</h3>
            <p>
                テキストボックスにntpサーバーを設定し、Pythonに現在の時刻を照会するように依頼します。
                Pythonは、指定されたntpサーバーに問い合わせ、結果を我々に教えてくれます。
            </p>
        </section>

        <!-- NTPサーバーを指定する箇所 -->
        <section id="form">
            <label for="ntp">NTPサーバー</label>
            <input type="text" name="ntp" id="ntp" placeholder="ntp.nict.jp">
        </section>

        <section>
            <!-- このボタンをクリックするとPython側の処理が実行される -->
            <button onclick="getCurrentTime()">Pythonさん!教えて</button>
            <!-- ここがPythonから送られた結果が表示される -->
            <p id="result">----/--/-- --:--:--</p>
        </section>
    </div>
</body>
</html>

「index.css」の中身がこちらのようになります。コードだけ
で記事が長くなるのでリンクを張っております。そちらからコピペしてください。

この状態でindex.htmlをブラウザで開きます。以下のように表示されたらOKです。

② バックエンド開発

ここからeelの出番です。まずは、「eel_basic」というフォルダーを新たに作ります。そして、そのフォルダーに上で作成した「web」フォルダーをまるごと移動します。そして、「app.py」を新たに作ります。フォルダー構成は以下のようになります。

eel_basic
|
|----web
|
|----app.py

eelをインストールします。また、NTPサーバーの問い合わせを行うためにntplibを使いますので合わせてインストールしておきましょう。

pip install eel
pip install ntplib

app.pyの中身を以下のようにします。

# eelのインポート
import eel

# ウエブコンテンツを持つフォルダー
eel.init("web")

# 最初に表示するhtmlページ
eel.start("html/index.html")

たったこの3行(コメント抜き)です。

③ 実行

ターミナルから「eel_basic」に移動し、以下のコマンドを入力するとアプリが起動されます。

python app.py

簡単にGUIアプリの作成ができました。

④ JavascriptからPythonの処理

ただ表示だけでは意味がないので、ボタンを押した時にPython側の処理を実行できるようにします。こちら実現するためには、htmlファイルの以下の1行を追加します。

<script type="text/javascript" src="/eel.js"></script>

この「eel.js」は私たちが気にする必要がありません。eelが用意してくれるので、ファイルが見えないから焦る必要がありません。そして、私たちのJavascriptのコードを書くために別のファイルを用意します。最終的のhtmlファイルは以下のようになります。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- 外部スタイルシートの読み込み -->
    <link rel="stylesheet" href="../css/index.css">
    <title>eelチュートリアル</title>
</head>

<body>
    <div class="box">
    省略
    </div>

    <!-- 気にしないで記入する -->
    <script type="text/javascript" src="/eel.js"></script>

    <!-- 外部JSファイルの読み込み -->
    <script src="../js/index.js"></script>
</body>
</html>

そして、Javascriptからアクセスさせるためには、@eel.expose装飾した関数をPython側で用意します。「app.py」の中身が以下のように変えます。

# eelのインポート
import eel

# これを書くことでJSからアクセスができます
@eel.expose
def ask_python_from_js_get_time(server):
    #ここで処理を記述
    TODO()

# ウエブコンテンツを持つフォルダー
eel.init("web")

# 最初に表示するhtmlページ
eel.start("html/index.html")

「index.js」には、ボタンをクリックした時の処理を記述します。

function getCurrentTime() {
    let server = document.getElementById("ntp").value;
    if (server.trim().length == 0) {
        document.getElementById("result").innerHTML = "Enter NTP server address!";
        return;
    }

    // ここでPython側の処理を実行
    eel.ask_python_from_js_get_time(server);
}

htmlファイルに読み込んだ「eel.js」で「eel」オブジェクトが生成され、@eel.expose装飾された関数がそのオブジェクトのプロパティとしてアクセスできます。

⑤ PythonからJavascriptの処理

今度は、Python側で処理した結果をフロントに知らせるための処理を追加します。このためには、Python側でJavascriptの関数を呼び出す仕組みになります。上のケースでは、pythonに@eel.expose装飾する事で、Javascriptにエクスポーズしました。今回はその逆をします。つまり、Javascriptの関数をエクスポーズします。index.jsに以下のように追加します。

eel.expose(run_js_from_python);
function run_js_from_python(msg) {
    document.getElementById("result").innerHTML = msg;
}

このようにすることでPython側からアクセスできます。「app.py」は最終的に以下のようになります。ntplibが入ってないのであれば、pip install ntplibでインストールしておきましょう。

# eelのインポート
import eel
import time
import ntplib
from time import ctime
from datetime import datetime

# これを書くことでJSからアクセスができます
@eel.expose
def ask_python_from_js_get_time(server):
    now_time = ""
    try:
        ntp_client = ntplib.NTPClient()
        ntp_resp = ntp_client.request(server)
        ntp_time = datetime.strptime(ctime(ntp_resp.tx_time), "%a %b %d %H:%M:%S %Y")
        now_time = ntp_time.strftime("%Y/%m/%d %H:%M:%S")
    except:
        now_time = "Woops! something went wrong."
    finally:
        # NOTE
        # return now_time
        # JSの関数を呼び出す
        eel.run_js_from_python(now_time)

# ウエブコンテンツを持つフォルダー
eel.init("web")

# 最初に表示するhtmlページ
eel.start("html/index.html")

ここまで来たら、完了です。もう一度、ターミナルから「eel_basic」に移動し、以下のコマンドを入力するとアプリが起動されます。色々NTPサーバーを指定し、ボタンを押してみてください。

python app.py

⑥ 補足-1

ちなみにapp.pyのコメントにNOTEを書いた行の下でreturn分をコメントしていますが、実は一般の関数と同様にことらもreturnを使えます。今回はPythonからJavascriptの関数そしてその逆の通信の方法を学びたかったため、すこし複雑な構成にしました。Javascriptの関数からPythonの関数を呼び出し、その戻り値を何か処理したいときは、Javascriptのasyncを使います。
run_js_from_python関数なしで作りたい場合は、index.jsの中身が以下のようになります。

async function refreshTime(server) {
    let msg = await eel.ask_python_from_js_get_time(server)();
    document.getElementById("result").innerHTML = msg;
}

function getCurrentTime() {
    let server = document.getElementById("ntp").value;
    if (server.trim().length == 0) {
        document.getElementById("result").innerHTML = "Enter NTP server address!";
        return;
    }

    refreshTime(server);
}

⑦ 補足-2

今回デスクトップアプリを作ったのですが、実はPython側でサーバーの役割し、Javascriptでクライアントの役割をします。デスクトップアプリといっても実際にはサーバークライアント構成になっています。サーバーで使うポート番号やアプリのウインドウサイズを以下のように指定することもできます。

eel.start("html/index.html", options={'port': 8080}, size=(650, 700))

⑧ 実行ファイルの作成

PyInstallerを使って一つの実行ファイルを作成することができます

python -m eel app.py web --onefile --noconsole

dist,buildの二つのフォルダーが作成され、distの中に実行ファイルがあります。

最後に

html,jsが好きな方には気に入るのではないかと思います。ソースコードをGithubにおいております。ご自由にお使いください。Githubは英語版になります。index.jsのコメントのNOTEの部分の実装すると1秒ごとに画面が更新されます。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away