wxPython
wxWidgetsのPythonバインディング。各プラットフォームのネイティブUIコントロールを使用し、OS標準の外観を実現。軽量で高性能な業務アプリケーション開発に適している。
GitHub概要
wxWidgets/Phoenix
wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.
トピックス
スター履歴
フレームワーク
wxPython
概要
wxPythonは、PythonでネイティブなクロスプラットフォームGUIアプリケーションを開発するためのフレームワークです。wxWidgetsをベースとし、各プラットフォームのネイティブなルック&フィールを提供します。豊富なウィジェット、高度なコントロール、強力なイベントシステムを特徴とし、プロフェッショナルなデスクトップアプリケーション開発に適しています。
詳細
wxPythonは2025年において、Python GUIフレームワークの中で「ネイティブ外観」を重視する開発者の重要な選択肢です。30年近い歴史を持つwxWidgetsライブラリをベースとしており、Windows、macOS、Linuxでそれぞれのプラットフォームに最適化された外観とユーザビリティを実現します。
最大の特徴は、各プラットフォームのネイティブウィジェットを使用することで、OSの標準的なルック&フィールを維持することです。これにより、ユーザーが慣れ親しんだインターフェースでアプリケーションを提供でき、クロスプラットフォーム展開時もプラットフォーム固有の使い勝手を損ないません。
豊富な高度なウィジェット(グリッド、リッチテキスト、ツリーコントロール、OpenGLキャンバス等)、強力なイベントドリブンアーキテクチャ、wxGladeによるビジュアルUI設計、印刷機能、ドラッグ&ドロップ、国際化サポートなど、エンタープライズアプリケーション開発に必要な機能が包括的に提供されています。
GIMP、Audacity、BitTorrent、Chandlerなど、多くの著名なアプリケーションでwxPythonが採用されており、商用・オープンソース問わず幅広い分野で実績があります。
メリット・デメリット
メリット
- ネイティブ外観: 各プラットフォームの標準UIを自動的に採用
- 豊富なウィジェット: 高度なコントロールとカスタムウィジェット
- 成熟したライブラリ: 30年近い開発実績と安定性
- クロスプラットフォーム: Windows、macOS、Linux完全対応
- 強力なイベントシステム: 柔軟なイベントドリブン開発
- 印刷・描画機能: 高度な描画とドキュメント出力機能
- ドラッグ&ドロップ: 直感的なファイル操作とUI
- 国際化サポート: 多言語アプリケーション開発
- wxGlade統合: ビジュアルUI設計ツール
- 商用利用可能: wxWindowsライセンスで商用利用に制約なし
デメリット
- 学習コスト: 高機能ゆえの複雑な概念とAPI
- パッケージサイズ: 豊富な機能によりバイナリサイズが大きい
- メモリ使用量: ネイティブウィジェットによるメモリ消費
- モダンデザイン: 最新のフラットデザインには不向き
- アニメーション: 動的UIエフェクトの実装が限定的
- 配布の複雑さ: 依存関係とプラットフォーム固有の配布
主要リンク
- wxPython公式サイト
- wxPython公式ドキュメント
- wxPython GitHub リポジトリ
- wxWidgets公式サイト
- wxGlade - ビジュアルUIデザイナー
- wxPython Demo
書き方の例
Hello Worldアプリケーション
# hello_world.py
import wx
class HelloWorldFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='wxPython Hello World')
# フレームのサイズと位置設定
self.SetSize(300, 200)
self.Center()
# メインパネル
panel = wx.Panel(self)
# レイアウト用のシーザー
main_sizer = wx.BoxSizer(wx.VERTICAL)
# ラベル
self.label = wx.StaticText(panel, label="Hello, wxPython!")
main_sizer.Add(self.label, 0, wx.ALL | wx.CENTER, 20)
# ボタン
self.button = wx.Button(panel, label="クリックしてください")
self.button.Bind(wx.EVT_BUTTON, self.on_button_click)
main_sizer.Add(self.button, 0, wx.ALL | wx.CENTER, 10)
# カウンター表示
self.counter_label = wx.StaticText(panel, label="クリック回数: 0")
main_sizer.Add(self.counter_label, 0, wx.ALL | wx.CENTER, 10)
panel.SetSizer(main_sizer)
self.click_count = 0
# メニューバー作成
self.create_menu_bar()
def create_menu_bar(self):
menu_bar = wx.MenuBar()
# ファイルメニュー
file_menu = wx.Menu()
exit_item = file_menu.Append(wx.ID_EXIT, "終了\tCtrl+Q", "アプリケーションを終了します")
self.Bind(wx.EVT_MENU, self.on_exit, exit_item)
# ヘルプメニュー
help_menu = wx.Menu()
about_item = help_menu.Append(wx.ID_ABOUT, "バージョン情報", "このアプリケーションについて")
self.Bind(wx.EVT_MENU, self.on_about, about_item)
menu_bar.Append(file_menu, "ファイル")
menu_bar.Append(help_menu, "ヘルプ")
self.SetMenuBar(menu_bar)
def on_button_click(self, event):
self.click_count += 1
self.counter_label.SetLabel(f"クリック回数: {self.click_count}")
if self.click_count == 1:
self.label.SetLabel("ボタンがクリックされました!")
else:
self.label.SetLabel(f"{self.click_count}回クリックされました!")
def on_exit(self, event):
self.Close(True)
def on_about(self, event):
info = wx.adv.AboutDialogInfo()
info.SetName("Hello World")
info.SetVersion("1.0")
info.SetDescription("wxPythonのシンプルなHello Worldアプリケーション")
info.SetCopyright("(C) 2025")
wx.adv.AboutBox(info)
class HelloWorldApp(wx.App):
def OnInit(self):
frame = HelloWorldFrame()
frame.Show()
return True
if __name__ == '__main__':
app = HelloWorldApp()
app.MainLoop()
高度なウィジェットの使用例
# advanced_widgets.py
import wx
import wx.grid
import wx.lib.mixins.listctrl as listmix
class AdvancedWidgetsFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='高度なウィジェット')
self.SetSize(800, 600)
self.Center()
# ノートブック(タブコントロール)
self.notebook = wx.Notebook(self)
# 各タブページを作成
self.create_grid_page()
self.create_tree_page()
self.create_list_page()
self.create_text_page()
# メインシーザー
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(self.notebook, 1, wx.EXPAND | wx.ALL, 5)
self.SetSizer(main_sizer)
def create_grid_page(self):
# グリッドページ
grid_panel = wx.Panel(self.notebook)
self.notebook.AddPage(grid_panel, "グリッド")
# グリッド作成
grid = wx.grid.Grid(grid_panel)
grid.CreateGrid(10, 5)
# カラムヘッダー設定
grid.SetColLabelValue(0, "名前")
grid.SetColLabelValue(1, "年齢")
grid.SetColLabelValue(2, "職業")
grid.SetColLabelValue(3, "給与")
grid.SetColLabelValue(4, "部署")
# サンプルデータ
sample_data = [
["田中太郎", "30", "エンジニア", "5000000", "開発部"],
["佐藤花子", "25", "デザイナー", "4500000", "デザイン部"],
["鈴木次郎", "35", "マネージャー", "7000000", "管理部"],
["高橋美香", "28", "アナリスト", "5500000", "分析部"],
["山田健太", "32", "エンジニア", "5200000", "開発部"]
]
for row, data in enumerate(sample_data):
for col, value in enumerate(data):
grid.SetCellValue(row, col, value)
# カラム幅の自動調整
grid.AutoSizeColumns()
# セルの編集可能性設定
for row in range(5):
grid.SetReadOnly(row, 1, True) # 年齢カラムを読み取り専用
# イベントバインド
grid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.on_grid_cell_changed)
# レイアウト
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(grid, 1, wx.EXPAND | wx.ALL, 5)
grid_panel.SetSizer(sizer)
self.grid = grid
def create_tree_page(self):
# ツリーページ
tree_panel = wx.Panel(self.notebook)
self.notebook.AddPage(tree_panel, "ツリー")
# ツリーコントロール
tree = wx.TreeCtrl(tree_panel, style=wx.TR_DEFAULT_STYLE | wx.TR_EDIT_LABELS)
# ルートノード
root = tree.AddRoot("プロジェクト")
# サブノード追加
frontend = tree.AppendItem(root, "フロントエンド")
tree.AppendItem(frontend, "HTML/CSS")
tree.AppendItem(frontend, "JavaScript")
tree.AppendItem(frontend, "React")
tree.AppendItem(frontend, "Vue.js")
backend = tree.AppendItem(root, "バックエンド")
tree.AppendItem(backend, "Python")
tree.AppendItem(backend, "Django")
tree.AppendItem(backend, "FastAPI")
tree.AppendItem(backend, "データベース")
devops = tree.AppendItem(root, "DevOps")
tree.AppendItem(devops, "Docker")
tree.AppendItem(devops, "Kubernetes")
tree.AppendItem(devops, "CI/CD")
# ツリーを展開
tree.ExpandAll()
# イベントバインド
tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_tree_selection)
tree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.on_tree_right_click)
# レイアウト
sizer = wx.BoxSizer(wx.VERTICAL)
info_label = wx.StaticText(tree_panel, label="選択されたアイテム: なし")
sizer.Add(info_label, 0, wx.ALL, 5)
sizer.Add(tree, 1, wx.EXPAND | wx.ALL, 5)
tree_panel.SetSizer(sizer)
self.tree = tree
self.tree_info_label = info_label
def create_list_page(self):
# リストページ
list_panel = wx.Panel(self.notebook)
self.notebook.AddPage(list_panel, "リスト")
# リストコントロール(レポートスタイル)
list_ctrl = wx.ListCtrl(list_panel, style=wx.LC_REPORT | wx.LC_SINGLE_SEL)
# カラム追加
list_ctrl.AppendColumn("ID", width=50)
list_ctrl.AppendColumn("名前", width=100)
list_ctrl.AppendColumn("メール", width=200)
list_ctrl.AppendColumn("状態", width=80)
# サンプルデータ追加
users = [
(1, "田中太郎", "[email protected]", "アクティブ"),
(2, "佐藤花子", "[email protected]", "非アクティブ"),
(3, "鈴木次郎", "[email protected]", "アクティブ"),
(4, "高橋美香", "[email protected]", "保留中"),
(5, "山田健太", "[email protected]", "アクティブ"),
]
for user in users:
index = list_ctrl.InsertItem(list_ctrl.GetItemCount(), str(user[0]))
list_ctrl.SetItem(index, 1, user[1])
list_ctrl.SetItem(index, 2, user[2])
list_ctrl.SetItem(index, 3, user[3])
# 状態に応じて色分け
if user[3] == "アクティブ":
list_ctrl.SetItemTextColour(index, wx.Colour(0, 128, 0))
elif user[3] == "非アクティブ":
list_ctrl.SetItemTextColour(index, wx.Colour(128, 128, 128))
else:
list_ctrl.SetItemTextColour(index, wx.Colour(255, 165, 0))
# イベントバインド
list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_list_selection)
list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_list_activation)
# ボタン
btn_panel = wx.Panel(list_panel)
add_btn = wx.Button(btn_panel, label="追加")
edit_btn = wx.Button(btn_panel, label="編集")
delete_btn = wx.Button(btn_panel, label="削除")
add_btn.Bind(wx.EVT_BUTTON, self.on_add_user)
edit_btn.Bind(wx.EVT_BUTTON, self.on_edit_user)
delete_btn.Bind(wx.EVT_BUTTON, self.on_delete_user)
# ボタンレイアウト
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
btn_sizer.Add(add_btn, 0, wx.ALL, 5)
btn_sizer.Add(edit_btn, 0, wx.ALL, 5)
btn_sizer.Add(delete_btn, 0, wx.ALL, 5)
btn_panel.SetSizer(btn_sizer)
# メインレイアウト
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(list_ctrl, 1, wx.EXPAND | wx.ALL, 5)
sizer.Add(btn_panel, 0, wx.EXPAND | wx.ALL, 5)
list_panel.SetSizer(sizer)
self.list_ctrl = list_ctrl
def create_text_page(self):
# リッチテキストページ
text_panel = wx.Panel(self.notebook)
self.notebook.AddPage(text_panel, "リッチテキスト")
# リッチテキストコントロール
rich_text = wx.richtext.RichTextCtrl(text_panel,
style=wx.VSCROLL | wx.HSCROLL | wx.NO_BORDER)
# スタイル設定
rich_text.BeginFontSize(14)
rich_text.BeginBold()
rich_text.WriteText("wxPython リッチテキストコントロール\n\n")
rich_text.EndBold()
rich_text.EndFontSize()
rich_text.WriteText("このコントロールでは以下の機能が利用できます:\n\n")
rich_text.BeginLeftIndent(20)
rich_text.BeginSymbolBullet('•', 20, 20)
rich_text.WriteText("文字の装飾(太字、斜体、下線)\n")
rich_text.EndSymbolBullet()
rich_text.BeginSymbolBullet('•', 20, 20)
rich_text.WriteText("色の変更\n")
rich_text.EndSymbolBullet()
rich_text.BeginSymbolBullet('•', 20, 20)
rich_text.WriteText("フォントサイズの変更\n")
rich_text.EndSymbolBullet()
rich_text.BeginSymbolBullet('•', 20, 20)
rich_text.WriteText("画像の挿入\n")
rich_text.EndSymbolBullet()
rich_text.BeginSymbolBullet('•', 20, 20)
rich_text.WriteText("表の作成\n")
rich_text.EndSymbolBullet()
rich_text.EndLeftIndent()
rich_text.WriteText("\n\n")
rich_text.BeginTextColour(wx.Colour(255, 0, 0))
rich_text.WriteText("赤い文字")
rich_text.EndTextColour()
rich_text.WriteText("と")
rich_text.BeginTextColour(wx.Colour(0, 0, 255))
rich_text.WriteText("青い文字")
rich_text.EndTextColour()
rich_text.WriteText("を組み合わせることも可能です。")
# ツールバー
toolbar = wx.ToolBar(text_panel)
toolbar.AddTool(wx.ID_BOLD, "太字", wx.ArtProvider.GetBitmap(wx.ART_BOLD))
toolbar.AddTool(wx.ID_ITALIC, "斜体", wx.ArtProvider.GetBitmap(wx.ART_ITALIC))
toolbar.AddTool(wx.ID_UNDERLINE, "下線", wx.ArtProvider.GetBitmap(wx.ART_UNDERLINE))
toolbar.AddSeparator()
toolbar.AddTool(wx.ID_SAVE, "保存", wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE))
toolbar.AddTool(wx.ID_OPEN, "開く", wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN))
toolbar.Realize()
# イベントバインド
self.Bind(wx.EVT_TOOL, self.on_bold, id=wx.ID_BOLD)
self.Bind(wx.EVT_TOOL, self.on_italic, id=wx.ID_ITALIC)
self.Bind(wx.EVT_TOOL, self.on_underline, id=wx.ID_UNDERLINE)
# レイアウト
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(toolbar, 0, wx.EXPAND)
sizer.Add(rich_text, 1, wx.EXPAND | wx.ALL, 5)
text_panel.SetSizer(sizer)
self.rich_text = rich_text
def on_grid_cell_changed(self, event):
row = event.GetRow()
col = event.GetCol()
value = self.grid.GetCellValue(row, col)
wx.MessageBox(f"セル ({row}, {col}) が '{value}' に変更されました", "セル変更", wx.OK | wx.ICON_INFORMATION)
def on_tree_selection(self, event):
item = event.GetItem()
text = self.tree.GetItemText(item)
self.tree_info_label.SetLabel(f"選択されたアイテム: {text}")
def on_tree_right_click(self, event):
item = event.GetItem()
text = self.tree.GetItemText(item)
menu = wx.Menu()
add_item = menu.Append(wx.ID_ANY, "子アイテムを追加")
delete_item = menu.Append(wx.ID_ANY, "削除")
def on_add_child(evt):
dlg = wx.TextEntryDialog(self, "新しいアイテム名を入力してください:", "アイテム追加")
if dlg.ShowModal() == wx.ID_OK:
new_text = dlg.GetValue()
self.tree.AppendItem(item, new_text)
self.tree.Expand(item)
dlg.Destroy()
def on_delete_item(evt):
if item != self.tree.GetRootItem():
self.tree.Delete(item)
self.Bind(wx.EVT_MENU, on_add_child, add_item)
self.Bind(wx.EVT_MENU, on_delete_item, delete_item)
self.PopupMenu(menu)
menu.Destroy()
def on_list_selection(self, event):
index = event.GetIndex()
name = self.list_ctrl.GetItemText(index, 1)
print(f"選択されたユーザー: {name}")
def on_list_activation(self, event):
index = event.GetIndex()
name = self.list_ctrl.GetItemText(index, 1)
wx.MessageBox(f"ユーザー '{name}' がダブルクリックされました", "ユーザー選択", wx.OK | wx.ICON_INFORMATION)
def on_add_user(self, event):
dlg = UserDialog(self, "新しいユーザー")
if dlg.ShowModal() == wx.ID_OK:
user_data = dlg.get_user_data()
index = self.list_ctrl.InsertItem(self.list_ctrl.GetItemCount(), str(user_data['id']))
self.list_ctrl.SetItem(index, 1, user_data['name'])
self.list_ctrl.SetItem(index, 2, user_data['email'])
self.list_ctrl.SetItem(index, 3, user_data['status'])
dlg.Destroy()
def on_edit_user(self, event):
selection = self.list_ctrl.GetFirstSelected()
if selection >= 0:
# 既存のデータを取得
user_data = {
'id': int(self.list_ctrl.GetItemText(selection, 0)),
'name': self.list_ctrl.GetItemText(selection, 1),
'email': self.list_ctrl.GetItemText(selection, 2),
'status': self.list_ctrl.GetItemText(selection, 3)
}
dlg = UserDialog(self, "ユーザー編集", user_data)
if dlg.ShowModal() == wx.ID_OK:
new_data = dlg.get_user_data()
self.list_ctrl.SetItem(selection, 1, new_data['name'])
self.list_ctrl.SetItem(selection, 2, new_data['email'])
self.list_ctrl.SetItem(selection, 3, new_data['status'])
dlg.Destroy()
def on_delete_user(self, event):
selection = self.list_ctrl.GetFirstSelected()
if selection >= 0:
name = self.list_ctrl.GetItemText(selection, 1)
if wx.MessageBox(f"ユーザー '{name}' を削除しますか?", "削除確認",
wx.YES_NO | wx.ICON_QUESTION) == wx.YES:
self.list_ctrl.DeleteItem(selection)
def on_bold(self, event):
self.rich_text.ApplyBoldToSelection()
def on_italic(self, event):
self.rich_text.ApplyItalicToSelection()
def on_underline(self, event):
self.rich_text.ApplyUnderlineToSelection()
class UserDialog(wx.Dialog):
def __init__(self, parent, title, user_data=None):
super().__init__(parent, title=title)
self.user_data = user_data or {'id': 0, 'name': '', 'email': '', 'status': 'アクティブ'}
# コントロール作成
main_sizer = wx.BoxSizer(wx.VERTICAL)
# フォーム
form_sizer = wx.FlexGridSizer(4, 2, 5, 5)
form_sizer.Add(wx.StaticText(self, label="ID:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.id_ctrl = wx.SpinCtrl(self, value=str(self.user_data['id']), min=1, max=9999)
form_sizer.Add(self.id_ctrl, 1, wx.EXPAND)
form_sizer.Add(wx.StaticText(self, label="名前:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.name_ctrl = wx.TextCtrl(self, value=self.user_data['name'])
form_sizer.Add(self.name_ctrl, 1, wx.EXPAND)
form_sizer.Add(wx.StaticText(self, label="メール:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.email_ctrl = wx.TextCtrl(self, value=self.user_data['email'])
form_sizer.Add(self.email_ctrl, 1, wx.EXPAND)
form_sizer.Add(wx.StaticText(self, label="状態:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.status_ctrl = wx.Choice(self, choices=["アクティブ", "非アクティブ", "保留中"])
self.status_ctrl.SetStringSelection(self.user_data['status'])
form_sizer.Add(self.status_ctrl, 1, wx.EXPAND)
form_sizer.AddGrowableCol(1)
# ボタン
btn_sizer = wx.StdDialogButtonSizer()
ok_btn = wx.Button(self, wx.ID_OK, "OK")
cancel_btn = wx.Button(self, wx.ID_CANCEL, "キャンセル")
btn_sizer.AddButton(ok_btn)
btn_sizer.AddButton(cancel_btn)
btn_sizer.Realize()
# レイアウト
main_sizer.Add(form_sizer, 1, wx.EXPAND | wx.ALL, 10)
main_sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALL, 10)
self.SetSizer(main_sizer)
self.Fit()
ok_btn.SetDefault()
self.name_ctrl.SetFocus()
def get_user_data(self):
return {
'id': self.id_ctrl.GetValue(),
'name': self.name_ctrl.GetValue(),
'email': self.email_ctrl.GetValue(),
'status': self.status_ctrl.GetStringSelection()
}
class AdvancedWidgetsApp(wx.App):
def OnInit(self):
frame = AdvancedWidgetsFrame()
frame.Show()
return True
if __name__ == '__main__':
app = AdvancedWidgetsApp()
app.MainLoop()
イベントシステムとレイアウト管理
# event_layout_demo.py
import wx
class EventLayoutFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='イベントシステムとレイアウト')
self.SetSize(600, 400)
self.Center()
# メインパネル
panel = wx.Panel(self)
# シーザーレイアウトのデモ
self.create_sizer_demo(panel)
# ステータスバー
self.CreateStatusBar()
self.SetStatusText("準備完了")
# タイマー
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_timer)
# ウィンドウイベント
self.Bind(wx.EVT_CLOSE, self.on_close)
self.Bind(wx.EVT_SIZE, self.on_size)
def create_sizer_demo(self, parent):
# メインの垂直シーザー
main_sizer = wx.BoxSizer(wx.VERTICAL)
# タイトル
title = wx.StaticText(parent, label="シーザーレイアウトとイベントのデモ")
title_font = title.GetFont()
title_font.PointSize += 4
title_font = title_font.Bold()
title.SetFont(title_font)
main_sizer.Add(title, 0, wx.ALL | wx.CENTER, 10)
# 水平シーザー1: ボタンとテキスト
button_sizer = wx.BoxSizer(wx.HORIZONTAL)
# ボタングループ
self.start_btn = wx.Button(parent, label="開始")
self.stop_btn = wx.Button(parent, label="停止")
self.reset_btn = wx.Button(parent, label="リセット")
self.start_btn.Bind(wx.EVT_BUTTON, self.on_start)
self.stop_btn.Bind(wx.EVT_BUTTON, self.on_stop)
self.reset_btn.Bind(wx.EVT_BUTTON, self.on_reset)
button_sizer.Add(self.start_btn, 0, wx.ALL, 5)
button_sizer.Add(self.stop_btn, 0, wx.ALL, 5)
button_sizer.Add(self.reset_btn, 0, wx.ALL, 5)
# タイマー表示
self.timer_label = wx.StaticText(parent, label="タイマー: 0秒")
timer_font = self.timer_label.GetFont()
timer_font.PointSize += 2
self.timer_label.SetFont(timer_font)
button_sizer.Add(self.timer_label, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 10)
main_sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 5)
# 水平シーザー2: スライダーとテキストコントロール
slider_sizer = wx.BoxSizer(wx.HORIZONTAL)
# スライダー
slider_sizer.Add(wx.StaticText(parent, label="値:"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
self.slider = wx.Slider(parent, value=50, minValue=0, maxValue=100,
style=wx.SL_HORIZONTAL | wx.SL_LABELS)
self.slider.Bind(wx.EVT_SLIDER, self.on_slider)
slider_sizer.Add(self.slider, 2, wx.ALL | wx.EXPAND, 5)
# テキストコントロール
self.text_ctrl = wx.TextCtrl(parent, value="50")
self.text_ctrl.Bind(wx.EVT_TEXT, self.on_text_change)
slider_sizer.Add(self.text_ctrl, 0, wx.ALL, 5)
main_sizer.Add(slider_sizer, 0, wx.EXPAND | wx.ALL, 5)
# グリッドシーザー: チェックボックスとラジオボタン
grid_sizer = wx.GridBagSizer(5, 5)
# チェックボックスグループ
grid_sizer.Add(wx.StaticText(parent, label="オプション:"),
pos=(0, 0), flag=wx.ALIGN_CENTER_VERTICAL)
self.check1 = wx.CheckBox(parent, label="オプション1")
self.check2 = wx.CheckBox(parent, label="オプション2")
self.check3 = wx.CheckBox(parent, label="オプション3")
self.check1.Bind(wx.EVT_CHECKBOX, self.on_checkbox)
self.check2.Bind(wx.EVT_CHECKBOX, self.on_checkbox)
self.check3.Bind(wx.EVT_CHECKBOX, self.on_checkbox)
grid_sizer.Add(self.check1, pos=(0, 1))
grid_sizer.Add(self.check2, pos=(0, 2))
grid_sizer.Add(self.check3, pos=(0, 3))
# ラジオボタングループ
grid_sizer.Add(wx.StaticText(parent, label="選択:"),
pos=(1, 0), flag=wx.ALIGN_CENTER_VERTICAL)
self.radio1 = wx.RadioButton(parent, label="選択肢A", style=wx.RB_GROUP)
self.radio2 = wx.RadioButton(parent, label="選択肢B")
self.radio3 = wx.RadioButton(parent, label="選択肢C")
self.radio1.Bind(wx.EVT_RADIOBUTTON, self.on_radio)
self.radio2.Bind(wx.EVT_RADIOBUTTON, self.on_radio)
self.radio3.Bind(wx.EVT_RADIOBUTTON, self.on_radio)
grid_sizer.Add(self.radio1, pos=(1, 1))
grid_sizer.Add(self.radio2, pos=(1, 2))
grid_sizer.Add(self.radio3, pos=(1, 3))
main_sizer.Add(grid_sizer, 0, wx.ALL | wx.EXPAND, 10)
# ログ表示エリア
log_box = wx.StaticBox(parent, label="イベントログ")
log_sizer = wx.StaticBoxSizer(log_box, wx.VERTICAL)
self.log_ctrl = wx.TextCtrl(parent, style=wx.TE_MULTILINE | wx.TE_READONLY)
log_sizer.Add(self.log_ctrl, 1, wx.EXPAND | wx.ALL, 5)
# ログクリアボタン
clear_btn = wx.Button(parent, label="ログクリア")
clear_btn.Bind(wx.EVT_BUTTON, self.on_clear_log)
log_sizer.Add(clear_btn, 0, wx.ALL, 5)
main_sizer.Add(log_sizer, 1, wx.EXPAND | wx.ALL, 5)
parent.SetSizer(main_sizer)
# 初期値
self.timer_count = 0
def log_event(self, message):
"""イベントログに追加"""
import datetime
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
self.log_ctrl.AppendText(f"[{timestamp}] {message}\n")
def on_start(self, event):
self.timer.Start(1000) # 1秒間隔
self.log_event("タイマー開始")
self.SetStatusText("タイマー実行中")
def on_stop(self, event):
self.timer.Stop()
self.log_event("タイマー停止")
self.SetStatusText("タイマー停止")
def on_reset(self, event):
self.timer.Stop()
self.timer_count = 0
self.timer_label.SetLabel("タイマー: 0秒")
self.log_event("タイマーリセット")
self.SetStatusText("タイマーリセット")
def on_timer(self, event):
self.timer_count += 1
self.timer_label.SetLabel(f"タイマー: {self.timer_count}秒")
if self.timer_count % 10 == 0:
self.log_event(f"タイマー: {self.timer_count}秒経過")
def on_slider(self, event):
value = self.slider.GetValue()
self.text_ctrl.SetValue(str(value))
self.log_event(f"スライダー値変更: {value}")
def on_text_change(self, event):
try:
value = int(self.text_ctrl.GetValue())
if 0 <= value <= 100:
self.slider.SetValue(value)
self.log_event(f"テキスト値変更: {value}")
except ValueError:
pass # 無効な入力は無視
def on_checkbox(self, event):
checkbox = event.GetEventObject()
label = checkbox.GetLabel()
checked = checkbox.GetValue()
status = "チェック" if checked else "チェック解除"
self.log_event(f"{label}: {status}")
def on_radio(self, event):
radio = event.GetEventObject()
label = radio.GetLabel()
self.log_event(f"ラジオボタン選択: {label}")
def on_clear_log(self, event):
self.log_ctrl.Clear()
self.log_event("ログクリア")
def on_size(self, event):
size = event.GetSize()
self.SetStatusText(f"ウィンドウサイズ: {size.width} x {size.height}")
event.Skip() # 他のハンドラーも呼び出し
def on_close(self, event):
if self.timer.IsRunning():
self.timer.Stop()
self.log_event("アプリケーション終了")
event.Skip()
class EventLayoutApp(wx.App):
def OnInit(self):
frame = EventLayoutFrame()
frame.Show()
return True
if __name__ == '__main__':
app = EventLayoutApp()
app.MainLoop()
ドラッグ&ドロップとファイル操作
# drag_drop_file.py
import wx
import os
class FileDropTarget(wx.FileDropTarget):
"""ファイルドロップターゲット"""
def __init__(self, window):
wx.FileDropTarget.__init__(self)
self.window = window
def OnDropFiles(self, x, y, filenames):
"""ファイルがドロップされた時の処理"""
for filename in filenames:
self.window.add_file(filename)
return True
class DragDropFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='ドラッグ&ドロップとファイル操作')
self.SetSize(700, 500)
self.Center()
# メインパネル
panel = wx.Panel(self)
# ファイルリスト
self.create_file_list(panel)
# ファイルドロップターゲット設定
drop_target = FileDropTarget(self)
self.file_list.SetDropTarget(drop_target)
# メニューとツールバー
self.create_menu_toolbar()
# ステータスバー
self.CreateStatusBar()
self.SetStatusText("ファイルをドラッグ&ドロップしてください")
def create_file_list(self, parent):
# スプリッターウィンドウ
splitter = wx.SplitterWindow(parent, style=wx.SP_3D | wx.SP_LIVE_UPDATE)
# 左パネル: ファイルリスト
left_panel = wx.Panel(splitter)
left_sizer = wx.BoxSizer(wx.VERTICAL)
# ファイルリストボックス
left_sizer.Add(wx.StaticText(left_panel, label="ファイル一覧:"), 0, wx.ALL, 5)
self.file_list = wx.ListBox(left_panel, style=wx.LB_SINGLE)
self.file_list.Bind(wx.EVT_LISTBOX, self.on_file_select)
self.file_list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_file_open)
left_sizer.Add(self.file_list, 1, wx.EXPAND | wx.ALL, 5)
# ボタン
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
add_btn = wx.Button(left_panel, label="ファイル追加")
remove_btn = wx.Button(left_panel, label="削除")
clear_btn = wx.Button(left_panel, label="クリア")
add_btn.Bind(wx.EVT_BUTTON, self.on_add_file)
remove_btn.Bind(wx.EVT_BUTTON, self.on_remove_file)
clear_btn.Bind(wx.EVT_BUTTON, self.on_clear_files)
btn_sizer.Add(add_btn, 0, wx.ALL, 2)
btn_sizer.Add(remove_btn, 0, wx.ALL, 2)
btn_sizer.Add(clear_btn, 0, wx.ALL, 2)
left_sizer.Add(btn_sizer, 0, wx.ALL | wx.CENTER, 5)
left_panel.SetSizer(left_sizer)
# 右パネル: ファイル情報とプレビュー
right_panel = wx.Panel(splitter)
right_sizer = wx.BoxSizer(wx.VERTICAL)
# ファイル情報
info_box = wx.StaticBox(right_panel, label="ファイル情報")
info_sizer = wx.StaticBoxSizer(info_box, wx.VERTICAL)
self.file_name_label = wx.StaticText(right_panel, label="ファイル名: ")
self.file_size_label = wx.StaticText(right_panel, label="サイズ: ")
self.file_type_label = wx.StaticText(right_panel, label="種類: ")
self.file_path_label = wx.StaticText(right_panel, label="パス: ")
info_sizer.Add(self.file_name_label, 0, wx.ALL, 5)
info_sizer.Add(self.file_size_label, 0, wx.ALL, 5)
info_sizer.Add(self.file_type_label, 0, wx.ALL, 5)
info_sizer.Add(self.file_path_label, 0, wx.ALL, 5)
right_sizer.Add(info_sizer, 0, wx.EXPAND | wx.ALL, 5)
# プレビューエリア
preview_box = wx.StaticBox(right_panel, label="プレビュー")
preview_sizer = wx.StaticBoxSizer(preview_box, wx.VERTICAL)
self.preview_text = wx.TextCtrl(right_panel, style=wx.TE_MULTILINE | wx.TE_READONLY)
preview_sizer.Add(self.preview_text, 1, wx.EXPAND | wx.ALL, 5)
right_sizer.Add(preview_sizer, 1, wx.EXPAND | wx.ALL, 5)
right_panel.SetSizer(right_sizer)
# スプリッター設定
splitter.SplitVertically(left_panel, right_panel, 250)
splitter.SetMinimumPaneSize(200)
# メインレイアウト
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(splitter, 1, wx.EXPAND)
parent.SetSizer(main_sizer)
# ファイルデータ保存用
self.file_data = {}
def create_menu_toolbar(self):
# メニューバー
menubar = wx.MenuBar()
# ファイルメニュー
file_menu = wx.Menu()
file_menu.Append(wx.ID_OPEN, "ファイルを開く\tCtrl+O")
file_menu.Append(wx.ID_SAVEAS, "エクスポート\tCtrl+E")
file_menu.AppendSeparator()
file_menu.Append(wx.ID_EXIT, "終了\tCtrl+Q")
# 編集メニュー
edit_menu = wx.Menu()
edit_menu.Append(wx.ID_DELETE, "削除\tDel")
edit_menu.Append(wx.ID_CLEAR, "すべてクリア\tCtrl+A")
menubar.Append(file_menu, "ファイル")
menubar.Append(edit_menu, "編集")
self.SetMenuBar(menubar)
# イベントバインド
self.Bind(wx.EVT_MENU, self.on_add_file, id=wx.ID_OPEN)
self.Bind(wx.EVT_MENU, self.on_export_list, id=wx.ID_SAVEAS)
self.Bind(wx.EVT_MENU, self.on_remove_file, id=wx.ID_DELETE)
self.Bind(wx.EVT_MENU, self.on_clear_files, id=wx.ID_CLEAR)
self.Bind(wx.EVT_MENU, self.on_exit, id=wx.ID_EXIT)
# ツールバー
toolbar = self.CreateToolBar()
toolbar.AddTool(wx.ID_OPEN, "開く", wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN))
toolbar.AddTool(wx.ID_SAVEAS, "エクスポート", wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE_AS))
toolbar.AddSeparator()
toolbar.AddTool(wx.ID_DELETE, "削除", wx.ArtProvider.GetBitmap(wx.ART_DELETE))
toolbar.AddTool(wx.ID_CLEAR, "クリア", wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE))
toolbar.Realize()
def add_file(self, filepath):
"""ファイルをリストに追加"""
if os.path.exists(filepath) and filepath not in self.file_data:
filename = os.path.basename(filepath)
self.file_list.Append(filename)
# ファイル情報を保存
stat = os.stat(filepath)
self.file_data[filename] = {
'path': filepath,
'size': stat.st_size,
'type': self.get_file_type(filepath)
}
self.SetStatusText(f"ファイルを追加しました: {filename}")
def get_file_type(self, filepath):
"""ファイルタイプを取得"""
ext = os.path.splitext(filepath)[1].lower()
types = {
'.txt': 'テキストファイル',
'.py': 'Pythonファイル',
'.jpg': '画像ファイル',
'.jpeg': '画像ファイル',
'.png': '画像ファイル',
'.gif': '画像ファイル',
'.pdf': 'PDFファイル',
'.doc': 'Wordファイル',
'.docx': 'Wordファイル',
'.xls': 'Excelファイル',
'.xlsx': 'Excelファイル',
}
return types.get(ext, '不明なファイル')
def on_file_select(self, event):
"""ファイル選択時の処理"""
selection = self.file_list.GetSelection()
if selection != wx.NOT_FOUND:
filename = self.file_list.GetString(selection)
file_info = self.file_data[filename]
# ファイル情報表示
self.file_name_label.SetLabel(f"ファイル名: {filename}")
self.file_size_label.SetLabel(f"サイズ: {file_info['size']} bytes")
self.file_type_label.SetLabel(f"種類: {file_info['type']}")
self.file_path_label.SetLabel(f"パス: {file_info['path']}")
# プレビュー表示
self.show_preview(file_info['path'])
def show_preview(self, filepath):
"""ファイルプレビュー表示"""
try:
# テキストファイルの場合
if filepath.endswith(('.txt', '.py', '.md', '.css', '.js', '.html', '.xml')):
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read(1000) # 最初の1000文字のみ
if len(content) == 1000:
content += "\n... (続きがあります)"
self.preview_text.SetValue(content)
else:
self.preview_text.SetValue("このファイル形式はプレビューできません。")
except Exception as e:
self.preview_text.SetValue(f"プレビューエラー: {str(e)}")
def on_file_open(self, event):
"""ファイルダブルクリック時の処理"""
selection = self.file_list.GetSelection()
if selection != wx.NOT_FOUND:
filename = self.file_list.GetString(selection)
filepath = self.file_data[filename]['path']
# デフォルトアプリケーションで開く
try:
if wx.Platform == '__WXMSW__':
os.startfile(filepath)
elif wx.Platform == '__WXMAC__':
os.system(f'open "{filepath}"')
else:
os.system(f'xdg-open "{filepath}"')
self.SetStatusText(f"ファイルを開きました: {filename}")
except Exception as e:
wx.MessageBox(f"ファイルを開けませんでした: {str(e)}", "エラー", wx.OK | wx.ICON_ERROR)
def on_add_file(self, event):
"""ファイル追加ダイアログ"""
with wx.FileDialog(self, "ファイルを選択してください",
style=wx.FD_OPEN | wx.FD_MULTIPLE) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return
pathnames = fileDialog.GetPaths()
for path in pathnames:
self.add_file(path)
def on_remove_file(self, event):
"""選択ファイル削除"""
selection = self.file_list.GetSelection()
if selection != wx.NOT_FOUND:
filename = self.file_list.GetString(selection)
self.file_list.Delete(selection)
del self.file_data[filename]
# 表示クリア
self.file_name_label.SetLabel("ファイル名: ")
self.file_size_label.SetLabel("サイズ: ")
self.file_type_label.SetLabel("種類: ")
self.file_path_label.SetLabel("パス: ")
self.preview_text.SetValue("")
self.SetStatusText(f"ファイルを削除しました: {filename}")
def on_clear_files(self, event):
"""すべてのファイルをクリア"""
self.file_list.Clear()
self.file_data.clear()
# 表示クリア
self.file_name_label.SetLabel("ファイル名: ")
self.file_size_label.SetLabel("サイズ: ")
self.file_type_label.SetLabel("種類: ")
self.file_path_label.SetLabel("パス: ")
self.preview_text.SetValue("")
self.SetStatusText("すべてのファイルをクリアしました")
def on_export_list(self, event):
"""ファイルリストのエクスポート"""
with wx.FileDialog(self, "ファイルリストを保存",
defaultFile="file_list.txt",
wildcard="テキストファイル (*.txt)|*.txt",
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return
pathname = fileDialog.GetPath()
try:
with open(pathname, 'w', encoding='utf-8') as f:
f.write("ファイルリスト\n")
f.write("=" * 50 + "\n\n")
for filename, info in self.file_data.items():
f.write(f"ファイル名: {filename}\n")
f.write(f"パス: {info['path']}\n")
f.write(f"サイズ: {info['size']} bytes\n")
f.write(f"種類: {info['type']}\n")
f.write("-" * 30 + "\n")
self.SetStatusText(f"ファイルリストを保存しました: {pathname}")
except Exception as e:
wx.MessageBox(f"保存できませんでした: {str(e)}", "エラー", wx.OK | wx.ICON_ERROR)
def on_exit(self, event):
self.Close()
class DragDropApp(wx.App):
def OnInit(self):
frame = DragDropFrame()
frame.Show()
return True
if __name__ == '__main__':
app = DragDropApp()
app.MainLoop()
設定とビルド
# wxPythonインストール
pip install wxpython
# インストール確認
python -c "import wx; print(f'wxPython {wx.version()}')"
# 追加パッケージ(オプション)
pip install wxpython[all] # すべてのオプション機能を含む
# アプリケーション実行
python hello_world.py
# スタンドアロン実行ファイル作成
pip install pyinstaller
pyinstaller --onefile --windowed hello_world.py
# MacOSでのアプリバンドル作成
pyinstaller --onefile --windowed --icon=icon.ico hello_world.py
# Linux環境での依存関係
# Ubuntu/Debian
sudo apt-get install python3-wxgtk4.0-dev
# CentOS/RHEL
sudo yum install wxGTK3-devel
# requirements.txt作成
pip freeze > requirements.txt
プロジェクト構造例
my_wxpython_app/
├── main.py # メインアプリケーション
├── frames/
│ ├── __init__.py
│ ├── main_frame.py # メインフレーム
│ └── dialog_frames.py # ダイアログクラス
├── panels/
│ ├── __init__.py
│ ├── custom_panels.py # カスタムパネル
│ └── grid_panels.py # グリッド関連パネル
├── utils/
│ ├── __init__.py
│ ├── file_utils.py # ファイル操作ユーティリティ
│ └── event_utils.py # イベント処理ユーティリティ
├── resources/
│ ├── icons/ # アイコンファイル
│ ├── images/ # 画像ファイル
│ └── data/ # データファイル
├── tests/
│ ├── __init__.py
│ └── test_main.py # テストファイル
├── requirements.txt
└── README.md