wxPython事件处理

事件处理是GUI编程的核心概念。在wxPython中,当用户与界面交互时(如点击按钮、输入文本等),会触发相应的事件。通过事件处理机制,我们可以响应这些用户操作并执行相应的逻辑。

事件处理基础

wxPython使用事件驱动模型。当发生特定事件时,系统会生成相应的事件对象,并将其传递给注册了该事件处理函数的对象。

事件绑定

在wxPython中,使用Bind()方法将事件与处理函数关联起来:

Python
# 基本事件绑定语法
control.Bind(event_type, handler_function)

# 示例:按钮点击事件
button = wx.Button(panel, label="点击我")
button.Bind(wx.EVT_BUTTON, self.on_button_click)

def on_button_click(self, event):
    print("按钮被点击了!")

常用事件类型

按钮事件

按钮点击是最常见的事件之一:

Python
import wx

class ButtonEventFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="按钮事件示例")
        panel = wx.Panel(self)
        
        # 创建按钮
        button1 = wx.Button(panel, label="普通按钮")
        button2 = wx.Button(panel, label="警告按钮")
        
        # 绑定事件
        button1.Bind(wx.EVT_BUTTON, self.on_normal_click)
        button2.Bind(wx.EVT_BUTTON, self.on_warning_click)
        
        # 布局
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(button1, 0, wx.ALL, 10)
        sizer.Add(button2, 0, wx.ALL, 10)
        panel.SetSizer(sizer)
        
    def on_normal_click(self, event):
        wx.MessageBox("普通按钮被点击!", "信息", wx.OK | wx.ICON_INFORMATION)
        
    def on_warning_click(self, event):
        result = wx.MessageBox("确定要执行此操作吗?", "确认", 
                              wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
        if result == wx.YES:
            wx.MessageBox("操作已执行!", "完成", wx.OK | wx.ICON_INFORMATION)

文本事件

文本控件的事件包括文本变化、回车键按下等:

Python
import wx

class TextEventFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="文本事件示例")
        panel = wx.Panel(self)
        
        # 创建文本控件
        self.text_ctrl = wx.TextCtrl(panel, value="在此输入文本")
        self.result_text = wx.StaticText(panel, label="字符数: 0")
        
        # 绑定事件
        self.text_ctrl.Bind(wx.EVT_TEXT, self.on_text_change)
        self.text_ctrl.Bind(wx.EVT_TEXT_ENTER, self.on_text_enter)
        
        # 布局
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 10)
        sizer.Add(self.result_text, 0, wx.ALL, 10)
        panel.SetSizer(sizer)
        
    def on_text_change(self, event):
        text = self.text_ctrl.GetValue()
        self.result_text.SetLabel(f"字符数: {len(text)}")
        
    def on_text_enter(self, event):
        text = self.text_ctrl.GetValue()
        wx.MessageBox(f"您输入了: {text}", "回车事件", wx.OK | wx.ICON_INFORMATION)

鼠标事件

鼠标事件包括点击、双击、移动等:

Python
import wx

class MouseEventFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="鼠标事件示例")
        panel = wx.Panel(self)
        
        # 创建一个面板用于接收鼠标事件
        self.canvas = wx.Panel(panel, size=(300, 200))
        self.canvas.SetBackgroundColour(wx.Colour(240, 240, 240))
        self.info_text = wx.StaticText(panel, label="鼠标信息将显示在这里")
        
        # 绑定鼠标事件
        self.canvas.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
        self.canvas.Bind(wx.EVT_LEFT_UP, self.on_left_up)
        self.canvas.Bind(wx.EVT_MOTION, self.on_mouse_move)
        self.canvas.Bind(wx.EVT_RIGHT_DOWN, self.on_right_down)
        
        # 布局
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.canvas, 0, wx.ALL, 10)
        sizer.Add(self.info_text, 0, wx.ALL, 10)
        panel.SetSizer(sizer)
        
    def on_left_down(self, event):
        pos = event.GetPosition()
        self.info_text.SetLabel(f"左键按下: ({pos.x}, {pos.y})")
        
    def on_left_up(self, event):
        pos = event.GetPosition()
        self.info_text.SetLabel(f"左键释放: ({pos.x}, {pos.y})")
        
    def on_mouse_move(self, event):
        if event.Dragging():
            pos = event.GetPosition()
            self.info_text.SetLabel(f"鼠标拖拽: ({pos.x}, {pos.y})")
            
    def on_right_down(self, event):
        pos = event.GetPosition()
        self.info_text.SetLabel(f"右键按下: ({pos.x}, {pos.y})")
        menu = wx.Menu()
        item1 = menu.Append(wx.ID_ANY, "菜单项1")
        item2 = menu.Append(wx.ID_ANY, "菜单项2")
        
        # 绑定菜单事件
        self.Bind(wx.EVT_MENU, self.on_menu_item1, item1)
        self.Bind(wx.EVT_MENU, self.on_menu_item2, item2)
        
        self.PopupMenu(menu)
        menu.Destroy()

键盘事件

键盘事件包括按键按下、释放等:

Python
import wx

class KeyEventFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="键盘事件示例")
        panel = wx.Panel(self)
        
        self.info_text = wx.StaticText(panel, label="按键信息将显示在这里\n按ESC键可以清空文本")
        self.text_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE, size=(300, 150))
        
        # 绑定键盘事件
        self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
        self.text_ctrl.Bind(wx.EVT_CHAR, self.on_char)
        
        # 布局
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.info_text, 0, wx.ALL, 10)
        sizer.Add(self.text_ctrl, 1, wx.ALL | wx.EXPAND, 10)
        panel.SetSizer(sizer)
        
        # 设置焦点
        self.text_ctrl.SetFocus()
        
    def on_key_down(self, event):
        keycode = event.GetKeyCode()
        if keycode == wx.WXK_ESCAPE:
            self.text_ctrl.Clear()
            self.info_text.SetLabel("文本已清空")
        else:
            keyname = ""
            if keycode < 256:
                keyname = chr(keycode)
            self.info_text.SetLabel(f"按键按下: {keyname} (代码: {keycode})")
        event.Skip()  # 继续处理事件
        
    def on_char(self, event):
        keycode = event.GetKeyCode()
        if keycode < 256:
            char = chr(keycode)
            self.info_text.SetLabel(f"字符输入: {char}")

事件对象

每个事件处理函数都会接收一个事件对象作为参数,该对象包含了事件的相关信息:

Python
def on_button_click(self, event):
    # 获取事件对象
    evt_obj = event.GetEventObject()
    
    # 获取事件类型
    evt_type = event.GetEventType()
    
    # 获取事件时间戳
    timestamp = event.GetTimestamp()
    
    # 获取事件ID
    evt_id = event.GetId()
    
    print(f"事件对象: {evt_obj}")
    print(f"事件类型: {evt_type}")
    print(f"时间戳: {timestamp}")
    print(f"事件ID: {evt_id}")

事件传播和处理

在wxPython中,事件可以向上级控件传播:

Python
import wx

class EventPropagationFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="事件传播示例")
        panel = wx.Panel(self)
        
        # 创建嵌套的控件
        outer_panel = wx.Panel(panel)
        outer_panel.SetBackgroundColour(wx.Colour(200, 200, 255))
        
        inner_button = wx.Button(outer_panel, label="点击我")
        
        # 绑定事件
        inner_button.Bind(wx.EVT_BUTTON, self.on_button_click)
        outer_panel.Bind(wx.EVT_BUTTON, self.on_panel_click)
        panel.Bind(wx.EVT_BUTTON, self.on_frame_click)
        
        # 布局
        inner_sizer = wx.BoxSizer(wx.VERTICAL)
        inner_sizer.Add(inner_button, 0, wx.ALL | wx.CENTER, 20)
        outer_panel.SetSizer(inner_sizer)
        
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add(outer_panel, 1, wx.ALL | wx.EXPAND, 20)
        panel.SetSizer(main_sizer)
        
    def on_button_click(self, event):
        wx.MessageBox("按钮点击事件", "事件", wx.OK | wx.ICON_INFORMATION)
        # event.Skip()  # 取消注释这行可以让事件继续传播
        
    def on_panel_click(self, event):
        wx.MessageBox("面板点击事件", "事件", wx.OK | wx.ICON_INFORMATION)
        # event.Skip()  # 取消注释这行可以让事件继续传播
        
    def on_frame_click(self, event):
        wx.MessageBox("框架点击事件", "事件", wx.OK | wx.ICON_INFORMATION)

自定义事件

除了使用内置事件,还可以创建自定义事件:

Python
import wx
import threading
import time

# 定义自定义事件类型
wx.CustomEvent = wx.NewEventType()
EVT_CUSTOM = wx.PyEventBinder(wx.CustomEvent, 1)

class CustomEvent(wx.PyCommandEvent):
    def __init__(self, event_type, id):
        wx.PyCommandEvent.__init__(self, event_type, id)
        self.data = None
        
    def SetData(self, data):
        self.data = data
        
    def GetData(self):
        return self.data

class CustomEventFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="自定义事件示例")
        panel = wx.Panel(self)
        
        self.info_text = wx.StaticText(panel, label="等待自定义事件...")
        self.button = wx.Button(panel, label="启动后台任务")
        
        self.button.Bind(wx.EVT_BUTTON, self.on_start_task)
        self.Bind(EVT_CUSTOM, self.on_custom_event)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.info_text, 0, wx.ALL, 10)
        sizer.Add(self.button, 0, wx.ALL, 10)
        panel.SetSizer(sizer)
        
    def on_start_task(self, event):
        self.button.Enable(False)
        self.info_text.SetLabel("后台任务运行中...")
        
        # 启动后台线程
        thread = threading.Thread(target=self.background_task)
        thread.daemon = True
        thread.start()
        
    def background_task(self):
        # 模拟耗时任务
        time.sleep(3)
        
        # 发送自定义事件
        evt = CustomEvent(wx.CustomEvent, self.GetId())
        evt.SetData("任务完成!")
        wx.PostEvent(self, evt)
        
    def on_custom_event(self, event):
        self.button.Enable(True)
        self.info_text.SetLabel(event.GetData())