wxPython 教程:从入门到实战
目录
- 第一部分:wxPython 简介
- 什么是 wxPython?
- 为什么选择 wxPython?
- 环境安装
- 第二部分:核心概念
- 应用程序类 (
wx.App) - 窗口类 (
wx.Frame) - 控件
- 布局管理器
- 事件处理
- 应用程序类 (
- 第三部分:常用控件详解
- 静态文本 (
wx.StaticText) - 文本输入框 (
wx.TextCtrl) - 按钮 (
wx.Button) - 单选按钮 (
wx.RadioButton) - 复选框 (
wx.CheckBox) - 列表框 (
wx.ListBox) - 面板 (
wx.Panel)
- 静态文本 (
- 第四部分:布局实战
wx.BoxSizer(垂直和水平布局)wx.GridSizer(网格布局)wx.FlexGridSizer(灵活网格布局)
- 第五部分:事件处理进阶
- 绑定方法
- 使用
wx.EVT_*常量
- 第六部分:创建一个完整的应用程序
- 案例:一个简单的记事本
- 添加菜单栏 (
wx.MenuBar) - 添加工具栏 (
wx.ToolBar) - 添加状态栏 (
wx.StatusBar)
- 第七部分:高级主题与资源
- 自定义对话框 (
wx.Dialog) - 使用资源文件 (.xrc)
- 多线程与界面更新
- 推荐学习资源
- 自定义对话框 (
第一部分:wxPython 简介
什么是 wxPython?
wxPython 是一个 Python 的第三方库,它是对跨平台 GUI 工具库 wxWidgets 的 Python 封装,这意味着你可以用 Python 代码来创建原生的 Windows、macOS 和 Linux 应用程序,这些应用程序的外观和感觉会与所在平台保持一致。

为什么选择 wxPython?
- 跨平台:一套代码,可在多个操作系统上运行。
- 原生外观:应用程序看起来就像是用原生语言(如 C++)编写的,用户体验好。
- 功能强大:提供了几乎所有你能想到的 GUI 控件和功能。
- 成熟稳定:wxWidgets 本身已经有非常长的历史,wxPython 也发展了多年,非常可靠。
- 面向对象:设计清晰,易于理解和扩展。
环境安装
在命令行中,使用 pip 进行安装:
pip install wxPython
为了验证安装是否成功,可以运行以下 Python 代码:
import wx app = wx.App(False) # 创建一个 wx.App 实例 frame = wx.Frame(None, title="Hello World") # 创建一个窗口 frame.Show(True) # 显示窗口 app.MainLoop() # 进入主事件循环
如果弹出了一个标题为 "Hello World" 的窗口,那么恭喜你,wxPython 已经成功安装!
第二部分:核心概念
应用程序类 (wx.App)
wx.App 是 wxPython 应用的心脏,它负责初始化 GUI 系统、管理主事件循环和处理未捕获的异常。

wx.App(False):False参数表示不自动创建任何窗口,我们可以手动创建。app.MainLoop(): 启动应用程序的事件循环,程序会在这里等待用户操作(如点击按钮、移动鼠标),直到窗口被关闭。
窗口类 (wx.Frame)
wx.Frame 是应用程序的主窗口,它有自己的标题栏、边框和可以包含其他控件(按钮、文本框等)的区域。
frame = wx.Frame(None, title="我的第一个窗口", size=(400, 300))
None: 表示这个窗口没有父窗口,它是顶级窗口。: 窗口标题。size: 窗口的初始大小(宽度和高度)。
控件
控件是构成用户界面的基本元素,如按钮、文本框、菜单等,控件必须被放置在 wx.Frame 或 wx.Panel 上才能显示。
button = wx.Button(frame, label="点击我")
布局管理器
直接使用绝对坐标(pos=(x, y))来放置控件在窗口大小改变时会非常糟糕,wxPython 强烈使用布局管理器来自动调整控件的位置和大小。
最常用的是 wx.BoxSizer,它可以是水平的 (wx.HORIZONTAL) 或垂直的 (wx.VERTICAL)。

sizer = wx.BoxSizer(wx.VERTICAL) # 创建一个垂直布局器 sizer.Add(button, 0, wx.ALL, 5) # 将按钮添加到布局器中 frame.SetSizer(sizer) # 将布局器设置给窗口
sizer.Add(control, proportion, flag, border)control: 要添加的控件。proportion: 控件在可用空间中的伸缩比例,0 表示不伸缩。flag: 对齐方式和边框样式,如wx.ALL(四周),wx.ALIGN_CENTER(居中)。border: 边框宽度(像素)。
事件处理
GUI 程序是事件驱动的,用户的每一个操作(点击、输入、关闭窗口)都会产生一个事件,我们需要编写“事件处理器”函数来响应这些事件。
基本流程:
- 创建控件。
- 定义一个函数,这个函数会在事件发生时被调用。
- 将控件和事件绑定到这个函数。
def on_button_click(event):
print("按钮被点击了!")
button.Bind(wx.EVT_BUTTON, on_button_click)
第三部分:常用控件详解
让我们在一个 wx.Panel 上创建控件,Panel 是放置控件的容器,通常比直接放在 Frame 上更灵活。
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super(MyFrame, self).__init__(parent, title=title, size=(400, 300))
# 创建一个面板
panel = wx.Panel(self)
# --- 在这里添加控件 ---
# 1. 静态文本
static_text = wx.StaticText(panel, label="用户名:", pos=(10, 10))
# 2. 文本输入框
text_ctrl = wx.TextCtrl(panel, pos=(70, 10))
# 3. 按钮
button = wx.Button(panel, label="提交", pos=(10, 50))
button.Bind(wx.EVT_BUTTON, self.on_submit) # 绑定点击事件
self.Centre() # 窗口居中
self.Show()
def on_submit(self, event):
print(f"提交的用户名是: {self.FindWindowById(1001).GetValue()}")
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame(None, "控件示例")
app.MainLoop()
其他常用控件
wx.RadioButton(单选按钮): 一组中只能选一个。wx.CheckBox(复选框): 可以多选。wx.ListBox(列表框): 显示一个列表,可以选择一项或多项。
第四部分:布局实战
使用绝对布局 (pos) 是不好的习惯,让我们用 wx.BoxSizer 来重构上面的例子。
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super(MyFrame, self).__init__(parent, title=title, size=(400, 300))
self.InitUI()
self.Centre()
self.Show()
def InitUI(self):
panel = wx.Panel(self)
# 创建垂直布局器
vbox = wx.BoxSizer(wx.VERTICAL)
# 用户名
hbox1 = wx.BoxSizer(wx.HORIZONTAL)
st1 = wx.StaticText(panel, label="用户名:")
tc1 = wx.TextCtrl(panel)
hbox1.Add(st1, flag=wx.RIGHT, border=8)
hbox1.Add(tc1, proportion=1)
vbox.Add(hbox1, flag=wx.EXPAND | wx.ALL, border=10)
# 密码
hbox2 = wx.BoxSizer(wx.HORIZONTAL)
st2 = wx.StaticText(panel, label="密码:")
tc2 = wx.TextCtrl(panel, style=wx.TE_PASSWORD) # 密码输入框
hbox2.Add(st2, flag=wx.RIGHT, border=8)
hbox2.Add(tc2, proportion=1)
vbox.Add(hbox2, flag=wx.EXPAND | wx.ALL, border=10)
# 提交按钮
btn = wx.Button(panel, label="提交")
vbox.Add(btn, flag=wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, border=10)
btn.Bind(wx.EVT_BUTTON, self.on_submit)
panel.SetSizer(vbox)
def on_submit(self, event):
print("提交按钮被点击!")
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame(None, "布局示例")
app.MainLoop()
wx.BoxSizer(wx.HORIZONTAL): 创建一个水平布局器。proportion=1: 让文本输入框在水平方向上可以伸缩,填满剩余空间。wx.EXPAND: 让控件在分配的空间内尽可能伸展。wx.ALIGN_CENTER: 让按钮在垂直方向上居中。
第五部分:事件处理进阶
除了 Bind 方法,还可以使用 Connect 方法,或者更现代的 EVT_* 常量。
# 方法一:Bind (推荐) button.Bind(wx.EVT_BUTTON, self.on_button_click) # 方法二:Connect (传统方法) self.Connect(button.GetId(), wx.EVT_BUTTON.typeId, self.on_button_click) # 方法三:在构造函数中使用EVT_* (在wxPython 4.0+中仍然可用,但Bind更灵活) # self.Bind(wx.EVT_BUTTON, self.on_button_click, button)
第六部分:创建一个完整的应用程序
我们来创建一个简单的“记事本”应用程序,包含菜单栏、工具栏、文本区域和状态栏。
import wx
import os
class NotepadFrame(wx.Frame):
def __init__(self, parent, title):
super(NotepadFrame, self).__init__(parent, title=title, size=(800, 600))
self.current_file = None
self.InitUI()
self.Centre()
def InitUI(self):
# 1. 创建菜单栏
menubar = wx.MenuBar()
file_menu = wx.Menu()
help_menu = wx.Menu()
# 菜单项
new_item = file_menu.Append(wx.ID_NEW, '新建(N)', '新建文件')
open_item = file_menu.Append(wx.ID_OPEN, '打开(O)', '打开文件')
save_item = file_menu.Append(wx.ID_SAVE, '保存(S)', '保存文件')
save_as_item = file_menu.Append(wx.ID_SAVEAS, '另存为(A)', '另存为文件')
file_menu.AppendSeparator()
quit_item = file_menu.Append(wx.ID_EXIT, '退出(Q)', '退出应用程序')
about_item = help_menu.Append(wx.ID_ABOUT, 'A)', '关于记事本')
menubar.Append(file_menu, '文件(F)')
menubar.Append(help_menu, '帮助(H)')
self.SetMenuBar(menubar)
# 2. 创建工具栏
toolbar = self.CreateToolBar()
new_tb = toolbar.AddTool(wx.ID_NEW, '新建', wx.Bitmap('new.png'))
open_tb = toolbar.AddTool(wx.ID_OPEN, '打开', wx.Bitmap('open.png'))
save_tb = toolbar.AddTool(wx.ID_SAVE, '保存', wx.Bitmap('save.png'))
toolbar.Realize()
# 3. 创建文本区域
self.text_ctrl = wx.TextCtrl(self, style=wx.TE_MULTILINE)
# 4. 创建状态栏
self.CreateStatusBar()
# 5. 绑定事件
self.Bind(wx.EVT_MENU, self.on_new, new_item)
self.Bind(wx.EVT_MENU, self.on_open, open_item)
self.Bind(wx.EVT_MENU, self.on_save, save_item)
self.Bind(wx.EVT_MENU, self.on_save_as, save_as_item)
self.Bind(wx.EVT_MENU, self.on_quit, quit_item)
self.Bind(wx.EVT_MENU, self.on_about, about_item)
# 绑定工具栏事件
self.Bind(wx.EVT_TOOL, self.on_new, new_tb)
self.Bind(wx.EVT_TOOL, self.on_open, open_tb)
self.Bind(wx.EVT_TOOL, self.on_save, save_tb)
# --- 事件处理函数 ---
def on_new(self, event):
self.text_ctrl.SetValue('')
self.current_file = None
self.SetTitle("未命名 - 记事本")
def on_open(self, event):
with wx.FileDialog(self, "打开文件", wildcard="文本文件 (*.txt)|*.txt",
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return
pathname = fileDialog.GetPath()
try:
with open(pathname, 'r', encoding='utf-8') as file:
self.text_ctrl.SetValue(file.read())
self.current_file = pathname
self.SetTitle(f"{os.path.basename(pathname)} - 记事本")
except IOError:
wx.LogError(f"无法打开文件 {pathname}.")
def on_save(self, event):
if not self.current_file:
self.on_save_as(event)
else:
self.save_file(self.current_file)
def on_save_as(self, event):
with wx.FileDialog(self, "保存文件", wildcard="文本文件 (*.txt)|*.txt",
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return
pathname = fileDialog.GetPath()
self.save_file(pathname)
self.current_file = pathname
self.SetTitle(f"{os.path.basename(pathname)} - 记事本")
def save_file(self, pathname):
try:
with open(pathname, 'w', encoding='utf-8') as file:
file.write(self.text_ctrl.GetValue())
self.SetStatusMessage(f"文件已保存到 {pathname}")
except IOError:
wx.LogError(f"无法保存文件到 {pathname}.")
def on_quit(self, event):
self.Close()
def on_about(self, event):
wx.MessageBox('这是一个简单的 wxPython 记事本示例', '#39;, wx.OK | wx.ICON_INFORMATION)
if __name__ == '__main__':
app = wx.App(False)
frame = NotepadFrame(None, "记事本")
app.MainLoop()
第七部分:高级主题与资源
自定义对话框 (wx.Dialog)
当你需要用户输入一些信息或确认操作时,可以创建自定义对话框。
class MyDialog(wx.Dialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent, title="输入对话框")
self.InitUI()
self.Centre()
def InitUI(self):
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
wx.StaticText(panel, label="请输入你的名字:")
self.name_text = wx.TextCtrl(panel)
hbox = wx.BoxSizer(wx.HORIZONTAL)
ok_button = wx.Button(panel, label="确定")
cancel_button = wx.Button(panel, label="取消")
hbox.Add(ok_button, 0, wx.ALL, 5)
hbox.Add(cancel_button, 0, wx.ALL, 5)
vbox.Add(panel, 1, wx.EXPAND)
vbox.Add(hbox, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10)
self.SetSizer(vbox)
ok_button.Bind(wx.EVT_BUTTON, self.on_ok)
cancel_button.Bind(wx.EVT_BUTTON, self.on_cancel)
def on_ok(self, event):
print(f"你输入的名字是: {self.name_text.GetValue()}")
self.EndModal(wx.ID_OK) # 以ID_OK结束对话框
def on_cancel(self, event):
self.EndModal(wx.ID_CANCEL) # 以ID_CANCEL结束对话框
# 在主窗口中调用
# dialog = MyDialog(self)
# if dialog.ShowModal() == wx.ID_OK:
# print("用户点击了确定")
# dialog.Destroy()
使用资源文件 (.xrc)
对于复杂的 UI,直接用 Python 代码编写会很臃肿,可以使用 wxFormBuilder 或 DialogBlocks 等可视化设计器来创建 UI,并导出为 XRC (XML Resource) 文件,然后在 Python 中加载它。
import wx
import wx.xrc as xrc
class MyFrame(wx.Frame):
def __init__(self, parent):
self.res = xrc.XmlResource("my_dialog.xrc") # 加载XRC文件
self.frame = self.res.LoadFrame(parent, "MyFrame")
self.panel = xrc.XRCCTRL(self.frame, "panel") # 获取面板
self.button = xrc.XRCCTRL(self.panel, "myButton") # 获取按钮
self.button.Bind(wx.EVT_BUTTON, self.on_button_click)
self.frame.Show()
def on_button_click(self, event):
wx.MessageBox("Hello from XRC!", "Info", wx.OK)
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame(None)
app.MainLoop()
多线程与界面更新
重要原则: 绝对不要在非主线程中操作 UI 控件,所有 UI 更新都必须在主线程(即 MainLoop 所在的线程)中进行。
可以使用 wx.CallAfter 来安全地从其他线程调用 UI 更新函数。
import wx
import threading
import time
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super(MyFrame, self).__init__(parent, title=title, size=(300, 200))
self.panel = wx.Panel(self)
self.btn = wx.Button(self.panel, label="开始任务", pos=(50, 50))
self.progress = wx.Gauge(self.panel, range=100, pos=(50, 100), size=(200, 25))
self.btn.Bind(wx.EVT_BUTTON, self.on_start)
self.Show()
def on_start(self, event):
self.btn.Disable()
# 在新线程中运行耗时任务
thread = threading.Thread(target=self.run_long_task)
thread.start()
def run_long_task(self):
for i in range(100):
time.sleep(0.05)
# 使用 CallAfter 安全地更新进度条
wx.CallAfter(self.update_progress, i + 1)
wx.CallAfter(self.task_finished)
def update_progress(self, value):
self.progress.SetValue(value)
def task_finished(self):
wx.MessageBox("任务完成!", "提示", wx.OK)
self.progress.SetValue(0)
self.btn.Enable()
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame(None, "多线程示例")
app.MainLoop()
推荐学习资源
- 官方文档: wxPython Phoenix Documentation (强烈推荐,内容最权威)
- Robin Dunn 的博客: wxPython 的创始人的博客,有很多深入的教程和文章。
- GitHub: wxPython/wxPython (源码和问题追踪)
- Stack Overflow: 搜索
wxpython标签,可以找到大量已解决的问题。
希望这份详尽的教程能帮助你顺利入门 wxPython!祝你编码愉快!
