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()
  • 虚拟列表控件: 对于大量数据,使用虚拟列表控件
  • 缓存绘图操作: 对于复杂的自定义绘图,考虑使用缓存位图
  • 合理的事件处理: 避免在事件处理函数中执行耗时操作