杰瑞科技汇

wxPython教程,如何快速入门?

wxPython 教程:从入门到实战

目录

  1. 第一部分:wxPython 简介
    • 什么是 wxPython?
    • 为什么选择 wxPython?
    • 环境安装
  2. 第二部分:核心概念
    • 应用程序类 (wx.App)
    • 窗口类 (wx.Frame)
    • 控件
    • 布局管理器
    • 事件处理
  3. 第三部分:常用控件详解
    • 静态文本 (wx.StaticText)
    • 文本输入框 (wx.TextCtrl)
    • 按钮 (wx.Button)
    • 单选按钮 (wx.RadioButton)
    • 复选框 (wx.CheckBox)
    • 列表框 (wx.ListBox)
    • 面板 (wx.Panel)
  4. 第四部分:布局实战
    • wx.BoxSizer (垂直和水平布局)
    • wx.GridSizer (网格布局)
    • wx.FlexGridSizer (灵活网格布局)
  5. 第五部分:事件处理进阶
    • 绑定方法
    • 使用 wx.EVT_* 常量
  6. 第六部分:创建一个完整的应用程序
    • 案例:一个简单的记事本
    • 添加菜单栏 (wx.MenuBar)
    • 添加工具栏 (wx.ToolBar)
    • 添加状态栏 (wx.StatusBar)
  7. 第七部分:高级主题与资源
    • 自定义对话框 (wx.Dialog)
    • 使用资源文件 (.xrc)
    • 多线程与界面更新
    • 推荐学习资源

第一部分:wxPython 简介

什么是 wxPython?

wxPython 是一个 Python 的第三方库,它是对跨平台 GUI 工具库 wxWidgets 的 Python 封装,这意味着你可以用 Python 代码来创建原生的 Windows、macOS 和 Linux 应用程序,这些应用程序的外观和感觉会与所在平台保持一致。

wxPython教程,如何快速入门?-图1
(图片来源网络,侵删)

为什么选择 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 系统、管理主事件循环和处理未捕获的异常。

wxPython教程,如何快速入门?-图2
(图片来源网络,侵删)
  • wx.App(False): False 参数表示不自动创建任何窗口,我们可以手动创建。
  • app.MainLoop(): 启动应用程序的事件循环,程序会在这里等待用户操作(如点击按钮、移动鼠标),直到窗口被关闭。

窗口类 (wx.Frame)

wx.Frame 是应用程序的主窗口,它有自己的标题栏、边框和可以包含其他控件(按钮、文本框等)的区域。

frame = wx.Frame(None, title="我的第一个窗口", size=(400, 300))
  • None: 表示这个窗口没有父窗口,它是顶级窗口。: 窗口标题。
  • size: 窗口的初始大小(宽度和高度)。

控件

控件是构成用户界面的基本元素,如按钮、文本框、菜单等,控件必须被放置在 wx.Framewx.Panel 上才能显示。

button = wx.Button(frame, label="点击我")

布局管理器

直接使用绝对坐标(pos=(x, y))来放置控件在窗口大小改变时会非常糟糕,wxPython 强烈使用布局管理器来自动调整控件的位置和大小。

最常用的是 wx.BoxSizer,它可以是水平的 (wx.HORIZONTAL) 或垂直的 (wx.VERTICAL)。

wxPython教程,如何快速入门?-图3
(图片来源网络,侵删)
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 程序是事件驱动的,用户的每一个操作(点击、输入、关闭窗口)都会产生一个事件,我们需要编写“事件处理器”函数来响应这些事件。

基本流程:

  1. 创建控件
  2. 定义一个函数,这个函数会在事件发生时被调用。
  3. 将控件和事件绑定到这个函数。
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!祝你编码愉快!

分享:
扫描分享到社交APP
上一篇
下一篇