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.
GitHub Overview
wxWidgets/Phoenix
wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.
Topics
Star History
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
- wxPython Official Site
- wxPython Official Documentation
- wxPython GitHub Repository
- wxWidgets Official Site
- wxGlade - Visual UI Designer
- wxPython Demo
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