概要
pythonで簡単にwindowsのデスクトップアプリを作りたいなあと思ってググると
https://qiita.com/ForestMountain1234/items/bc709c3599ee86e2a0dd
というような記事が見つかった。ここで紹介されているfletというのを使ったりtkinterを使うのがおそらく王道だと思うのだが、よくよく調べてみるとfletは実際にはwebアプリとして機能するらしい。
それならば、普段使い慣れたflaskでも同じようなことが出来るのでは無いかと思い、作成してみたのが本記事である。
メリット
- 普段使い慣れたflaskで作成できる(人による)。
- templateとviewを分離できる。
- CSSでおしゃれな外観にできる
具体例
最小限の構成はこんな感じ
.
├── デスクトップアプリ風アプリ.vbs
├── app.py
├── application
├── __init__.py
├── view
└── templates
└── index.py
デスクトップアプリ風アプリ.vbsをダブルクリックしてアプリを起動する。
Set ws = CreateObject("Wscript.Shell")
ws.run "python app.py", vbhide
ws.run "http://localhost:8888/"
アイデアとしては次の通り
- vbsファイルが起動されたときにflaskアプリの起動とブラウザでの接続を行う。
- flaskアプリは基本的に終了しない。そのため多重起動の防止が重要。
ちなみに、vbsファイルを使用している理由はpythonをバックグラウンドで実行するため。vbScriptは既に非推奨だが、その時が来たら良い代替も出ているだろう。ちなみに普通のbatファイルでやる場合でも、
start /min python app.py
start chrome "http://localhost:8888"
などと/minオプションを付けるだけでそれっぽくなるし、終了もpythonが動いているコマンドプロンプトでCtrl+Cで出来るようになる。
app.py
PORT = 8888
DEBUG = True # 完成したらFalseにする
# ポイント1 多重起動の防止
import socket
if not DEBUG:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("localhost", PORT)) # 既にflaskが起動していると例外が発生して落ちる
sock.close()
from application import app
if __name__ == "__main__":
app.run(debug=DEBUG, port=PORT) # host="0.0.0.0"を付けると勝手に公開されるので注意。
flaskが起動するとPORTがbindできないことを利用して、多重起動の防止としている。ポイントとして、DEBUG=Trueのとき、flaskのテストサーバーは何故か2回起動するためこの方法で多重起動が防止できない。開発中はファイル更新で自動的に再起動する挙動は便利なのでdebug=Trueとして、自分で起動する(起動用のvbsを使用しない)。そして、debug=TrueとするとCPU使用率がめちゃくちゃ高い(弊環境では待機しているだけで14~15%)ので、完成したらdebug=Falseとするのが吉。
あくまで簡易的なものが目標なので、ちゃんとエラーをキャッチして処理した後os._exit(1)で落ちるようにするとかロックファイルを使用するというのが正しいとは思うが、最小限はコレだと思っている。あとimportとかの順番がめちゃくちゃなのだが、なんというか、既に起動している時にアプリを読み込むのも無駄だなあと思ってこうしている。初期化が必要なアプリだと、勝手に初期化されるのを防ぐという意味合いもある。
application/__init__.py
from flask import Flask, render_template
app = Flask(__name__)
# 初期化とかしたければこの辺に書いておく
@app.route("/index")
def index():
return render_template("index.html")
application/template.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>テストアプリ</title>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>
このあたりは一般的なflaskアプリと全く同じ。情報量がfletとかに比べて多いところもメリットだと思う。
全く本編と関係ないが、簡単に高速にアプリを開発するときのおすすめはalpine.jsである。時間があればalpine.js絡みの記事も書いてみたい。
終了について
debug=Falseで起動したflask(のテストサーバ)は待機中に目くじらを立てるほどリソースは消費しないため、基本的にはパソコンのシャットダウンで終了で問題ないと思うが、何らかの理由で(アプリを再起動したい等)終了する必要があるときは、
tasklist | find -i "python"
でプロセスID(PID)を取得して
taskkill /pid 取得したPID
で終了できる。
他の方法として
@app.route('/shutdown')
def app_shutdown():
sys.exit(1)
return {}
のようなコードを入れておき、http://localhost:8888/shutdownにアクセスすると
終了する。ボタンのような形式でリンクを張っておけば終了ボタンクリックで終了すると言ったことも可能。
終了について(余談)
当初は、デスクトップアプリなのだからウインドウを消したときに終了した方が良いと思い次のような機構を考えた。
- フロントは一定時間ごと(例えば、1分ごと)に/keep-aliveにGETリクエストを送信する。
- バックエンドは/keep-aliveにアクセスがあったらタイマー変数を3に戻す。
- threadingで別スレッドを立ち上げて1分に1回タイマー変数をデクリメントする。
- タイマー変数が0になったら終了(os._exit(1))する。
というようなコードを実装していた。ただ、"簡単に"作成することが今回の目的であったので、ここまで複雑なことはしたくないなあと思い、本手法に至った。