wxPython学习笔记(三)

要理解事件,咱们须要知道哪些术语?

事件(event):在你的应用程序期间发生的事情,它要求有一个响应。程序员

事件对象(event object):在wxPython中,它具体表明一个事件,其中包括了事件的数据等属性。它是类wx.Event或其子类的实例,子类如wx.CommandEventwx.MouseEvent编程

事件类型(event type)wxPython分配给每一个事件对象的一个整数ID。事件类型给出了关于该事件自己更多的信息。例如,wx.MouseEvent的事件类型标识了该事件是一个鼠标单击仍是一个鼠标移动。app

事件源(event source):任何wxPython对象都能产生事件。例如按钮、菜单、列表框和任何别的窗口部件。框架

事件驱动(event-driven):一个程序结构,它的大部分时间花在等待或响应事件上。函数

事件队列(event queue):已发生的但未处理的事件的一个列表。工具

事件处理器(event handler):响应事件时所调用的函数或方法。也称做处理器函数或处理器方法。oop

事件绑定器(event binder):一个封装了特定窗口部件,特定事件类型和一个事件处理器wxPython对象。为了被调用,全部事件处理器必须用一个事件绑定器注册。布局

wx.EvtHandler:一个wxPython类,它容许它的实例在一个特定类型,一个事件源,和一个事件处理器之间建立绑定。注意,这个类与先前定义的事件处理函数或方法不是同一个东西。学习

什么是事件驱动编程?

事件驱动程序结构的主要特色:spa

一、在初始化设置以后,程序的大部分时间花在了一个空闭的循环之中。进入这个循环就标志着程序与用户交互的部分的开始,退出这个循环就标志结束。在wxPython中,这个循环的方法是:wx.App.MainLoop(),而且在你的脚本中显式地被调用。当全部的顶级窗口关闭时,主循环退出。

二、程序包含了对应于发生在程序环境中的事情的事件。事件一般由用户的行为触发,可是也能够由系统的行为或程序中其余任意的代码。在wxPython中,全部的事件都是类wx.Event或其子类的一个实例。每一个事件都有一个事件类型属性,它使得不一样的事件可以被辨别。例如,鼠标释放和鼠示按下事件都被认为是同一个类的实例,但有不一样的事件类型。

三、做为这个空闭的循环部分,程序按期检查是否有任何请求响应事情发生。有两种机制使得事件驱动系统能够获得有关事件的通知。最常被wxPython使用的方法是,把事件传送到一个中心队列,由该队列触发相应事件的处理。另外一种方法是使用轮询的方法,全部可能引起事件的事件主被主过程按期查询并询问是否有没有处理的事件。

四、当事件发生时,基于事件的系统试着肯定相关代码来处理该事件,若是有,相关代码被执行。在wxPython中,原系统事件被转换为wx.Event实例,而后使用wx.EvtHandler.ProcessEvent()方法将事件分派给适当的处理器代码。下图呈现了这个过程:

事件机制的组成部分是事件绑定器对象和事件处理器。事件绑定器是一个预约义的wxPython对象。每一个事件都有各自的事件绑定器。事件处理器是一个函数或方法,它要求一个wxPython事件实例做为参数。当用户触发了适当的事件时,一个事件处理器被调用。

编写事件处理器

在你的wxPython代码中,事件和事件处理器是基于相关的窗口部件的。例如,一个按钮被单击被分派给一个基于该按钮的专用的事件处理器。为了要把一个来自特定窗口部件的事件绑定到一个特定的处理器方法,你要使用一个绑定器对象来管理这个链接。例如:

self.Bind(wx.EVT_BUTTON, self.OnClick, aButton)

上例使用了预约义的事件绑定器对象wx.EVT_BUTTON来将aButton对象上的按钮单击事件与方法self.OnClick相关联起来。这个Bind()方法wx.EvtHandler的一个方法,wx.EvtHandler是全部可显示对象的父类。所以上例代码行能够被放置在任何显示类。

设计事件驱动程序

对于事件驱动程序的设计,因为没有假设事件什么时候发生,因此程序员将大量的控制交给了用户。你的wxPython程序中的大多数代码经过用户或系统的行为被直接或间接地执行。例如在用户选择了一个菜单项、或按下一个工具栏按钮、或按下了特定的按键组合后,你的程序中有关保存工做的代码被执行了。

另外一方面,事件驱动体系一般是分散性的。响应一个窗口部件事件的代码一般不是定义在该部件的定义中的。例如,响应一个按钮单击事件的代码没必要是该按钮定义的一部分,而能够存在在该按钮所附的框架中或其它地方。

事件触发

wx.CloseEvent:当一个框架关闭时触发。这个事件的类型分为一个一般的框架关闭和一个系统关闭事件。 wx.CommandEvent:与窗口部件的简单的各类交互都将触发这个事件,如按钮单击、菜单项选择、单选按钮选择。这些交互有它各自的事件类型。许多更复杂的窗口部件,如列表等则定义wx.CommandEvent的子类。事件处理系统对待命令事件与其它事件不一样。 wx.KeyEvent:按键事件。这个事件的类型分按下按键、释放按键、整个按键动做。 wx.MouseEvent:鼠标事件。这个事件的类型分鼠标移动和鼠标敲击。对于哪一个鼠标按钮被敲击和是单击仍是双击都有各自的事件类型。 wx.PaintEvent:当窗口的内容须要被重画时触发。wx.SizeEvent:当窗口的大小或其布局改变时触发。 wx.TimerEvent:能够由类wx.Timer类建立,它是按期的事件。

如何将事件绑定处处理器?

事件绑定器被用于将一个wxPython窗口部件与一个事件对象和一个处理器函数链接起来。这个链接使得wxPython系统可以经过执行处理器函数中的代码来响应相应窗口部件上的事件。 

使用wx.EvtHandler的方法工做

常用的wx.EvtHandler的方法是Bind(),它建立事件绑定。该方法的用法以下:

Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)

Bind()函数将一个事件和一个对象与一个事件处理器函数关联起来。参数event是必选的,参数handler也是必选的,它是一个可调用的Python对象,一般是一个被绑定的方法或函数。参数handler能够是None,这种状况下,事件没有关联的处理器。参数source是产生该事件的源窗口部件,这个参数在触发事件的窗口部件与用做事件处理器的窗口部件不相同时使用。一般状况下这个参数使用默认值None,这是由于你通常使用一个定制的wx.Frame类做为处理器,而且绑定来自于包含在该框架内的窗口部件的事件。父窗口的__init__是一个用于声明事件绑定的方便的位置。可是若是父窗口包含了多个按钮敲击事件源(好比OK按钮和Cancel按钮),那么就要指定source参数以便wxPython区分它们(?)。下面是该方法的一个例子:

self.Bind(wx.EVT_BUTTON, self.OnClick, button)

演示了使用参数source和不使用参数source的方法:

def __init__(self, parent, id): 
    wx.Frame.__init__(self, parent, id, 'Frame With Button',
            size=(300, 100)) 
    panel = wx.Panel(self, -1)                              
    button = wx.Button(panel, -1, "Close", pos=(130, 15),   
            size=(40, 40)) 
    self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) #1 绑定框架关闭事件   
    self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) #2 绑定按钮事件  

    def OnCloseMe(self, event): 
        self.Close(True) 
    def OnCloseWindow(self, event):
        self.Destroy() 

说明:

#1 这行绑定框架关闭事件到self.OnCloseWindow方法。因为这个事件经过该框架触发且用于帧,因此不须要传递一个source参数。

#2 这行未来自按钮对象的按钮敲击事件绑定到self.OnCloseMe方法。这样作是为了让wxPython可以区分在这个框架中该按钮和其它按钮所产生的事件。

Bind()方法中的参数idid2使用ID号指定了事件的源。通常状况下这不必,由于事件源的ID号能够从参数source中提取。可是某些时候直接使用ID是合理的。例如,若是你在使用一个对话框的ID号,这比使用窗口部件更容易。若是你同时使用了参数idid2,你就可以以窗口部件的ID号形式将这两个ID号之间范围的窗口部件绑定到事件。这仅适用于窗口部件的ID号是连续的。

wxPython是如何处理事件的?

代码以下:

import wx 

class MouseEventFrame(wx.Frame): 
    
    def __init__(self, parent, id): 
        wx.Frame.__init__(self, parent, id, 'Frame With Button',
                size=(300, 100)) 
        self.panel = wx.Panel(self)                             
        self.button = wx.Button(self.panel,
            label="Not Over", pos=(100, 15)) 
        self.Bind(wx.EVT_BUTTON, self.OnButtonClick,   
            self.button)    #1 绑定按钮事件                 
        self.button.Bind(wx.EVT_ENTER_WINDOW,   
            self.OnEnterWindow)     #2 绑定鼠标位于其上事件          
        self.button.Bind(wx.EVT_LEAVE_WINDOW, 
            self.OnLeaveWindow)     #3 绑定鼠标离开事件
 
    def OnButtonClick(self, event): 
        self.panel.SetBackgroundColour('Green') 
        self.panel.Refresh() 
        
    def OnEnterWindow(self, event): 
        self.button.SetLabel("Over Me!") 
        event.Skip() 
        
    def OnLeaveWindow(self, event): 
        self.button.SetLabel("Not Over") 
        event.Skip() 

if __name__ == '__main__': 
    app = wx.App() 
    frame = MouseEventFrame(parent=None, id=-1)
    frame.Show() 
    app.MainLoop()

 

说明

MouseEventFrame包含了一个位于中间的按钮。在其上敲击鼠标将致使框架的背景色改变为绿色。#1绑定了鼠标敲击事件。当鼠标指针位于这个按钮上时,按钮上的标签将改变,这用#2绑定。当鼠标离开这个按钮时,标签变回原样,这用#3绑定。

经过观察上面的鼠标事件例子,咱们引出了在wxPython中的事件处理的一些问题。#1中,按钮事件由附着在框架上的按钮触发,那么wxPython怎么知道在框架对象中查找绑定而不是在按钮对象上呢?在#2和#3中,鼠标的进入和离开事件被绑定到了按钮,为何这两个事件不能被绑到框架上呢。这些问题将经过检查wxPython用来决定如何响应事件的过程来获得回答。

Skip():在wxPython中,若是一个动做会触发多个事件,那么应该使用Skip方法来保证每一个都被处理到。其实为了保证不遗漏,在每一个事件处理的方法中都调用Skip方法应该是一种良好的习惯。

理解事件处理过程

第一步,建立事件

第二步,肯定事件对象是否被容许处理事件

第三步 定位绑定器对象

第四步 决定是否继续处理

第五步 决定是否展开 

使用Skip()方法

事件的第一个处理器函数被发现并执行完后,该事件处理将终止,除非在处理器返回以前调用了该事件的Skip()方法。调用Skip()方法容许另外被绑定的处理器被搜索,在某些状况下,你想继续处理事件,以便原窗口部件的默认行为和你定制的处理能被执行。

# 同时响应鼠标按下和按钮敲击
import wx

class DoubleEventFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, 'Frame With Button',
                          size=(300, 100))
        self.panel = wx.Panel(self, -1)
        self.button = wx.Button(self.panel, -1, "Click Me", pos=(100, 15))
        self.Bind(wx.EVT_BUTTON, self.OnButtonClick,
                  self.button)  # 1 绑定按钮敲击事件
        self.button.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)  # 2 绑定鼠标左键按下事件

    def OnButtonClick(self, event):
        self.panel.SetBackgroundColour('Green')
        self.panel.Refresh()

    def OnMouseDown(self, event):
        self.button.SetLabel("Again!")
        event.Skip()  # 3 确保继续处理


if __name__ == '__main__':
    app = wx.App()
    frame = DoubleEventFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop() 

#1 这行绑定按钮敲击事件OnButtonClick()处理器,这个处理器改变框架的背景色。

#2 这行绑定鼠标左键按下事件到OnMouseDown()处理器,这个处理器改变按钮的标签文本。因为鼠标左键按下事件不是命令事件,因此它必须被绑定到按钮(self.button.Bind)而非框架(self.Bind)。

分析:鼠标按下的时候没有变色,文本框内容改变,说明调用了OnMouseDown方法;鼠标按下再释放的时候,变色,说明调用了OnButtonClick方法。若是在OnMouseDown方法中没有Skip(),则事件会被终止,即不会变色。

当用户在按钮上敲击鼠标时,经过直接与底层操做系统交互,鼠标左键按下事件首先被产生。一般状况下,鼠标左键按下事件改变按钮的状态,随着鼠标左键的释放,产生了wx.EVT_BUTTON敲击事件。因为行#3的Skip()语句,DoubleEventFrame维持处理。没有Skip()语句,事件处理规则发如今#2建立的绑定,而在按钮能产生wx.EVT_BUTTON事件以前中止。因为Skip()的调用,事件处理照常继续,而且按钮敲击被建立。

记住,当绑定低级事件时如鼠标按下或释放,wxPython指望捕获这些低级事件以便生成进一步的事件,为了进一步的事件处理,你必须调用Skip()方法,不然进一步的事件处理将被阻止

在应用程序对象中还包含哪些其它的属性?

未学习

如何建立本身的事件?

建立自定义事件的步骤

一、定义一个新的事件类,它是wxPythonwx.PyEvent类的子类。若是你想这个事件被做为命令事件,你能够建立wx.PyCommandEvent的子类。像许多wxPython中的覆盖同样,一个类的py版本使得wxWidget系统明白用Python写的覆盖C++方法的方法。

二、建立一个事件类型和一个绑定器对象去绑定该事件到特定的对象。

三、添加可以建造这个新事件实例的代码,而且使用ProcessEvent()方法将这个实例引入事件处理系统。一旦该事件被建立,你就能够像使用其它的wxPython事件同样建立绑定和处理器方法。

import wx


class TwoButtonEvent(wx.PyCommandEvent):  # 1 定义事件
    def __init__(self, evtType, id):
        wx.PyCommandEvent.__init__(self, evtType, id)
        self.clickCount = 0

    def GetClickCount(self):
        return self.clickCount

    def SetClickCount(self, count):
        self.clickCount = count


myEVT_TWO_BUTTON = wx.NewEventType()  # 2 建立一个事件类型
EVT_TWO_BUTTON = wx.PyEventBinder(myEVT_TWO_BUTTON, 1)  # 3 建立一个绑定器对象


class TwoButtonPanel(wx.Panel):
    def __init__(self, parent, id=-1, leftText="Left",
                 rightText="Right"):
        wx.Panel.__init__(self, parent, id)
        self.leftButton = wx.Button(self, label=leftText)
        self.rightButton = wx.Button(self, label=rightText,
                                     pos=(100, 0))
        self.leftClick = False
        self.rightClick = False
        self.clickCount = 0
        # 4 下面两行绑定更低级的事件
        self.leftButton.Bind(wx.EVT_LEFT_DOWN, self.OnLeftClick)
        self.rightButton.Bind(wx.EVT_LEFT_DOWN, self.OnRightClick)

    def OnLeftClick(self, event):
        self.leftClick = True
        self.OnClick()
        event.Skip()  # 5 继续处理

    def OnRightClick(self, event):
        self.rightClick = True
        self.OnClick()
        event.Skip()  # 6 继续处理

    def OnClick(self):
        self.clickCount += 1
        if self.leftClick and self.rightClick:
            self.leftClick = False
            self.rightClick = False
            evt = TwoButtonEvent(myEVT_TWO_BUTTON, self.GetId())  # 7 建立自定义事件
            evt.SetClickCount(self.clickCount)  # 添加数据到事件
            self.GetEventHandler().ProcessEvent(evt)  # 8 处理事件


class CustomEventFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, 'Click Count: 0',
                          size=(300, 100))
        panel = TwoButtonPanel(self)
        self.Bind(EVT_TWO_BUTTON, self.OnTwoClick, panel)  # 9 绑定自定义事件

    def OnTwoClick(self, event):  # 10 定义一个事件处理器函数
        self.SetTitle("Click Count: %s" % event.GetClickCount())


if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = CustomEventFrame(parent=None, id=-1)
    frame.Show()
    app.MainLoop() 

说明

#1 这个关于事件类的构造器声明为wx.PyCommandEvent的一个子类。 wx.PyEventwx.PyCommandEventwxPython特定的结构,你能够用来建立新的事件类而且能够把C++类和你的Python代码链接起来。若是你试图直接使用wx.Event,那么在事件处理期间wxPython不能明白你的子类的新方法,由于C++事件处理不了解该Python子类。若是你wx.PyEvent,一个对该Python实例的引用被保存,而且之后被直接传递给事件处理器,使得该Python代码能被使用。

#2 全局函数wx.NewEventType()的做用相似于wx.NewId();它返回一个惟一的事件类型ID。这个惟一的值标识了一个应用于事件处理系统的事件类型。

#3 这个绑定器对象的建立使用了这个新事件类型做为一个参数。这第二个参数的取值位于[0,2]之间,它表明wxId标识号,该标识号用于wx.EvtHandler.Bind()方法去肯定哪一个对象是事件的源。

#4 为了建立这个新的更高级的命令事件,程序必需响应特定的用户事件,例如,在每一个按钮对象上的鼠标左键按下。依据哪一个按钮被敲击,该事件被绑定到OnLeftClick()OnRightClick()方法。处理器设置了布尔值,以代表按键是否被敲击。

#5 #6 Skip()的调用容许在该事件处理完成后的进一步处理。在这里,这个新的事件不须要skip调用;它在事件处理器完成以前被分派了(self.OnClick())。可是全部的鼠标左键按下事件须要调用Skip(),以便处理器不把最后的按钮敲击挂起。这个程序没有处理按钮敲击事件,可是因为使用了Skip()wxPython在敲击期间使用按钮敲击事件来正确地绘制按钮。若是被挂起了,用户将不会获得来自按钮按下的反馈。

#7 若是两个按钮都被敲击了,该代码建立这个新事件的一个实例。事件类型和两个按钮的ID做为构造器的参数。一般,一个事件类能够有多个事件类型,尽管本例中不是这样。

#8 ProcessEvent()的调用将这个新事件引入到事件处理系统中。GetEventHandler()调用返回wx.EvtHandler的一个实例。大多数状况下,返回的实例是窗口部件对象自己,可是若是其它的wx.EvtHandler()方法已经被压入了事件处理器堆栈,那么返回的将是堆栈项的项目。

#9 该自定义的事件的绑定如同其它事件同样,在这里使用#3所建立的绑定器。

#10 这个例子的事件处理器函数改变窗口的标题以显示敲击数。

总结

一、wxPython应用程序使用基于事件的控制流。应用程序的大部分时间花费在一个主循环中,等待事件并分派它们到适当的处理器函数。

二、全部的wxPython事件是wx.Event类的子类。低级的事件,如鼠标敲击,被用来创建高级的事件,如按钮敲击或菜单项选择。这些由wxPython窗口部件引发的高级事件是类wx.CommandEvent的子类。大多的事件类经过一个事件类型字段被进一步分类,事件类型字段区分事件

三、为了捕获事件和函数之间的关联,wxPython使用类wx.PyEventBinder的实例。类wx.PyEventBinder有许多预约义的实例,每一个都对应于一个特定的事件类型。每一个wxPython窗口部件都是类wx.EvtHandler的子类。类wx.EvtHandler有一个方法Bind(),它一般在初始化时被调用,所带参数是一个事件绑定器实例和一个处理器函数。根据事件的类型,别的wxPython对象的ID可能也须要被传递给Bind()调用。

四、事件一般被发送给产生它们的对象,以搜索一个绑定对象,这个绑定对象绑定事件到一个处理器函数。若是事件是命令事件,这个事件沿容器级向上传递直到一个窗口部件被发现有一个针对该事件类型的处理器。一旦一个事件处理器被发现,对于该事件的处理就中止,除非这个处理器调用了该事件的Skip()方法。你能够容许多个处理器去响应一个事件,或去核查该事件的全部默认行为。主循环的某些方面可使用wx.App的方法来控制。

五、在wxPython中能够建立自定义事件,并做为定制(自定义)的窗口部件的行为的一部分。自定义的事件是类wx.PyEvent的子类,自定义的命令事件是类wx.PyCommandEvent的子类。为了建立一个自定义事件,新的类必须被定义,而且关于每一个事件类型(这些事件类型被这个新类所管理)的绑定器必须被建立。最后,这个事件必须在系统的某处被生成,这经过经由ProcessEvent()方法传递一个新的实例给事件处理器系统来实现。

相关文章
相关标签/搜索