概要
pywebviewというGUIライブラリを使って、簡単なアプリのコードの例を紹介します。
いわゆるTODOアプリでタスクを登録し、必要がなくなれば削除できる機能と
jsonを使ってファイルにタスクを保存し、読み込む機能を備えます。
画面操作等ブラウザ操作は、javascriptでほかの複雑な計算やローカルのファイル操作等は、pythonが担当と言った構成になるかと思います。
インストール
pywebviewは、
pip install pywebview
で、インストールできます。
TODOアプリ コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
import webview import json import os # 初期データ tasks = [] # JSONファイルのパス json_file = "tasks.json" # タスクをJSONファイルから読み込む def load_tasks_from_file(): global tasks if os.path.exists(json_file): with open(json_file, "r", encoding="utf-8") as file: tasks = json.load(file) else: tasks = [] # タスクをJSONファイルに保存する def save_tasks_to_file(): with open(json_file, "w", encoding="utf-8") as file: json.dump(tasks, file, indent=4, ensure_ascii=False) # JavaScriptと連携するクラス class Api: def get_tasks(self): """タスク一覧を取得""" return tasks def add_task(self, task): """タスクを追加""" if task: tasks.append(task) return {"status": "success", "message": "Task added"} return {"status": "error", "message": "Task cannot be empty"} def remove_task(self, task_index): """タスクを削除""" try: del tasks[task_index] return {"status": "success", "message": "Task removed"} except IndexError: return {"status": "error", "message": "Invalid index"} def save_tasks(self): """タスクを保存""" save_tasks_to_file() def load_tasks(self): """タスクを読み込み""" load_tasks_from_file() # HTMLテンプレート html = """ <!DOCTYPE html> <html> <head> <title>ToDo App</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .task { display: flex; justify-content: space-between; margin: 5px 0; } button { margin-left: 10px; } .menu { margin-bottom: 10px; } </style> </head> <body> <h1>ToDo App</h1> <div class="menu"> <button onclick="saveTasks()">Save JSON</button> <button onclick="loadTasks()">Load JSON</button> </div> <input id="taskInput" type="text" placeholder="Enter a task" /> <button onclick="addTask()">Add Task</button> <ul id="taskList"></ul> <script> async function fetchTasks() { const tasks = await pywebview.api.get_tasks(); const taskList = document.getElementById('taskList'); taskList.innerHTML = ''; tasks.forEach((task, index) => { const li = document.createElement('li'); li.className = 'task'; li.innerHTML = ` ${task} <button onclick="removeTask(${index})">Delete</button> `; taskList.appendChild(li); }); } async function addTask() { const taskInput = document.getElementById('taskInput'); const task = taskInput.value.trim(); if (task) { const response = await pywebview.api.add_task(task); if (response.status === 'success') { taskInput.value = ''; fetchTasks(); } else { alert(response.message); } } else { alert('Task cannot be empty'); } } async function removeTask(index) { const response = await pywebview.api.remove_task(index); if (response.status === 'success') { fetchTasks(); } else { alert(response.message); } } async function saveTasks() { await pywebview.api.save_tasks(); alert('Tasks saved to JSON file.'); } async function loadTasks() { await pywebview.api.load_tasks(); fetchTasks(); } // ページ読み込み後に0.5秒待ってからloadTasks()を実行 window.onload = function() { setTimeout(function() { loadTasks(); }, 500); }; </script> </body> </html> """ # PyWebviewの設定 def start_app(): load_tasks_from_file() # 起動時にタスクを読み込む api = Api() # ウィンドウの作成 webview.create_window( 'ToDo App', html=html, js_api=api, width=800, height=600, resizable=True ) webview.start() if __name__ == '__main__': start_app() |
コードを実行すると以下のような画面になります。
この場合、すでにタスクが幾つか登録されています。
コード解説
全体の構造
タスクは、jsonファイルとして保存します。
中身は、ただタスク名の配列が一つあるだけです。
タスクに変更があるとPython内部では、jsonの配列をリストとして扱っていてそれを増減するだけの仕組みです。
そして、保存する時はjsonファイルに変換して保存します。
逆にロードボタンを押したときや起動時にはjsonファイルを読み込みます。
(今気が付きましたが”Load”ボタンは、いらないですね。使う機会がないや。
使うとしたらファイルを手動で書き換えるとかjsonファイルを複数用意して名前を書き換えてロードするとかですね。練習用なのでそのままにしておきます。)
htmlテンプレートをヒアドキュメントとしてコードと同じファイルに持っています。
もちろん本来は、htmlを外部のファイルとして読み込むこともできます。
PythonがわApiクラス内にメソッドを登録して、ブラウザ側からはそれらのメソッドを使うような仕組みになっています。
メインブロック
説明の都合上、コードの解説が前後します。
150~167行
152:タスクを保存したjsonファイルがあれば読み込みます。
153:Apiクラスをインスタンス化します。
156:webview.create.window()でウィンドウを作ります。
158:ウィンドウのテンプレートにヒアドキュメントで定義したhtmlを指定します。
159:Apiをjavascriptから呼び出せるapiとして定義します。
Json関連の関数
11~23行目 load_tasks_from_file() save_tasks_from_file()
Jsonを読み込む関数と保存する関数が2つ定義されています。
13行目:”load_tasks_from_file()”内の”global tasks”は、16行目で代入しているのでglobalを指定しています。代入する場合は断らない限りローカル変数として扱われます。ですのでglobalを指定しない限り6行目の”tasks”には、代入されません。
代入ではなく、”tasks”を変更するだけの時は変更は反映されます。
“save_tasks_from_files()”でglobal宣言が無いのは、”tasks”で読み取るだけだからです。
Apiクラス
25~52行目 class Api(): …
JavaScriptから呼び出すPythonの機能がクラスとしてまとめられています。
内容は、タスクを管理するリスト”tasks”に要素を増やしたり削除したりする機能が中心で
メソッドによってそのメソッドの成否を辞書として返しています。
Htmlテンプレート
54~148行目 html = “””…”””
ここで、htmlと内部でJavaScriptを使って画面の書き換えを行っています。
78~86行目 <body> …
画面は、ここで作られています。
86行目
<ul id="taskList"></ul>
このリストに要素(<li>…</li>)を増減することで画面上のタスクを増減します。
PythonのApiクラスの利用
先にJavaScriptからPythonのApiクラスのメソッドを使う方法を解説します。
90行目
const tasks = await pywebview.api.get_tasks();
この行の様に “pywebview.api.XXX” で登録したメソッド”XXX”が呼び出され値が返されます。
このようにしてHtmlとPythonの間で情報がやり取りされます。
JavaScript
89~102行目 fetchTasks()
この関数は、タスクをいったん全て消してから”tasks”の内容から再度タスク画面を再構築しています。async, awaitを使って非同期に処理を行っています。
104~118行目 addTask()
“tasks”に”task”を追加して、追加された”tasks”から “fetchTasks()” を使ってタスク画面を再構築しています。
120~127行目 removeTask()
“addTask()”と同様のやり方でタスクを削除しています。
129~137行目 loadTasks() saveTasks()
140~144行目
“window.onload”イベントに “loadTasks()” を登録しています。
つまり、画面が読み込まれたらタスクを読み込み画面を構築しています。
その際、0.5秒待ってから “loadTasks()” を行っています。
待たないとうまく描画できません。
本来は、あまり良くない対応かと思います。
“pywebviewready”と言うpywebviewの準備が整ったときに何かするというイベントも用意されているのですがこれを使用してもうまく行きませんでした。
ちょっと、対処療法的なやり方ですがこれで良しとします。
まとめ
PyWebviewを使用すると、PythonとWeb技術を組み合わせて手軽にGUIアプリケーションを作成できます。
Htmlを使えば画面は、自由自在ですね。
今回のTODOアプリでは、Pythonでバックエンド処理とファイル操作を行い、JavaScriptでフロントエンドの操作と画面描画を担当しました。
このアプリを基に、さらなる機能拡張やカスタマイズが可能です。
Commnts