wxPython高级主题
掌握了wxPython的基础知识后,我们可以探索一些高级主题,这些主题将帮助您创建更强大、更专业的应用程序。本章将介绍多线程、国际化、资源管理等高级特性。
多线程编程
在GUI应用程序中,长时间运行的操作会阻塞用户界面。使用多线程可以避免这个问题:
Python
import wx
import threading
import time
import queue
class ThreadedAppFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="多线程示例")
self.panel = wx.Panel(self)
self.result_text = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE | wx.TE_READONLY, size=(400, 200))
self.start_button = wx.Button(self.panel, label="开始长时间任务")
self.progress = wx.Gauge(self.panel, range=100)
# 创建事件队列
self.event_queue = queue.Queue()
# 绑定事件
self.start_button.Bind(wx.EVT_BUTTON, self.on_start_task)
self.Bind(wx.EVT_IDLE, self.on_idle)
# 布局
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.result_text, 1, wx.ALL | wx.EXPAND, 10)
sizer.Add(self.progress, 0, wx.ALL | wx.EXPAND, 10)
sizer.Add(self.start_button, 0, wx.ALL | wx.CENTER, 10)
self.panel.SetSizer(sizer)
def on_start_task(self, event):
self.start_button.Enable(False)
self.result_text.AppendText("开始执行长时间任务...\n")
self.progress.SetValue(0)
# 启动工作线程
thread = threading.Thread(target=self.long_running_task)
thread.daemon = True
thread.start()
def long_running_task(self):
for i in range(101):
# 模拟长时间运行的任务
time.sleep(0.1)
# 将进度更新放入队列
self.event_queue.put(("progress", i))
# 检查是否需要取消
if hasattr(self, 'cancel_task') and self.cancel_task:
self.event_queue.put(("cancelled",))
return
# 任务完成
self.event_queue.put(("completed", "任务完成!"))
def on_idle(self, event):
# 处理队列中的事件
try:
while True:
msg_type, data = self.event_queue.get_nowait()
if msg_type == "progress":
self.progress.SetValue(data)
self.result_text.AppendText(f"进度: {data}%\n")
elif msg_type == "completed":
self.result_text.AppendText(f"{data}\n")
self.start_button.Enable(True)
elif msg_type == "cancelled":
self.result_text.AppendText("任务已取消\n")
self.start_button.Enable(True)
except queue.Empty:
pass
# 继续接收空闲事件
if not self.event_queue.empty():
event.RequestMore()
使用wx.CallAfter进行线程安全操作
wx.CallAfter是另一种在多线程环境中更新UI的安全方法:
Python
import wx
import threading
import time
class CallAfterFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="wx.CallAfter示例")
self.panel = wx.Panel(self)
self.status_text = wx.StaticText(self.panel, label="准备就绪")
self.start_button = wx.Button(self.panel, label="开始后台任务")
self.start_button.Bind(wx.EVT_BUTTON, self.on_start_task)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.status_text, 0, wx.ALL, 10)
sizer.Add(self.start_button, 0, wx.ALL, 10)
self.panel.SetSizer(sizer)
def on_start_task(self, event):
self.start_button.Enable(False)
self.status_text.SetLabel("后台任务运行中...")
# 启动后台线程
thread = threading.Thread(target=self.background_task)
thread.daemon = True
thread.start()
def background_task(self):
# 模拟后台工作
time.sleep(3)
# 使用wx.CallAfter安全更新UI
wx.CallAfter(self.on_task_completed)
def on_task_completed(self):
self.status_text.SetLabel("后台任务已完成!")
self.start_button.Enable(True)
国际化和本地化
wxPython支持应用程序的国际化,可以为不同语言的用户提供本地化界面:
Python
import wx
import locale
import gettext
import os
class I18nFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="国际化示例")
self.panel = wx.Panel(self)
# 设置语言
self.setup_locale()
# 创建控件
self.label = wx.StaticText(self.panel, label=_("请选择您的语言:"))
self.lang_choice = wx.Choice(self.panel, choices=[_("中文"), _("English")])
self.greeting_text = wx.StaticText(self.panel, label=_("您好,欢迎使用我们的应用程序!"))
self.button = wx.Button(self.panel, label=_("点击我"))
# 绑定事件
self.lang_choice.Bind(wx.EVT_CHOICE, self.on_language_change)
self.button.Bind(wx.EVT_BUTTON, self.on_button_click)
# 布局
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.label, 0, wx.ALL, 10)
sizer.Add(self.lang_choice, 0, wx.ALL | wx.EXPAND, 10)
sizer.Add(self.greeting_text, 0, wx.ALL, 10)
sizer.Add(self.button, 0, wx.ALL | wx.CENTER, 10)
self.panel.SetSizer(sizer)
def setup_locale(self):
# 设置语言环境(简化示例)
lang = locale.getdefaultlocale()[0]
if lang and lang.startswith('zh'):
self.current_lang = 'zh'
else:
self.current_lang = 'en'
def on_language_change(self, event):
selection = self.lang_choice.GetSelection()
if selection == 0: # 中文
self.switch_language('zh')
else: # 英文
self.switch_language('en')
def switch_language(self, lang):
self.current_lang = lang
# 在实际应用中,这里会重新加载翻译文件
if lang == 'zh':
self.label.SetLabel("请选择您的语言:")
self.lang_choice.SetItems(["中文", "English"])
self.greeting_text.SetLabel("您好,欢迎使用我们的应用程序!")
self.button.SetLabel("点击我")
else:
self.label.SetLabel("Please select your language:")
self.lang_choice.SetItems(["Chinese", "English"])
self.greeting_text.SetLabel("Hello, welcome to our application!")
self.button.SetLabel("Click me")
self.Layout()
def on_button_click(self, event):
if self.current_lang == 'zh':
wx.MessageBox("按钮被点击了!", "信息", wx.OK | wx.ICON_INFORMATION)
else:
wx.MessageBox("Button clicked!", "Information", wx.OK | wx.ICON_INFORMATION)
资源管理和优化
有效的资源管理对于大型应用程序至关重要:
Python
import wx
import gc
class ResourceManagerFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="资源管理示例")
self.panel = wx.Panel(self)
self.bitmap = None
self.timer = None
# 创建控件
self.load_button = wx.Button(self.panel, label="加载资源")
self.release_button = wx.Button(self.panel, label="释放资源")
self.gc_button = wx.Button(self.panel, label="运行垃圾回收")
self.info_text = wx.StaticText(self.panel, label="资源状态: 未加载")
# 绑定事件
self.load_button.Bind(wx.EVT_BUTTON, self.on_load_resource)
self.release_button.Bind(wx.EVT_BUTTON, self.on_release_resource)
self.gc_button.Bind(wx.EVT_BUTTON, self.on_run_gc)
# 布局
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.load_button, 0, wx.ALL, 10)
sizer.Add(self.release_button, 0, wx.ALL, 10)
sizer.Add(self.gc_button, 0, wx.ALL, 10)
sizer.Add(self.info_text, 0, wx.ALL, 10)
self.panel.SetSizer(sizer)
def on_load_resource(self, event):
# 模拟加载大型资源
try:
# 在实际应用中,这里会加载图片、数据等资源
self.bitmap = wx.Bitmap(100, 100)
self.info_text.SetLabel("资源状态: 已加载")
self.release_button.Enable(True)
# 启动定时器模拟持续使用资源
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_timer, self.timer)
self.timer.Start(1000)
except Exception as e:
wx.MessageBox(f"加载资源失败: {e}", "错误", wx.OK | wx.ICON_ERROR)
def on_release_resource(self, event):
# 释放资源
if self.bitmap:
del self.bitmap
self.bitmap = None
if self.timer:
self.timer.Stop()
self.timer.Destroy()
self.timer = None
self.info_text.SetLabel("资源状态: 已释放")
self.release_button.Enable(False)
def on_run_gc(self, event):
# 运行垃圾回收
collected = gc.collect()
wx.MessageBox(f"垃圾回收完成,清理了 {collected} 个对象", "信息", wx.OK | wx.ICON_INFORMATION)
def on_timer(self, event):
# 定时器事件处理
self.info_text.SetLabel(f"资源使用中... {time.time() % 60:.1f}s")
def __del__(self):
# 清理资源
if self.timer:
self.timer.Stop()
self.timer.Destroy()
自定义对话框
创建自定义对话框以满足特定需求:
Python
import wx
class CustomDialog(wx.Dialog):
def __init__(self, parent, title="自定义对话框"):
super().__init__(parent, title=title, size=(400, 300))
# 创建控件
self.panel = wx.Panel(self)
self.name_label = wx.StaticText(self.panel, label="姓名:")
self.name_ctrl = wx.TextCtrl(self.panel)
self.email_label = wx.StaticText(self.panel, label="邮箱:")
self.email_ctrl = wx.TextCtrl(self.panel)
self.phone_label = wx.StaticText(self.panel, label="电话:")
self.phone_ctrl = wx.TextCtrl(self.panel)
# 创建复选框
self.subscribe_checkbox = wx.CheckBox(self.panel, label="订阅我们的新闻通讯")
# 创建按钮
self.ok_button = wx.Button(self.panel, wx.ID_OK, "确定")
self.cancel_button = wx.Button(self.panel, wx.ID_CANCEL, "取消")
# 绑定事件
self.ok_button.Bind(wx.EVT_BUTTON, self.on_ok)
# 布局
main_sizer = wx.BoxSizer(wx.VERTICAL)
# 表单布局
form_sizer = wx.FlexGridSizer(cols=2, hgap=10, vgap=10)
form_sizer.AddGrowableCol(1)
form_sizer.Add(self.name_label, 0, wx.ALIGN_CENTER_VERTICAL)
form_sizer.Add(self.name_ctrl, 0, wx.EXPAND)
form_sizer.Add(self.email_label, 0, wx.ALIGN_CENTER_VERTICAL)
form_sizer.Add(self.email_ctrl, 0, wx.EXPAND)
form_sizer.Add(self.phone_label, 0, wx.ALIGN_CENTER_VERTICAL)
form_sizer.Add(self.phone_ctrl, 0, wx.EXPAND)
main_sizer.Add(form_sizer, 0, wx.ALL | wx.EXPAND, 20)
main_sizer.Add(self.subscribe_checkbox, 0, wx.ALL, 20)
# 按钮布局
button_sizer = wx.BoxSizer(wx.HORIZONTAL)
button_sizer.AddStretchSpacer(1)
button_sizer.Add(self.ok_button, 0, wx.RIGHT, 10)
button_sizer.Add(self.cancel_button, 0)
main_sizer.Add(button_sizer, 0, wx.ALL | wx.EXPAND, 20)
self.panel.SetSizer(main_sizer)
# 设置默认按钮
self.ok_button.SetDefault()
def on_ok(self, event):
# 验证输入
name = self.name_ctrl.GetValue().strip()
email = self.email_ctrl.GetValue().strip()
if not name:
wx.MessageBox("请输入姓名", "验证错误", wx.OK | wx.ICON_WARNING)
self.name_ctrl.SetFocus()
return
if not email or "@" not in email:
wx.MessageBox("请输入有效的邮箱地址", "验证错误", wx.OK | wx.ICON_WARNING)
self.email_ctrl.SetFocus()
return
# 验证通过,结束对话框
self.EndModal(wx.ID_OK)
def get_data(self):
return {
'name': self.name_ctrl.GetValue(),
'email': self.email_ctrl.GetValue(),
'phone': self.phone_ctrl.GetValue(),
'subscribe': self.subscribe_checkbox.GetValue()
}
class DialogDemoFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="自定义对话框演示")
self.panel = wx.Panel(self)
self.show_dialog_button = wx.Button(self.panel, label="显示自定义对话框")
self.result_text = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE | wx.TE_READONLY, size=(300, 150))
self.show_dialog_button.Bind(wx.EVT_BUTTON, self.on_show_dialog)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.show_dialog_button, 0, wx.ALL, 10)
sizer.Add(self.result_text, 1, wx.ALL | wx.EXPAND, 10)
self.panel.SetSizer(sizer)
def on_show_dialog(self, event):
dialog = CustomDialog(self)
if dialog.ShowModal() == wx.ID_OK:
data = dialog.get_data()
result = f"姓名: {data['name']}\n邮箱: {data['email']}\n电话: {data['phone']}\n订阅: {'是' if data['subscribe'] else '否'}"
self.result_text.SetValue(result)
dialog.Destroy()
应用程序配置管理
使用wx.Config管理应用程序配置:
Python
import wx
class ConfigFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="配置管理示例")
self.panel = wx.Panel(self)
# 获取配置对象
self.config = wx.Config("MyApp")
# 创建控件
self.username_label = wx.StaticText(self.panel, label="用户名:")
self.username_ctrl = wx.TextCtrl(self.panel)
self.theme_label = wx.StaticText(self.panel, label="主题:")
self.theme_choice = wx.Choice(self.panel, choices=["浅色", "深色", "蓝色"])
self.autosave_checkbox = wx.CheckBox(self.panel, label="自动保存")
self.save_button = wx.Button(self.panel, label="保存配置")
self.load_button = wx.Button(self.panel, label="加载配置")
# 绑定事件
self.save_button.Bind(wx.EVT_BUTTON, self.on_save_config)
self.load_button.Bind(wx.EVT_BUTTON, self.on_load_config)
# 加载现有配置
self.load_config()
# 布局
sizer = wx.BoxSizer(wx.VERTICAL)
# 用户名
username_sizer = wx.BoxSizer(wx.HORIZONTAL)
username_sizer.Add(self.username_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 10)
username_sizer.Add(self.username_ctrl, 1, wx.EXPAND)
sizer.Add(username_sizer, 0, wx.ALL | wx.EXPAND, 10)
# 主题
theme_sizer = wx.BoxSizer(wx.HORIZONTAL)
theme_sizer.Add(self.theme_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 10)
theme_sizer.Add(self.theme_choice, 1, wx.EXPAND)
sizer.Add(theme_sizer, 0, wx.ALL | wx.EXPAND, 10)
# 自动保存
sizer.Add(self.autosave_checkbox, 0, wx.ALL, 10)
# 按钮
button_sizer = wx.BoxSizer(wx.HORIZONTAL)
button_sizer.Add(self.save_button, 0, wx.RIGHT, 10)
button_sizer.Add(self.load_button, 0)
sizer.Add(button_sizer, 0, wx.ALL | wx.CENTER, 10)
self.panel.SetSizer(sizer)
def on_save_config(self, event):
# 保存配置
self.config.Write("username", self.username_ctrl.GetValue())
self.config.Write("theme", self.theme_choice.GetStringSelection())
self.config.WriteBool("autosave", self.autosave_checkbox.GetValue())
self.config.Flush() # 确保立即写入
wx.MessageBox("配置已保存", "信息", wx.OK | wx.ICON_INFORMATION)
def on_load_config(self, event):
# 加载配置
self.load_config()
wx.MessageBox("配置已加载", "信息", wx.OK | wx.ICON_INFORMATION)
def load_config(self):
# 从配置加载值
username = self.config.Read("username", "默认用户")
theme = self.config.Read("theme", "浅色")
autosave = self.config.ReadBool("autosave", False)
self.username_ctrl.SetValue(username)
self.theme_choice.SetStringSelection(theme)
self.autosave_checkbox.SetValue(autosave)
系统托盘集成
在系统托盘中显示应用程序图标:
Python
import wx
import wx.adv
class TrayFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="系统托盘示例")
self.panel = wx.Panel(self)
# 创建托盘图标
self.create_tray_icon()
# 创建控件
self.hide_button = wx.Button(self.panel, label="隐藏到托盘")
self.show_button = wx.Button(self.panel, label="从托盘显示")
self.close_button = wx.Button(self.panel, label="退出")
# 绑定事件
self.hide_button.Bind(wx.EVT_BUTTON, self.on_hide)
self.show_button.Bind(wx.EVT_BUTTON, self.on_show)
self.close_button.Bind(wx.EVT_BUTTON, self.on_close)
self.Bind(wx.EVT_CLOSE, self.on_frame_close)
# 布局
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.hide_button, 0, wx.ALL, 10)
sizer.Add(self.show_button, 0, wx.ALL, 10)
sizer.Add(self.close_button, 0, wx.ALL, 10)
self.panel.SetSizer(sizer)
def create_tray_icon(self):
# 创建托盘图标
icon = wx.Icon()
# 在实际应用中,这里会加载一个真正的图标文件
icon.CopyFromBitmap(wx.Bitmap(16, 16))
self.tray_icon = wx.adv.TaskBarIcon()
self.tray_icon.SetIcon(icon, "wxPython示例")
# 绑定托盘图标事件
self.tray_icon.Bind(wx.EVT_MENU, self.on_restore, id=wx.ID_REDO)
self.tray_icon.Bind(wx.EVT_MENU, self.on_exit, id=wx.ID_EXIT)
self.tray_icon.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)
def on_hide(self, event):
self.Hide()
def on_show(self, event):
self.Show()
self.Restore()
def on_close(self, event):
self.tray_icon.Destroy()
self.Destroy()
def on_frame_close(self, event):
# 最小化到托盘而不是关闭
self.Hide()
def on_left_down(self, event):
# 点击托盘图标时显示窗口
self.Show()
self.Restore()
def on_restore(self, event):
self.Show()
self.Restore()
def on_exit(self, event):
self.tray_icon.Destroy()
self.Destroy()
性能优化技巧
提高wxPython应用程序性能的一些技巧:
- 避免频繁的布局更新: 批量进行布局更改,然后调用Layout()
- 使用冻结和解冻: 在进行大量UI更新时使用Freeze()和Thaw()
- 虚拟列表控件: 对于大量数据,使用虚拟列表控件
- 缓存绘图操作: 对于复杂的自定义绘图,考虑使用缓存位图
- 合理的事件处理: 避免在事件处理函数中执行耗时操作