wxPython

wxWidgetsのPythonバインディング。各プラットフォームのネイティブUIコントロールを使用し、OS標準の外観を実現。軽量で高性能な業務アプリケーション開発に適している。

デスクトップクロスプラットフォームPythonGUIネイティブ

GitHub概要

wxWidgets/Phoenix

wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.

スター2,506
ウォッチ102
フォーク547
作成日:2012年7月17日
言語:Python
ライセンス:-

トピックス

awesomecross-platformguigui-frameworkgui-toolkitlinuxmacosxpythonwindowswxpythonwxwidgets

スター履歴

wxWidgets/Phoenix Star History
データ取得日時: 2025/8/13 01:43

フレームワーク

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エフェクトの実装が限定的
  • 配布の複雑さ: 依存関係とプラットフォーム固有の配布

主要リンク

書き方の例

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