jsp 下拉框绑定事件_使用wxPython打造自己的印象笔记 (3)事件处理

1729f42404bf740cc200e9b60687f17c.png

本来打算介绍一下wxFormBuilder这个可视化设计工具的,但我觉得事件处理挺重要的,有必要说明一下。

在wxPython中,事件可以来自输入设备,例如鼠标、键盘,也可以来自于标准控件,例如按钮。 还有一些事件并不是来自于交互行为,例如 wx.TimerEvent,当然,我们也可以自定义事件。 但无论事件来自于哪里,wxPython都将其封装成一致的数据结构。具体来说, 每个事件都是由以下三部分组成:

  1. 事件类型。点击按钮和按下键盘就是不同的事件类型。
  2. 事件类。每个事件都关联一个对象,该对象继承自 wx.CommandEvent或者 wx.KeyEvent, 这两种事件类又继承自 wx.Event
  3. 事件来源。 wx.Event中会含有事件来源的对象,通过该对象可以准确区分出来源。

事件绑定

我们可以使用 wx.EvtHandler.Bind 来绑定事件和对应的处理方法,该方法签名为: Bind(self, event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)。 各参数的含义如下:

  • event: 指定事件类型,通常以 EVT_开头,例如 wx.EVT_BUTTON表示按钮点击事件, wx.EVT_CHOICE表示下拉框选中事件。
  • handler: 可以调用的事件处理方法,如果是 None会取消该事件绑定。
  • source: 事件来源对象,例如一个窗体里面有很多按钮,就可以通过该参数来区分不同的事件来源。
  • id: 通过id来区分事件来源。
  • id2: 如果希望将事件处理方法绑定到很多ID上,可以使用此参数。

先来看个简单的小例子。

import wx

class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title='事件绑定')
        self.count = 0
        self.btn_add_counter = wx.Button(self, label='点击了0次')
        self.btn_reset_counter = wx.Button(self, label='重置计数器')
        self.btn_show_counter = wx.Button(self, label='显示计数器')

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.AddStretchSpacer()
        self.sizer.Add(self.btn_add_counter, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.BOTTOM, border=10)
        self.sizer.Add(self.btn_reset_counter, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.BOTTOM, border=10)
        self.sizer.Add(self.btn_show_counter, flag=wx.ALIGN_CENTER_HORIZONTAL)
        self.sizer.AddStretchSpacer()
        self.SetSizer(self.sizer)

        self.Bind(wx.EVT_BUTTON, self._add_counter, self.btn_add_counter)
        self.Bind(wx.EVT_BUTTON, self._reset_counter, id=self.btn_reset_counter.Id)
        self.btn_show_counter.Bind(wx.EVT_BUTTON, self._show_counter)

    def _add_counter(self, e):
        print(e.Id == self.btn_add_counter.Id)
        self.count += 1
        self.btn_add_counter.SetLabel(f'点击了{self.count}次')

    def _reset_counter(self, e):
        print(e.Id == self.btn_reset_counter.Id)
        self.count = 0
        self.btn_add_counter.SetLabel('点击了0次')

    def _show_counter(self, e):
        print(e.Id == self.btn_show_counter.Id)
        wx.MessageBox(f'当前计数器:{self.count}', '提示')

app = wx.App()
MyFrame().Show()
app.MainLoop()

运行上面的代码,如图所示。

999f8bff17e01bbb8a241268291a01d9.png

我们通过设置一个垂直排列的BoxSizer将三个按钮放置在窗体的中心位置,并让按钮之间保留10像素的垂直间距。

点击第一个按钮,按钮的文字会相应变化。点击第二个按钮,count的值重置为0,第一个按钮的文字也会变化。点击第三个按钮,会弹窗提示当前count数值。并且控制台输出了很多的True,这表明传递给事件处理方法的对象e就是当前的事件来源对象。

三个按钮使用了三种方式来实现事件绑定。第一个按钮通过source参数来指定事件来源,第二个按钮通过id来指定事件来源,第三个按钮没有采用事件冒泡传播的思路,而是直接将事件处理方法绑定到了按钮上。

在熟悉了如何进行事件绑定之后,让我们来了解一下事件处理的流程。

事件处理流程

当事件发生时, wx.EvtHandler.ProcessEvent方法会自动调用第一个对应的事件处理方法。其查找过程如下。

  1. wx.AppConsole.FilterEvent调用,如果返回值不是-1,事件处理会立即结束。
  2. 如果 wx.EvtHandler.SetEvtHandlerEnabled禁用了事件处理方法,那么接下来的三步会跳过,事件处理方法将会在 第5步恢复。
  3. 如果事件来源自 wx.Window并且关联一个验证器,wx.Validator将有机会处理该事件。
  4. 通过 Bind方法动态绑定的事件处理方法,会开始分析。
  5. 事件表中包含了事件类型对应的事件处理方法,另外,事件来源的父类定义的事件处理方法,也会开始执行。
  6. 如果步骤1~4中找到了一个事件处理方法,那么事件处理方法将继续进行链式查找。通常事件处理方法只有一个,将执行到 下一步。
  7. 如果事件源是一个 wx.Window对象,并且事件允许冒泡传播,针对事件源的父类,事件处理将从步骤1开始再次进行。 如果事件源不是 wx.Window对象但还有一个事件处理方法,并且事件源的父类是一个Window对象,此事件将传递给父类。
  8. 如果事件依然没有处理, wx.App将处理此事件。

事件的传播机制

继承自 wx.CommandEvent的事件如果没有处理,默认会自动传播给父组件。其他事件类型也具备类似的传播特性, 本质上是通过 wx.Event.ShouldPropagate方法来检测是否能够传播。

如果 wx.CommandEvent类型的事件传播过程中如果发现对应的父组件是一个对话框,就会立即停止传播,如果父组件是 Frame,则会正常传播。如果不希望在当前窗体上进行事件传播,可以给窗体设置一个标志 wx.Window.SetExtraStyle (wx.WS_EX_BLOCK_EVENTS)。

只有继承自 wx.CommandEvent的事件才会向上传播。

通过使用wx.EvtHandler.SetNextHandlerwx.EvtHandler.SetPreviousHandler方法可以建立事件处理双向链表, 如图所示。

3b60b725b71ac6962837a29f1519e7fd.png

如果A.ProcessEvent调用了但并没有处理事件,B.ProcessEvent将会执行,以此类推。

自定义事件

除了使用系统预定义的事件类型,我们还可以借助wx.lib.newlib这个模块,来很方便的自定义事件,来看一个例子。

import wx
import random
import wx.lib.newevent

class GuessNumberPanel(wx.Panel):
    GuessResultEvent, EVT_GUESS_RESULT = wx.lib.newevent.NewCommandEvent()

    def __init__(self, parent):
        super().__init__(parent)
        self.number = random.randint(1, 100)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(wx.StaticText(self, label='猜一个1到100之间的整数'), flag=wx.ALIGN_CENTER_HORIZONTAL|wx.TOP, border=20)

        self.btn_reset = wx.Button(self, label='重新开始')
        self.sizer.Add(self.btn_reset, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.TOP, border=20)

        self.tc_guess = wx.TextCtrl(self)
        self.sizer.Add(self.tc_guess, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.TOP, border=20)

        self.btn_guess = wx.Button(self, label='试一试')
        self.sizer.Add(self.btn_guess, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.TOP|wx.BOTTOM, border=20)

        self.btn_guess.Bind(wx.EVT_BUTTON, self._check_result)
        self.btn_reset.Bind(wx.EVT_BUTTON, self._reset)

        self.SetSizer(self.sizer)
        self.SetBackgroundColour('#0099CC')

    def _reset(self, _):
        self.tc_guess.Clear()
        self.number = random.randint(1, 100)

    def _check_result(self, _):
        correct = False
        message = ''

        try:
            guessed_number = int(self.tc_guess.Value)
            if guessed_number < 1 or guessed_number > 100:
                message = '数字需要在1到100之间'
            elif guessed_number < self.number:
                message = '小了'
            elif guessed_number == self.number:
                message = '正确'
                correct = True
            else:
                message = '大了'
        except:
            message = '请输入数字'
        finally:
            event = self.GuessResultEvent(self.Id, is_correct=correct, message=message)
            wx.PostEvent(self.GetEventHandler(), event)

class GuessNumberFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title='猜数字')
        self.guess_panel = GuessNumberPanel(self)
        self.st_guess_result = wx.StaticText(self)

        self.sizer = wx.BoxSizer()
        self.sizer.Add(self.guess_panel, proportion=1, flag=wx.EXPAND)
        self.sizer.Add(self.st_guess_result, proportion=1, flag=wx.TOP|wx.LEFT, border=30)
        self.SetSizer(self.sizer)

        self.Bind(self.guess_panel.EVT_GUESS_RESULT, self._handle_result)

    def _handle_result(self, e):
        print(e.message)
        if e.is_correct:
            self.st_guess_result.SetLabel('猜对了!')
        else:
            self.st_guess_result.SetLabel(e.message)

app = wx.App()
GuessNumberFrame().Show()
app.MainLoop()

这是一个猜数字的小游戏,游戏的截图如下所示。

de8a9580e45a5b7ac3ddeb14ed325c68.png

我们来分析上面的代码。首先看GuessNumberFrame这个类,初始化的时候创建了一个GuessNumberPanel实例和一个StaticText实例,然后使用BoxSizer把两者横向排列成一行,通过proportion参数来设定各自占据一半的空间,Panel还通过wx.EXPAND实现了纵向扩展。

然后通过self.Bind(self.guess_panel.EVT_GUESS_RESULT, self._handle_result)来处理自定义事件。在_handle_result方法中可以看到,传进去的事件对象有message和is_correct两个属性,这是如何实现的呢?我们来看GuessNumberPanel这个类。

通过BoxSizer将四个控件垂直排列成一列,并通过border参数来设置间距,关于具体的布局方式可以参考前面的文章,这里就不再说明了。重点是看_check_result方法,我们提取文本框的输入值,和当前的随机数进行比较,将对应的结果放到一个自定义事件GuessResultEvent里面,这个事件在前面已经定义好了,这里只需要构造一个实例即可,所需要的参数有事件源的Id和一些自定义参数,这里我们添加了is_correct和message两个参数,GuessNumberFrame里面相对应的事件处理方法就可以提取到这两个参数了。

从以上的例子可以看到,自定义事件实际上有以下几步构成。

  1. 定义事件类型。通过wx.lib.newevent.NewCommandEvent()方法实现,如果不希望事件传播,可以使用wx.lib.newevent.NewEvent()方法。
  2. 发送事件。注意:如果是wx.lib.newevent.NewEvent()创建的,就不需要Id参数
event = self.GuessResultEvent(self.Id, is_correct=correct, message=message)
wx.PostEvent(self.GetEventHandler(), event)

3. 后面就是绑定事件了,这个上文已经提及过。

在熟悉了事件处理机制之后,接下来我们就可以使用可视化设计工具快速创建界面了,下一篇将详细介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值