【簡単Python】pywebview で tkinter ではできない Web スタイルのGUIを作成する方法



いつもありがとうございます!
ノンプログラマー向け「Python解説シリーズ」へようこそ。
本稿では、「pywebview で tkinter ではできない Web スタイルのGUIを作成する方法」を解説いたします。
tkinter は、Pythonでデスクトップアプリを作るための定番GUIライブラリです。標準ライブラリに含まれており、インストールなしですぐ使える手軽さがあります。ただし、見た目はOS標準の部品に依存していて、カスタマイズの自由度は低く、モダンなデザインや洗練されたレイアウトを実現するには向いていません。
こうした問題の解決に使えるのが、pywebview です。
pywebview は、Web技術を活かしてデスクトップGUIを作成できるPythonのライブラリです。HTMLやCSSで作成した画面を、アプリ内のウィンドウに表示し、Pythonと連携することでWeb風のモダンな見た目とローカルアプリの操作性を両立できます。
Python を活用して、自身やコミュニティに適した改善ツールを作成し、仕事量は半分に、成果は 2 倍にしていきましょう。初心者の方にも理解しやすいよう、分かりやすく解説していきます。ぜひ、ご覧ください。
tkinter と pywebview の特徴比較
まず、tkinter と pywebview それぞれの特徴を押さえましょう。主な違いを、下表に整理しました。
それぞれに強みと制約があり、用途や求める表現によって選択肢が分かれます。Pythonだけで完結するシンプルな構成を望む場合は tkinter が適していますが、見た目の自由度やWeb技術との親和性を重視する場合は pywebview が有力な選択肢となるでしょう。
比較項目 | tkinter | pywebview |
---|---|---|
UIの描画方式 | ネイティブウィジェット(※1) | 組み込みWebView(HTML/CSS/JSで描画)(※2) |
見た目のカスタマイズ性 | 限定的(OS依存の古風なデザイン) | 高い(HTMLやCSSを使って柔軟にデザインできる) |
実行時に必要なもの | Pythonだけで動作 | Python + WebViewランタイム(例:WindowsではWebView2) |
使用する技術 | すべてPythonで記述 | PythonとHTML/CSS/JavaScript(またはテンプレート)を組み合わせて記述 |
主な用途 | シンプルな業務ツール、学習用GUI | モダンな見た目の業務アプリ、Web風デザインのデスクトップアプリ |
pywebwiew でGUIを作成する方法

アプリケーションの仕様
ここでは、「タスクの追加・削除・一覧表示」ができるシンプルなタスク管理アプリケーションを例に取ります。下図のようなアプリケーションです。

入力ボックスにタスクを入力し、

追加ボタンを押すとタスクが登録されます。

完了(削除)ボタンを押すと、タスクが削除されます。

なお、これを Tkinter で実装すると、下図のような見た目になります。いかがでしょう。だいぶクラシックな見た目になりますね。

ディレクトリ構成
次のようなディレクトリ構成です。
task_app(任意のディレクトリ名)/
├── .idea/ # 開発環境の設定フォルダ(PyCharmにより自動作成)
├── .venv/ # 仮想環境設定フォルダ(PyCharmにより自動作成)
├── index.html # アプリ画面を作成する HTML ファイル
├── main.py # アプリ全体を実行する Python スクリプト
└── tasks.json # タスクを保存するためのデータファイル(JSON形式)
コードの実装

まず、本アプリケーションのコード全文を示します。Python スクリプト(.py)と HTML ファイル(.html)の 2 つで構成され、連携して機能します。
【Python】
# 外部ライブラリ
import webview
# 標準ライブラリ
import json
import os
class TaskAPI:
def __init__(self, storage_path="tasks.json"):
self.storage_path = storage_path
self.tasks = self._load_tasks()
# ---------- 内部ユーティリティ ----------
def _load_tasks(self):
"""
保存ファイルからタスク一覧を読み込む
:return: 読み込んだタスクリスト(ファイルがない場合は空のリスト)
"""
if os.path.exists(self.storage_path):
with open(self.storage_path, "r", encoding="utf-8") as f:
return json.load(f)
return []
def _save_tasks(self):
"""
タスク一覧をJSONファイルに保存する
:return: なし(処理が成功すればファイルが上書き保存される)
"""
with open(self.storage_path, "w", encoding="utf-8") as f:
json.dump(self.tasks, f, ensure_ascii=False, indent=2)
# ---------- 外部操作用API(JSから呼ばれるメソッド) ----------
def add_task(self, task: str) -> bool:
"""
タスクを追加して保存する
:param task: 追加するタスクの文字列
:return: True ※処理完了をJS側に伝える役割(値自体は使わない)
"""
task = task.strip()
if task:
self.tasks.append(task)
self._save_tasks()
return True # JS側に処理完了を伝えるためのTrue(値自体は使わない)
def get_tasks(self):
"""
登録済みのタスクの一覧を返す
:return: 現在保存されているタスクのリスト
"""
return self.tasks
def remove_task(self, index: int) -> bool:
"""
受け取った index のタスクを削除
:param index: 削除対象となるタスクのインデックス(0始まり)
:return: 削除に成功した場合は True、指定が不正な場合は False
"""
try:
self.tasks.pop(index)
self._save_tasks()
return True
except IndexError:
return False # 念のため
# ---------- PyWebview 起動 ----------
if __name__ == "__main__":
api = TaskAPI()
window = webview.create_window(
"タスク管理アプリケーション",
"index.html",
js_api=api,
width=680,
height=600
)
webview.start(debug=False)
【HTML/CSS/JavaScript】
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>タスク管理アプリ</title>
<style>
/* 見た目を少し整えるだけの最低限スタイル */
body { font-family: sans-serif; margin: 2rem; }
input {
padding: 4px 6px;
width: 100%;
max-width: 500px;
box-sizing: border-box;
}
button { margin-left: 4px; }
li { margin-bottom: .25rem; }
.del-btn { margin-left: 8px; }
</style>
</head>
<body>
<h1>マイタスク一覧</h1>
<input type="text" id="taskInput" placeholder="新しいタスクを入力">
<button onclick="addTask()">追加</button>
<ul id="taskList"></ul>
<script>
// ----------------- 追加 -----------------
async function addTask () {
const taskInput = document.getElementById('taskInput');
const task = taskInput.value.trim();
if (task) {
await window.pywebview.api.add_task(task);
taskInput.value = '';
refreshTasks();
}
}
// ----------------- 削除 -----------------
async function deleteTask (index) {
await window.pywebview.api.remove_task(index);
refreshTasks();
}
// ----------------- 一覧表示 -----------------
async function refreshTasks () {
const tasks = await window.pywebview.api.get_tasks();
const list = document.getElementById('taskList');
list.innerHTML = '';
tasks.forEach((task, idx) => {
const li = document.createElement('li');
li.textContent = task;
const delBtn = document.createElement('button');
delBtn.textContent = '完了(削除)';
delBtn.className = 'del-btn';
delBtn.onclick = () => deleteTask(idx);
li.appendChild(delBtn);
list.appendChild(li);
});
}
// PyWebview が準備できたら初期表示
window.addEventListener('pywebviewready', refreshTasks);
</script>
</body>
</html>

解説していきます。
まず、Python のコードから解説していきます。
# 外部ライブラリ
import webview
# 標準ライブラリ
import json
import os
ここでは、アプリの動作に必要なライブラリをインポートしています。
webview
は、アプリ内にウィンドウを表示し、その中に HTML を描画してユーザーインターフェースを構成するためのライブラリです。
json
はタスクのデータを保存・読み込みする際に使用し、os
はファイルの存在確認などを行うライブラリです。
class TaskAPI:
def __init__(self, storage_path="tasks.json"):
self.storage_path = storage_path
self.tasks = self._load_tasks()
ここでは、タスクを管理するためのクラス TaskAPI
を定義しています。
__init__
は、このクラスを使うときに最初に呼び出される初期化処理です。
タスクの保存先となるファイルのパスを self.storage_path
に記録し(デフォルト値は "tasks.json"
)、そのファイルからタスクの一覧を読み込んで self.tasks
に保持しています。
# ---------- 内部ユーティリティ ----------
def _load_tasks(self):
"""
保存ファイルからタスク一覧を読み込む
:return: 読み込んだタスクリスト(ファイルがない場合は空のリスト)
"""
if os.path.exists(self.storage_path):
with open(self.storage_path, "r", encoding="utf-8") as f:
return json.load(f)
return []
ここでは、保存ファイル(tasks.json
)からタスクの一覧を読み込む内部メソッド _load_tasks
を定義しています。
self.storage_path
に指定されたファイル(tasks.json
)が存在すれば、その中に保存されたタスク一覧を読み込み、Python のリストとして返します。tasks.json
は、テキストでデータをやり取りするJSON(JSON)形式のファイルです。なお、ファイルが存在しない場合は、空のリストを返します。
def _save_tasks(self):
"""
タスク一覧をJSONファイルに保存する
:return: なし(処理が成功すればファイルが上書き保存される)
"""
with open(self.storage_path, "w", encoding="utf-8") as f:
json.dump(self.tasks, f, ensure_ascii=False, indent=2)
ここでは、self.tasks
に保持しているタスク一覧をファイルに書き出す内部メソッド _save_tasks
を定義しています。
open
でself.storage_path
(tasks.json
)を 書き込みモード("w"
)かつ UTF-8 で開きます。json.dump
を使って、Python のリスト形式になっているself.tasks
を JSON形式のテキスト に変換して書き込みます。ensure_ascii=False
によって日本語もそのまま保存し、indent=2
で読みやすいようにインデント付きで整形します。
タスクを追加・削除したあとにこのメソッドを呼ぶことで、最新のタスク一覧が tasks.json
に反映されます。
# ---------- 外部操作用API(JSから呼ばれるメソッド) ----------
def add_task(self, task: str) -> bool:
"""
タスクを追加して保存する
:param task: 追加するタスクの文字列
:return: True ※処理完了をJS側に伝える役割(値自体は使わない)
"""
task = task.strip()
if task:
self.tasks.append(task)
self._save_tasks()
return True # JS側に処理完了を伝えるためのTrue(値自体は使わない)
ここでは、外部(JavaScript側)から呼び出される API メソッド add_task
を定義しています。
task
には追加したいタスク名が文字列として渡されます。task.strip()
で入力値の前後にある余分な空白や改行を取り除きます。- 空でなければ
self.tasks
に追加し、_save_tasks()
でファイルへ保存します。 - 最後に
True
を返します。これは JavaScript 側に「処理が完了した」ことを示すための値で、Python 内では特別な意味を持ちません。
このメソッドは、フロントエンド(HTML/JavaScript)から新しいタスクを受け取り、内部リストと保存ファイルを更新する入り口となります。
def get_tasks(self):
"""
登録済みのタスクの一覧を返す
:return: 現在保存されているタスクのリスト
"""
return self.tasks
ここでは、現在保存されているタスクの一覧を取得するための get_tasks
メソッドを定義しています。
このメソッドは、self.tasks
に保持されているタスクリストをそのまま返します。外部( JavaScript 側)からタスクの一覧を参照したいときに呼び出される仕組みです。特別な処理はなく、リストの中身をそのまま渡す役割を持ちます。
def remove_task(self, index: int) -> bool:
"""
受け取った index のタスクを削除
:param index: 削除対象となるタスクのインデックス(0始まり)
:return: 削除に成功した場合は True、指定が不正な場合は False
"""
try:
self.tasks.pop(index)
self._save_tasks()
return True
except IndexError:
return False # 念のため
ここでは、指定された位置にあるタスクを削除するための remove_task
メソッドを定義しています。
このメソッドは、引数 index
(インデックス)に対応するタスクを self.tasks
から削除し、その後に _save_tasks()
を呼び出して変更内容をファイルに保存します。
pop
は、Python のリストに用意されているメソッドで、指定した位置にある要素をリストから取り出して削除する働きをします。
インデックスが不正でリストの範囲外を指定した場合には IndexError
が発生しますが、それを except
節で捕捉し、安全に False
を返すようになっています。
削除が成功した場合には True
を返し、成功・失敗を呼び出し元に伝える設計です。
# ---------- PyWebview 起動 ----------
if __name__ == "__main__":
api = TaskAPI()
window = webview.create_window(
"タスク管理アプリケーション",
"index.html",
js_api=api,
width=680,
height=600
)
webview.start(debug=False)
ここでは、Python スクリプトとしてこのファイルが直接実行された場合にアプリを起動する処理を記述しています。
- まず
api = TaskAPI()
によって、タスク管理用のクラスTaskAPI
のインスタンスを作成します。これが、JavaScript から呼び出すAPIとして使われます。 - 次に
webview.create_window()
を使って、ウィンドウを作成しています。ウィンドウのタイトルには"タスク管理アプリケーション"
が表示され、表示される内容は"index.html"
に記述されたHTMLです。引数js_api=api
により、HTML/JavaScript 側から Python のapi
(TaskAPIインスタンス)を呼び出せるようになります。 - ウィンドウのサイズは
width=680
,height=600
に設定されています。 - 最後に
webview.start(debug=False)
を呼び出すことで、アプリケーションが起動します。debug=False
にしていることで、開発用のデバッグモードは無効となり、本番向けの挙動になります。

以上で、Python側のコードの解説は終了です。

次に、HTML/CSS/JavaScript コードを解説していきます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>タスク管理アプリ</title>
<style>
/* 見た目を少し整えるだけの最低限スタイル */
body { font-family: sans-serif; margin: 2rem; }
input {
padding: 4px 6px;
width: 100%;
max-width: 500px;
box-sizing: border-box;
}
button { margin-left: 4px; }
li { margin-bottom: .25rem; }
.del-btn { margin-left: 8px; }
</style>
</head>
ここでは、アプリのHTMLの基本構造とスタイルの定義を行っています。
【 基本構造の指定】
<!DOCTYPE html>
HTML5文書であることを示します。<html lang="ja">
使用言語が日本語であることを指定します。<head>
ブラウザに表示されない「設定情報」のセクションです。
【 ヘッダ内の要素】
<meta charset="UTF-8">
日本語を正しく表示するための文字コード指定です。<title>
ブラウザのタブに表示されるタイトル(この場合は「タスク管理アプリ」)です。<style> ~ </style>
アプリ全体の見た目を整えるCSSです。以下のような調整が行われています。
【 スタイルの設定内容】
body
: フォントをサンセリフ体にし、ページの余白を確保input
: テキスト入力欄のパディングや最大幅を指定し、入力しやすさを向上button
: ボタン同士の間隔(左側)を空けて見た目を調整li
: タスクごとの縦の間隔を確保し、一覧の読みやすさを改善.del-btn
: 「削除」ボタン専用の余白を設定し、タスク名から話して見やすさを向上
<body>
<h1>マイタスク一覧</h1>
<input type="text" id="taskInput" placeholder="新しいタスクを入力">
<button onclick="addTask()">追加</button>
<ul id="taskList"></ul>
ここでは、タスクの入力欄、追加ボタン、タスクリストの表示エリアといった、アプリの基本的なインターフェースが構成されています。
<h1>マイタスク一覧</h1>
ページ上部に大きく表示される見出しです。このアプリケーションが「タスク管理」であることが、ユーザーに直感的に伝わるように設計されています。<input type="text" id="taskInput" placeholder="新しいタスクを入力">
タスクを入力するためのテキストボックスです。placeholder
属性により、「新しいタスクを入力」という薄いガイド文字が表示され、入力欄の用途がひと目で伝わります。また、この要素にはid="taskInput"
が設定されており、JavaScript 側からこの入力欄を特定し、操作するための識別子として使われます。ユーザーが入力した内容を取得したり、追加後に入力欄をクリアしたりする処理に利用されます。<button onclick="addTask()">追加</button>
入力欄の右側に配置された追加ボタンです。ユーザーがこれをクリックすると、JavaScriptで定義されたaddTask()
関数が呼び出され、タスクがPython側に送信・保存されます。<ul id="taskList"></ul>
追加されたタスクを一覧として表示するための領域です。JavaScript 側の処理によって、各タスクは<li>
要素としてこのリストに動的に追加されます。id="taskList"
が設定されていることで、JavaScript からこの要素を特定し、内容の描画や更新といった操作を直接行うことができます。
<script>
// ----------------- 追加 -----------------
async function addTask () {
const taskInput = document.getElementById('taskInput');
const task = taskInput.value.trim();
if (task) {
await window.pywebview.api.add_task(task);
taskInput.value = '';
refreshTasks();
}
}
ここでは、タスクを追加するための関数 addTask()
が定義されています。ユーザーが「追加」ボタンを押したときに実行される処理です。
const taskInput = document.getElementById('taskInput');
HTML側の入力欄(<input>
)を取得します。先に説明したように、id="taskInput"
によってこの要素を特定できます。const task = taskInput.value.trim();
入力欄から文字列を取得し、前後の空白を除去します。trim()
によって、意図しないスペースや改行だけの入力を防ぎます。if (task) { ... }
空文字でなければ、以下の処理を実行します。これにより、空のタスクが登録されないようになっています。await window.pywebview.api.add_task(task);
PyWebView を通じて、Python 側のadd_task()
メソッドが呼び出され、タスクの内容がファイルに保存されます。この処理は「非同期」で実行されるため、ユーザーの操作を止めることなく、裏側でスムーズに完了します。await
は、JavaScript において非同期処理の完了を待つためのキーワードwindow
は、JavaScript における最上位のグローバルオブジェクトwindow.pywebview
は、PyWebView が JavaScript 側に提供する特殊なオブジェクトwindow.pywebview.api
には、Python 側で登録したクラス(TaskAPI
)のメソッドがまとめられている(js_api=api
)window.pywebview.api.add_task(task)
は、上記のクラスから.add_task()
を呼び出している
taskInput.value = '';
タスクの追加後、入力欄を空に戻します。これにより、連続でタスクを追加しやすくなります。refreshTasks();
最新のタスクリストを取得して再描画します。新しく追加されたタスクがすぐに画面に反映されます。
この関数は、HTML と Python の間をつなぐ役割を果たしています。ユーザーが入力した内容を受け取り、それを Python 側に渡して保存し、さらに画面上に即座に反映するという、一連の処理がまとめられています。
// ----------------- 削除 -----------------
async function deleteTask (index) {
await window.pywebview.api.remove_task(index);
refreshTasks();
}
ここでは、指定されたタスクを削除するための関数 deleteTask()
が定義されています。削除ボタンをクリックしたときに実行される処理です。
async function deleteTask(index)
削除対象のタスクを特定するための引数index
(タスクリスト内の番号)を受け取る非同期関数です。このindex
は、タスク一覧を表示するrefreshTasks()
の中で、各削除ボタンに割り当てられています。await window.pywebview.api.remove_task(index);
Python 側のremove_task()
メソッドを呼び出して、該当のタスクを削除します。await
を使うことで、削除が確実に終わってから画面更新が行われるようになります。refreshTasks();
タスクリストの表示を最新の状態に更新します。削除されたタスクはこの時点で一覧から消えます。
// ----------------- 一覧表示 -----------------
async function refreshTasks () {
const tasks = await window.pywebview.api.get_tasks();
const list = document.getElementById('taskList');
list.innerHTML = '';
tasks.forEach((task, idx) => {
const li = document.createElement('li');
li.textContent = task;
const delBtn = document.createElement('button');
delBtn.textContent = '完了(削除)';
delBtn.className = 'del-btn';
delBtn.onclick = () => deleteTask(idx);
li.appendChild(delBtn);
list.appendChild(li);
});
}
ここでは、現在のタスク一覧を取得して画面上に表示し直すための関数 refreshTasks()
が定義されています。タスクの追加・削除後に必ず呼び出され、最新の状態を反映する役割を果たします。
const tasks = await window.pywebview.api.get_tasks();
Python 側のget_tasks()
メソッドを呼び出して、現在保存されているタスクリストを取得します。ここも非同期処理となっており、await
によって取得が完了するまで次に進みません。const list = document.getElementById('taskList');
HTML内の<ul>
要素(タスクリストの表示エリア)を取得します。これによって、JavaScript からこのリストの中身を操作できるようになります。list.innerHTML = '';
リストの中身を一旦すべて削除します。こうすることで、前の表示内容をクリアしてから新たに描画できるようになります。tasks.forEach((task, idx) => { ... });
取得したタスク配列を1つずつ処理します。task
にはタスクの内容(文字列)、idx
にはその順番(インデックス番号)が入ります。const li = document.createElement('li');
新しい<li>
要素を作成し、リストアイテム用の変数li
に格納します。li.textContent = task;
変数li
に、タスクの文字列を設定します。const delBtn = document.createElement('button');
「完了(削除)」ボタンを作成します。delBtn.textContent = '完了(削除)';
完了(削除)ボタンのラベルを設定します。delBtn.className = 'del-btn';
ボタンにクラス名を付けています。delBtn.onclick = () => deleteTask(idx);
削除ボタンがクリックされたときに実行される処理を登録します。ここでdeleteTask()
にidx
を渡すことで、該当タスクを削除できるようにしています。li.appendChild(delBtn);
リストアイテム(タスクの文字列)の横に削除ボタンを設置します。list.appendChild(li);
完成した<li>
を、<ul>
の中に追加します。これによって、画面上に1つのタスク項目が表示されます。
この関数は、タスク一覧の「描き直し」を担当する機能です。タスクの追加や削除が発生したあと、常にこの関数が呼び出されることで、画面表示が最新の状態に保たれます。
// PyWebview が準備できたら初期表示
window.addEventListener('pywebviewready', refreshTasks);
</script>
</body>
</html>
ここでは、PyWebView が立ち上がり、JavaScript から Python にアクセスできる状態になったときに、自動的に refreshTasks()
を実行するよう設定しています。アプリ起動直後にタスク一覧を表示するための初期化処理です。
PyWebView の初期化が完了したタイミングで pywebviewready
というイベントが発生します。そのイベントが発生したときに refreshTasks()
を実行するよう設定しています。これにより、ウィンドウが表示された直後にタスク一覧が取得・表示されます。

以上で解説は終了です。ありがとうございました。
Python プログラミングスキルアップのための参考情報
ここでは参考図書を紹介いたしますが、これに限らず自分に合うものを選ぶことが重要だと考えております。皆様の、より一層のご成功を心からお祈りしております。
「独学プログラマー」というPythonを題材にした書籍は大変勉強になりました。Pythonの技術解説だけにとどまらず、プログラミングの魅力や基本的な知識、思考法、仕事の進め方まで幅広く学べます。
こちらの記事でも紹介しております。もしよろしければご覧ください。
また、Pythonに関する書籍は多数出版されています。興味のある方は、チェックしてみてください。
\チェックしてみよう/
\チェックしてみよう/
\チェックしてみよう/

【初心者歓迎】【無料相談受付中】業務改善アプリケーション作成のご相談を承ります

いつもありがとうございます!
限られた時間をより良く使い、日本の生産性を高めたい──
みんなの実用学を運営するソフトデザイン工房では、業務整理や業務改善アプリケーション作成のご相談を承っております。
お気軽にご相談ください。
こちらの記事でも紹介しております。
おわりに


ご覧いただきありがとうございました!
本稿では、「pywebview で tkinter ではできない Web スタイルのGUIを作成する方法」を解説いたしました。
お問い合わせやご要望等ございましたら、「お問い合わせ/ご要望」またはコメントにて、ご連絡いただければ幸いでございます。
皆様の人生がより一層素晴らしいものになるよう、少しでもお役に立てれば幸いでございます。
なお、当サイトでは様々な情報を発信しております。もしよろしければ、トップページもご覧いただけると幸いでございます。
この記事を書いた人
- ソフトデザイン工房|日々の業務にちょうどいい自動化を
- ■人生を追求する凡人 ■日本一安全で、気の向くままに自分の時間を過ごせる、こだわりのキャンプ場を作るのが夢 ■ソフトデザイン工房代表(個人事業者) - 業務設計&業務支援アプリケーション作成サービスを展開 ■人生は時間そのもの。ひとりでも多くの人が「より良い人生にするために時間を使って欲しい」と願い、仕事のスキルの向上、余暇の充実、資産形成を研究。
最新の投稿
01|業務効率化・自動化2025年7月30日【簡単Python】pywebview で tkinter ではできない Web スタイルのGUIを作成する方法
02|WordPress/ブログ2025年7月27日【簡単WordPress】テーマ更新時に Hilighting Code Block が効かなくなる原因
01|業務効率化・自動化2025年7月26日【簡単Python】画面操作ライブラリ「PyAutoGUI」をマルチモニターに対応させるシンプルな方法
01|業務効率化・自動化2025年7月13日【エクセル/Excel 便利機能紹介】検索でヒットしたすべてのセルをまとめて選択する方法【動画あり】