PythonでGUIアプリの作成

## はじめに
Pythonでコードを書いて、いつもタミナルからコマンドを叩いて実行しています。自分としては、コマンド叩くのが楽ですが、お客様の事を考えるとGUIのほうが使いやすいと思います。そのために、Tkinterを使っていたのですが、レイアウトの調整が面倒でした。

. [Electron](https://electronjs.org/)が好きなのでPythonでも同じような事したいな、
. html（実際はJavascript）からPythonの関数呼びだしたいな
という思いがあり、ググってみたら[Eel](https://github.com/ChrisKnott/Eel)と出会いました。

簡単に実装ができ、とても満足しています。
>Pyinstallerを使う事で一つの実行ファイルを作ることもできます。デモ用のアプリをお客様に渡したいときは大変役に立ちます。

## アプリの概要
今回はGUIで入力した情報をもとにPythonの処理を実行するアプリを作ります。
具体的には、
1. 画面にてNTPサーバーを入力します
2. Javascriptを使ってPythonの関数を実行します
3. Python側で受け取ったNTPサーバーに時刻の問い合わせを行い、結果をJavascriptに返します
4. Javascriptが受け取った結果を画面に表示します
<img src="https://github.com/dakc/eel_basic/blob/master/gaiyo.png?raw=true" width="300px">

今回作るアプリが以下のようになります。
<img src="https://github.com/dakc/eel_basic/blob/master/app_ja.png?raw=true" width="300px">

## Let's Code
#### ① フロント開発
まずは、フロントの部分を実装するために以下の構成のフォルダーを作り、空のファイルを作ります。一般のウエブページを作った時の全く同じです。イメージ「img.jpg」は[こちらよりダウンロード](https://github.com/dakc/eel_basic/blob/master/web/image/img.jpg?raw=true)し、「image」フォルダーにおいてください。

```bash

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

僕はこの構成が好きですが、CSSやJSすべてhtmlに書いてもOKです。フォルダーを分けず一つのフォルダーにまとめてもOKです。

「index.html」の中身が以下のようになります。

```index.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」の中身が[こちらのように](https://github.com/dakc/eel_basic/blob/master/web/css/index.css)なります。コードだけ
で記事が長くなるのでリンクを張っております。そちらからコピペしてください。

この状態で**index.htmlをブラウザ**で開きます。以下のように表示されたらOKです。
<img src="https://github.com/dakc/eel_basic/blob/master/web_ja.png?raw=true" width="300px">

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

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

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

```bash
pip install eel
pip install ntplib
```
app.pyの中身を以下のようにします。

```app.py
# eelのインポート
import eel

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

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

たったこの3行（コメント抜き）です。


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

```bash
python app.py
```

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

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

```html
<script type="text/javascript" src="/eel.js"></script>
<script src="../js/index.js"></script>
```
1行目：eelをJavascriptから利用できるようにしたスクリプトです。気にしないでそのまま書きましょう。eelが用意してくれるので、ファイルが見えないから焦る必要がありません。
2行目：今回作る予定のスクリプトです。ファイルパスはフォルダー構成によって変わります。

最終的のhtmlファイルは以下のようになります。

```index.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」の中身が以下のように変えます。

```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」には、ボタンをクリックした時の処理を記述します。

```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に以下のように追加します。

```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**でインストールしておきましょう。

```app.py
# 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サーバーを指定し、ボタンを押してみてください。

```bash
python app.py
```

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

```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);
}
```

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

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

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

```bash
python -m eel app.py web --onefile --noconsole
```
dist,buildの二つのフォルダーが作成され、distの中に実行ファイルがあります。

> ※ PyInstallerは以下のコマンドでインストールできます。
>
```bash
pip install pyinstaller
```

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