wxPython

Python binding for wxWidgets. Uses native UI controls for each platform to achieve OS-standard appearance. Suitable for lightweight, high-performance business application development.

DesktopCross-platformPythonGUINative

GitHub Overview

wxWidgets/Phoenix

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

Stars2,506
Watchers102
Forks547
Created:July 17, 2012
Language:Python
License:-

Topics

awesomecross-platformguigui-frameworkgui-toolkitlinuxmacosxpythonwindowswxpythonwxwidgets

Star History

wxWidgets/Phoenix Star History
Data as of: 8/13/2025, 01:43 AM

Framework

wxPython

Overview

wxPython is a framework for developing native cross-platform GUI applications in Python. Based on wxWidgets, it provides native look and feel for each platform. Features rich widgets, advanced controls, and powerful event system, suitable for professional desktop application development.

Details

wxPython is an important choice for developers who prioritize "native appearance" among Python GUI frameworks in 2025. Based on the wxWidgets library with nearly 30 years of history, it achieves optimized appearance and usability for Windows, macOS, and Linux respectively.

The key feature is using native widgets for each platform to maintain the standard look and feel of the OS. This allows applications to provide interfaces users are familiar with, without losing platform-specific usability during cross-platform deployment.

Comprehensive features for enterprise application development are provided, including rich advanced widgets (grids, rich text, tree controls, OpenGL canvas, etc.), powerful event-driven architecture, visual UI design with wxGlade, printing functionality, drag & drop, and internationalization support.

wxPython is adopted in many prominent applications such as GIMP, Audacity, BitTorrent, and Chandler, with proven track record across various fields in both commercial and open-source projects.

Pros and Cons

Pros

  • Native Appearance: Automatically adopts standard UI for each platform
  • Rich Widgets: Advanced controls and custom widgets
  • Mature Library: Nearly 30 years of development track record and stability
  • Cross-platform: Complete Windows, macOS, Linux support
  • Powerful Event System: Flexible event-driven development
  • Printing & Drawing: Advanced drawing and document output capabilities
  • Drag & Drop: Intuitive file operations and UI
  • Internationalization Support: Multi-language application development
  • wxGlade Integration: Visual UI design tools
  • Commercial Use: wxWindows license allows commercial use without restrictions

Cons

  • Learning Curve: Complex concepts and APIs due to rich functionality
  • Package Size: Large binary size due to rich features
  • Memory Usage: Memory consumption from native widgets
  • Modern Design: Not suitable for latest flat designs
  • Animation: Limited dynamic UI effect implementation
  • Distribution Complexity: Dependencies and platform-specific distribution

Key Links

Code Examples

Hello World Application

# hello_world.py
import wx

class HelloWorldFrame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='wxPython Hello World')
        
        # Set frame size and position
        self.SetSize(300, 200)
        self.Center()
        
        # Main panel
        panel = wx.Panel(self)
        
        # Layout sizer
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        
        # Label
        self.label = wx.StaticText(panel, label="Hello, wxPython!")
        main_sizer.Add(self.label, 0, wx.ALL | wx.CENTER, 20)
        
        # Button
        self.button = wx.Button(panel, label="Click me")
        self.button.Bind(wx.EVT_BUTTON, self.on_button_click)
        main_sizer.Add(self.button, 0, wx.ALL | wx.CENTER, 10)
        
        # Counter display
        self.counter_label = wx.StaticText(panel, label="Click count: 0")
        main_sizer.Add(self.counter_label, 0, wx.ALL | wx.CENTER, 10)
        
        panel.SetSizer(main_sizer)
        
        self.click_count = 0
        
        # Create menu bar
        self.create_menu_bar()
    
    def create_menu_bar(self):
        menu_bar = wx.MenuBar()
        
        # File menu
        file_menu = wx.Menu()
        exit_item = file_menu.Append(wx.ID_EXIT, "Exit\tCtrl+Q", "Exit the application")
        self.Bind(wx.EVT_MENU, self.on_exit, exit_item)
        
        # Help menu
        help_menu = wx.Menu()
        about_item = help_menu.Append(wx.ID_ABOUT, "About", "About this application")
        self.Bind(wx.EVT_MENU, self.on_about, about_item)
        
        menu_bar.Append(file_menu, "File")
        menu_bar.Append(help_menu, "Help")
        
        self.SetMenuBar(menu_bar)
    
    def on_button_click(self, event):
        self.click_count += 1
        self.counter_label.SetLabel(f"Click count: {self.click_count}")
        if self.click_count == 1:
            self.label.SetLabel("Button clicked!")
        else:
            self.label.SetLabel(f"Clicked {self.click_count} times!")
    
    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("Simple Hello World application with wxPython")
        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 Widget Usage

# 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='Advanced Widgets')
        self.SetSize(800, 600)
        self.Center()
        
        # Notebook (tab control)
        self.notebook = wx.Notebook(self)
        
        # Create tab pages
        self.create_grid_page()
        self.create_tree_page()
        self.create_list_page()
        self.create_text_page()
        
        # Main sizer
        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 page
        grid_panel = wx.Panel(self.notebook)
        self.notebook.AddPage(grid_panel, "Grid")
        
        # Create grid
        grid = wx.grid.Grid(grid_panel)
        grid.CreateGrid(10, 5)
        
        # Set column headers
        grid.SetColLabelValue(0, "Name")
        grid.SetColLabelValue(1, "Age")
        grid.SetColLabelValue(2, "Occupation")
        grid.SetColLabelValue(3, "Salary")
        grid.SetColLabelValue(4, "Department")
        
        # Sample data
        sample_data = [
            ["John Doe", "30", "Engineer", "5000000", "Development"],
            ["Jane Smith", "25", "Designer", "4500000", "Design"],
            ["Bob Johnson", "35", "Manager", "7000000", "Management"],
            ["Alice Brown", "28", "Analyst", "5500000", "Analysis"],
            ["Charlie Wilson", "32", "Engineer", "5200000", "Development"]
        ]
        
        for row, data in enumerate(sample_data):
            for col, value in enumerate(data):
                grid.SetCellValue(row, col, value)
        
        # Auto-size columns
        grid.AutoSizeColumns()
        
        # Set cell editability
        for row in range(5):
            grid.SetReadOnly(row, 1, True)  # Age column read-only
        
        # Event binding
        grid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.on_grid_cell_changed)
        
        # Layout
        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 page
        tree_panel = wx.Panel(self.notebook)
        self.notebook.AddPage(tree_panel, "Tree")
        
        # Tree control
        tree = wx.TreeCtrl(tree_panel, style=wx.TR_DEFAULT_STYLE | wx.TR_EDIT_LABELS)
        
        # Root node
        root = tree.AddRoot("Project")
        
        # Add sub-nodes
        frontend = tree.AppendItem(root, "Frontend")
        tree.AppendItem(frontend, "HTML/CSS")
        tree.AppendItem(frontend, "JavaScript")
        tree.AppendItem(frontend, "React")
        tree.AppendItem(frontend, "Vue.js")
        
        backend = tree.AppendItem(root, "Backend")
        tree.AppendItem(backend, "Python")
        tree.AppendItem(backend, "Django")
        tree.AppendItem(backend, "FastAPI")
        tree.AppendItem(backend, "Database")
        
        devops = tree.AppendItem(root, "DevOps")
        tree.AppendItem(devops, "Docker")
        tree.AppendItem(devops, "Kubernetes")
        tree.AppendItem(devops, "CI/CD")
        
        # Expand tree
        tree.ExpandAll()
        
        # Event binding
        tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_tree_selection)
        tree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.on_tree_right_click)
        
        # Layout
        sizer = wx.BoxSizer(wx.VERTICAL)
        info_label = wx.StaticText(tree_panel, label="Selected item: None")
        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 page
        list_panel = wx.Panel(self.notebook)
        self.notebook.AddPage(list_panel, "List")
        
        # List control (report style)
        list_ctrl = wx.ListCtrl(list_panel, style=wx.LC_REPORT | wx.LC_SINGLE_SEL)
        
        # Add columns
        list_ctrl.AppendColumn("ID", width=50)
        list_ctrl.AppendColumn("Name", width=100)
        list_ctrl.AppendColumn("Email", width=200)
        list_ctrl.AppendColumn("Status", width=80)
        
        # Add sample data
        users = [
            (1, "John Doe", "[email protected]", "Active"),
            (2, "Jane Smith", "[email protected]", "Inactive"),
            (3, "Bob Johnson", "[email protected]", "Active"),
            (4, "Alice Brown", "[email protected]", "Pending"),
            (5, "Charlie Wilson", "[email protected]", "Active"),
        ]
        
        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])
            
            # Color coding by status
            if user[3] == "Active":
                list_ctrl.SetItemTextColour(index, wx.Colour(0, 128, 0))
            elif user[3] == "Inactive":
                list_ctrl.SetItemTextColour(index, wx.Colour(128, 128, 128))
            else:
                list_ctrl.SetItemTextColour(index, wx.Colour(255, 165, 0))
        
        # Event binding
        list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_list_selection)
        list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_list_activation)
        
        # Buttons
        btn_panel = wx.Panel(list_panel)
        add_btn = wx.Button(btn_panel, label="Add")
        edit_btn = wx.Button(btn_panel, label="Edit")
        delete_btn = wx.Button(btn_panel, label="Delete")
        
        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)
        
        # Button layout
        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)
        
        # Main layout
        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):
        # Rich text page
        text_panel = wx.Panel(self.notebook)
        self.notebook.AddPage(text_panel, "Rich Text")
        
        # Rich text control
        rich_text = wx.richtext.RichTextCtrl(text_panel, 
                                            style=wx.VSCROLL | wx.HSCROLL | wx.NO_BORDER)
        
        # Style settings
        rich_text.BeginFontSize(14)
        rich_text.BeginBold()
        rich_text.WriteText("wxPython Rich Text Control\n\n")
        rich_text.EndBold()
        rich_text.EndFontSize()
        
        rich_text.WriteText("This control supports the following features:\n\n")
        
        rich_text.BeginLeftIndent(20)
        rich_text.BeginSymbolBullet('•', 20, 20)
        rich_text.WriteText("Text formatting (bold, italic, underline)\n")
        rich_text.EndSymbolBullet()
        
        rich_text.BeginSymbolBullet('•', 20, 20)
        rich_text.WriteText("Color changes\n")
        rich_text.EndSymbolBullet()
        
        rich_text.BeginSymbolBullet('•', 20, 20)
        rich_text.WriteText("Font size changes\n")
        rich_text.EndSymbolBullet()
        
        rich_text.BeginSymbolBullet('•', 20, 20)
        rich_text.WriteText("Image insertion\n")
        rich_text.EndSymbolBullet()
        
        rich_text.BeginSymbolBullet('•', 20, 20)
        rich_text.WriteText("Table creation\n")
        rich_text.EndSymbolBullet()
        rich_text.EndLeftIndent()
        
        rich_text.WriteText("\n\n")
        rich_text.BeginTextColour(wx.Colour(255, 0, 0))
        rich_text.WriteText("Red text")
        rich_text.EndTextColour()
        
        rich_text.WriteText(" and ")
        
        rich_text.BeginTextColour(wx.Colour(0, 0, 255))
        rich_text.WriteText("blue text")
        rich_text.EndTextColour()
        
        rich_text.WriteText(" can be combined.")
        
        # Toolbar
        toolbar = wx.ToolBar(text_panel)
        toolbar.AddTool(wx.ID_BOLD, "Bold", wx.ArtProvider.GetBitmap(wx.ART_BOLD))
        toolbar.AddTool(wx.ID_ITALIC, "Italic", wx.ArtProvider.GetBitmap(wx.ART_ITALIC))
        toolbar.AddTool(wx.ID_UNDERLINE, "Underline", wx.ArtProvider.GetBitmap(wx.ART_UNDERLINE))
        toolbar.AddSeparator()
        toolbar.AddTool(wx.ID_SAVE, "Save", wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE))
        toolbar.AddTool(wx.ID_OPEN, "Open", wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN))
        toolbar.Realize()
        
        # Event binding
        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)
        
        # Layout
        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"Cell ({row}, {col}) changed to '{value}'", "Cell Changed", 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"Selected item: {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, "Add Child Item")
        delete_item = menu.Append(wx.ID_ANY, "Delete")
        
        def on_add_child(evt):
            dlg = wx.TextEntryDialog(self, "Enter new item name:", "Add Item")
            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"Selected user: {name}")
    
    def on_list_activation(self, event):
        index = event.GetIndex()
        name = self.list_ctrl.GetItemText(index, 1)
        wx.MessageBox(f"User '{name}' was double-clicked", "User Selection", wx.OK | wx.ICON_INFORMATION)
    
    def on_add_user(self, event):
        dlg = UserDialog(self, "New User")
        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:
            # Get existing data
            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, "Edit User", 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"Delete user '{name}'?", "Confirm Delete", 
                           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': 'Active'}
        
        # Create controls
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        
        # Form
        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="Name:"), 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="Email:"), 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="Status:"), 0, wx.ALIGN_CENTER_VERTICAL)
        self.status_ctrl = wx.Choice(self, choices=["Active", "Inactive", "Pending"])
        self.status_ctrl.SetStringSelection(self.user_data['status'])
        form_sizer.Add(self.status_ctrl, 1, wx.EXPAND)
        
        form_sizer.AddGrowableCol(1)
        
        # Buttons
        btn_sizer = wx.StdDialogButtonSizer()
        ok_btn = wx.Button(self, wx.ID_OK, "OK")
        cancel_btn = wx.Button(self, wx.ID_CANCEL, "Cancel")
        btn_sizer.AddButton(ok_btn)
        btn_sizer.AddButton(cancel_btn)
        btn_sizer.Realize()
        
        # Layout
        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 System and Layout Management

# event_layout_demo.py
import wx

class EventLayoutFrame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='Event System and Layout')
        self.SetSize(600, 400)
        self.Center()
        
        # Main panel
        panel = wx.Panel(self)
        
        # Sizer layout demo
        self.create_sizer_demo(panel)
        
        # Status bar
        self.CreateStatusBar()
        self.SetStatusText("Ready")
        
        # Timer
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.on_timer)
        
        # Window events
        self.Bind(wx.EVT_CLOSE, self.on_close)
        self.Bind(wx.EVT_SIZE, self.on_size)
        
    def create_sizer_demo(self, parent):
        # Main vertical sizer
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        
        # Title
        title = wx.StaticText(parent, label="Sizer Layout and Event Demo")
        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)
        
        # Horizontal sizer 1: Buttons and text
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        # Button group
        self.start_btn = wx.Button(parent, label="Start")
        self.stop_btn = wx.Button(parent, label="Stop")
        self.reset_btn = wx.Button(parent, label="Reset")
        
        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)
        
        # Timer display
        self.timer_label = wx.StaticText(parent, label="Timer: 0 seconds")
        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)
        
        # Horizontal sizer 2: Slider and text control
        slider_sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        # Slider
        slider_sizer.Add(wx.StaticText(parent, label="Value:"), 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)
        
        # Text control
        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: Checkboxes and radio buttons
        grid_sizer = wx.GridBagSizer(5, 5)
        
        # Checkbox group
        grid_sizer.Add(wx.StaticText(parent, label="Options:"), 
                      pos=(0, 0), flag=wx.ALIGN_CENTER_VERTICAL)
        
        self.check1 = wx.CheckBox(parent, label="Option 1")
        self.check2 = wx.CheckBox(parent, label="Option 2")
        self.check3 = wx.CheckBox(parent, label="Option 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))
        
        # Radio button group
        grid_sizer.Add(wx.StaticText(parent, label="Choice:"), 
                      pos=(1, 0), flag=wx.ALIGN_CENTER_VERTICAL)
        
        self.radio1 = wx.RadioButton(parent, label="Choice A", style=wx.RB_GROUP)
        self.radio2 = wx.RadioButton(parent, label="Choice B")
        self.radio3 = wx.RadioButton(parent, label="Choice 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 display area
        log_box = wx.StaticBox(parent, label="Event Log")
        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)
        
        # Log clear button
        clear_btn = wx.Button(parent, label="Clear Log")
        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)
        
        # Initial values
        self.timer_count = 0
    
    def log_event(self, message):
        """Add to event log"""
        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 second interval
        self.log_event("Timer started")
        self.SetStatusText("Timer running")
    
    def on_stop(self, event):
        self.timer.Stop()
        self.log_event("Timer stopped")
        self.SetStatusText("Timer stopped")
    
    def on_reset(self, event):
        self.timer.Stop()
        self.timer_count = 0
        self.timer_label.SetLabel("Timer: 0 seconds")
        self.log_event("Timer reset")
        self.SetStatusText("Timer reset")
    
    def on_timer(self, event):
        self.timer_count += 1
        self.timer_label.SetLabel(f"Timer: {self.timer_count} seconds")
        if self.timer_count % 10 == 0:
            self.log_event(f"Timer: {self.timer_count} seconds elapsed")
    
    def on_slider(self, event):
        value = self.slider.GetValue()
        self.text_ctrl.SetValue(str(value))
        self.log_event(f"Slider value changed: {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"Text value changed: {value}")
        except ValueError:
            pass  # Ignore invalid input
    
    def on_checkbox(self, event):
        checkbox = event.GetEventObject()
        label = checkbox.GetLabel()
        checked = checkbox.GetValue()
        status = "checked" if checked else "unchecked"
        self.log_event(f"{label}: {status}")
    
    def on_radio(self, event):
        radio = event.GetEventObject()
        label = radio.GetLabel()
        self.log_event(f"Radio button selected: {label}")
    
    def on_clear_log(self, event):
        self.log_ctrl.Clear()
        self.log_event("Log cleared")
    
    def on_size(self, event):
        size = event.GetSize()
        self.SetStatusText(f"Window size: {size.width} x {size.height}")
        event.Skip()  # Call other handlers too
    
    def on_close(self, event):
        if self.timer.IsRunning():
            self.timer.Stop()
        self.log_event("Application closing")
        event.Skip()

class EventLayoutApp(wx.App):
    def OnInit(self):
        frame = EventLayoutFrame()
        frame.Show()
        return True

if __name__ == '__main__':
    app = EventLayoutApp()
    app.MainLoop()

Drag & Drop and File Operations

# drag_drop_file.py
import wx
import os

class FileDropTarget(wx.FileDropTarget):
    """File drop target"""
    def __init__(self, window):
        wx.FileDropTarget.__init__(self)
        self.window = window
    
    def OnDropFiles(self, x, y, filenames):
        """Handle file drop"""
        for filename in filenames:
            self.window.add_file(filename)
        return True

class DragDropFrame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='Drag & Drop and File Operations')
        self.SetSize(700, 500)
        self.Center()
        
        # Main panel
        panel = wx.Panel(self)
        
        # File list
        self.create_file_list(panel)
        
        # Set file drop target
        drop_target = FileDropTarget(self)
        self.file_list.SetDropTarget(drop_target)
        
        # Menu and toolbar
        self.create_menu_toolbar()
        
        # Status bar
        self.CreateStatusBar()
        self.SetStatusText("Drag and drop files here")
    
    def create_file_list(self, parent):
        # Splitter window
        splitter = wx.SplitterWindow(parent, style=wx.SP_3D | wx.SP_LIVE_UPDATE)
        
        # Left panel: File list
        left_panel = wx.Panel(splitter)
        left_sizer = wx.BoxSizer(wx.VERTICAL)
        
        # File list box
        left_sizer.Add(wx.StaticText(left_panel, label="File List:"), 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)
        
        # Buttons
        btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        add_btn = wx.Button(left_panel, label="Add File")
        remove_btn = wx.Button(left_panel, label="Remove")
        clear_btn = wx.Button(left_panel, label="Clear")
        
        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: File info and preview
        right_panel = wx.Panel(splitter)
        right_sizer = wx.BoxSizer(wx.VERTICAL)
        
        # File info
        info_box = wx.StaticBox(right_panel, label="File Information")
        info_sizer = wx.StaticBoxSizer(info_box, wx.VERTICAL)
        
        self.file_name_label = wx.StaticText(right_panel, label="File name: ")
        self.file_size_label = wx.StaticText(right_panel, label="Size: ")
        self.file_type_label = wx.StaticText(right_panel, label="Type: ")
        self.file_path_label = wx.StaticText(right_panel, label="Path: ")
        
        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 area
        preview_box = wx.StaticBox(right_panel, label="Preview")
        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 settings
        splitter.SplitVertically(left_panel, right_panel, 250)
        splitter.SetMinimumPaneSize(200)
        
        # Main layout
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add(splitter, 1, wx.EXPAND)
        parent.SetSizer(main_sizer)
        
        # File data storage
        self.file_data = {}
    
    def create_menu_toolbar(self):
        # Menu bar
        menubar = wx.MenuBar()
        
        # File menu
        file_menu = wx.Menu()
        file_menu.Append(wx.ID_OPEN, "Open File\tCtrl+O")
        file_menu.Append(wx.ID_SAVEAS, "Export\tCtrl+E")
        file_menu.AppendSeparator()
        file_menu.Append(wx.ID_EXIT, "Exit\tCtrl+Q")
        
        # Edit menu
        edit_menu = wx.Menu()
        edit_menu.Append(wx.ID_DELETE, "Delete\tDel")
        edit_menu.Append(wx.ID_CLEAR, "Clear All\tCtrl+A")
        
        menubar.Append(file_menu, "File")
        menubar.Append(edit_menu, "Edit")
        
        self.SetMenuBar(menubar)
        
        # Event binding
        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
        toolbar = self.CreateToolBar()
        toolbar.AddTool(wx.ID_OPEN, "Open", wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN))
        toolbar.AddTool(wx.ID_SAVEAS, "Export", wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE_AS))
        toolbar.AddSeparator()
        toolbar.AddTool(wx.ID_DELETE, "Delete", wx.ArtProvider.GetBitmap(wx.ART_DELETE))
        toolbar.AddTool(wx.ID_CLEAR, "Clear", wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE))
        toolbar.Realize()
    
    def add_file(self, filepath):
        """Add file to list"""
        if os.path.exists(filepath) and filepath not in self.file_data:
            filename = os.path.basename(filepath)
            self.file_list.Append(filename)
            
            # Save file info
            stat = os.stat(filepath)
            self.file_data[filename] = {
                'path': filepath,
                'size': stat.st_size,
                'type': self.get_file_type(filepath)
            }
            
            self.SetStatusText(f"Added file: {filename}")
    
    def get_file_type(self, filepath):
        """Get file type"""
        ext = os.path.splitext(filepath)[1].lower()
        types = {
            '.txt': 'Text File',
            '.py': 'Python File',
            '.jpg': 'Image File',
            '.jpeg': 'Image File',
            '.png': 'Image File',
            '.gif': 'Image File',
            '.pdf': 'PDF File',
            '.doc': 'Word File',
            '.docx': 'Word File',
            '.xls': 'Excel File',
            '.xlsx': 'Excel File',
        }
        return types.get(ext, 'Unknown File')
    
    def on_file_select(self, event):
        """Handle file selection"""
        selection = self.file_list.GetSelection()
        if selection != wx.NOT_FOUND:
            filename = self.file_list.GetString(selection)
            file_info = self.file_data[filename]
            
            # Display file info
            self.file_name_label.SetLabel(f"File name: {filename}")
            self.file_size_label.SetLabel(f"Size: {file_info['size']} bytes")
            self.file_type_label.SetLabel(f"Type: {file_info['type']}")
            self.file_path_label.SetLabel(f"Path: {file_info['path']}")
            
            # Show preview
            self.show_preview(file_info['path'])
    
    def show_preview(self, filepath):
        """Show file preview"""
        try:
            # For text files
            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)  # Only first 1000 characters
                    if len(content) == 1000:
                        content += "\n... (more content available)"
                    self.preview_text.SetValue(content)
            else:
                self.preview_text.SetValue("This file format cannot be previewed.")
        except Exception as e:
            self.preview_text.SetValue(f"Preview error: {str(e)}")
    
    def on_file_open(self, event):
        """Handle file double-click"""
        selection = self.file_list.GetSelection()
        if selection != wx.NOT_FOUND:
            filename = self.file_list.GetString(selection)
            filepath = self.file_data[filename]['path']
            
            # Open with default application
            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"Opened file: {filename}")
            except Exception as e:
                wx.MessageBox(f"Could not open file: {str(e)}", "Error", wx.OK | wx.ICON_ERROR)
    
    def on_add_file(self, event):
        """File add dialog"""
        with wx.FileDialog(self, "Select files",
                          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):
        """Remove selected file"""
        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]
            
            # Clear display
            self.file_name_label.SetLabel("File name: ")
            self.file_size_label.SetLabel("Size: ")
            self.file_type_label.SetLabel("Type: ")
            self.file_path_label.SetLabel("Path: ")
            self.preview_text.SetValue("")
            
            self.SetStatusText(f"Removed file: {filename}")
    
    def on_clear_files(self, event):
        """Clear all files"""
        self.file_list.Clear()
        self.file_data.clear()
        
        # Clear display
        self.file_name_label.SetLabel("File name: ")
        self.file_size_label.SetLabel("Size: ")
        self.file_type_label.SetLabel("Type: ")
        self.file_path_label.SetLabel("Path: ")
        self.preview_text.SetValue("")
        
        self.SetStatusText("Cleared all files")
    
    def on_export_list(self, event):
        """Export file list"""
        with wx.FileDialog(self, "Save file list",
                          defaultFile="file_list.txt",
                          wildcard="Text files (*.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("File List\n")
                    f.write("=" * 50 + "\n\n")
                    for filename, info in self.file_data.items():
                        f.write(f"File name: {filename}\n")
                        f.write(f"Path: {info['path']}\n")
                        f.write(f"Size: {info['size']} bytes\n")
                        f.write(f"Type: {info['type']}\n")
                        f.write("-" * 30 + "\n")
                
                self.SetStatusText(f"Saved file list: {pathname}")
            except Exception as e:
                wx.MessageBox(f"Could not save: {str(e)}", "Error", 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()

Setup and Build

# Install wxPython
pip install wxpython

# Verify installation
python -c "import wx; print(f'wxPython {wx.version()}')"

# Additional packages (optional)
pip install wxpython[all]  # Include all optional features

# Run application
python hello_world.py

# Create standalone executable
pip install pyinstaller
pyinstaller --onefile --windowed hello_world.py

# Create app bundle on macOS
pyinstaller --onefile --windowed --icon=icon.ico hello_world.py

# Linux dependencies
# Ubuntu/Debian
sudo apt-get install python3-wxgtk4.0-dev

# CentOS/RHEL
sudo yum install wxGTK3-devel

# Create requirements.txt
pip freeze > requirements.txt

Example Project Structure

my_wxpython_app/
├── main.py                 # Main application
├── frames/
│   ├── __init__.py
│   ├── main_frame.py       # Main frame
│   └── dialog_frames.py    # Dialog classes
├── panels/
│   ├── __init__.py
│   ├── custom_panels.py    # Custom panels
│   └── grid_panels.py      # Grid-related panels
├── utils/
│   ├── __init__.py
│   ├── file_utils.py       # File operation utilities
│   └── event_utils.py      # Event handling utilities
├── resources/
│   ├── icons/              # Icon files
│   ├── images/             # Image files
│   └── data/               # Data files
├── tests/
│   ├── __init__.py
│   └── test_main.py        # Test files
├── requirements.txt
└── README.md