wxPython之Event事件(Markdown版本)

  事件是每个GUI应用程序必不可少的部分。所有的GUI应用程序都是事件驱动。应用程序响应在其生命周期里生成的不同类型的事件。事件主要由应用程序的用户生成。但是事件也有其他方式生成,比如网络连接,窗口管理器,定时器。所以当调用MainLoop()方法,应用程序等待事件生成。退出应用程序时,MainLoop()方法终止。

定义

  事件(Event)是一个来自底层框架,特别是GUI工具包的应用层信息。事件循环是一种编程结构,用来在一个程序里等待和调度事件或消息。事件循环不断寻找事件去处理。调度器是一个过程,将事件映射给事件处理器(Event Handlders)。事件处理器是一群响应事件的方法。
  事件对象是与事件相关联的对象。它通常是一个窗口。事件类型(Event Type)是已经发生的独特事件。事件绑定是一个对象,将一个事件类型和一个事件处理器绑在一起。

简单的事件例子

  下面的部分将描述一个简单的事件。我们以一个move事件为例。当我们移动窗口到一个新位置,产生一个move事件。该事件类型是wx.MoveEvent。事件的绑定是wx.EVT_MOVE。

# -*- coding: utf-8 -*-
import wx

class Example(wx.Frame):
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        self.InitUI()

    def InitUI(self):
        wx.StaticText(self, label='x:', pos=(10,10))
        wx.StaticText(self, label='y:', pos=(10,30))
        self.st1 = wx.StaticText(self, label='', pos=(30, 10))
        self.st2 = wx.StaticText(self, label='', pos=(30, 30))
        self.Bind(wx.EVT_MOVE, self.OnMove)
        self.SetSize((250, 180))
        self.SetTitle('Move event')
        self.Centre()
        self.Show(True) 

    def OnMove(self, event):
        x, y = event.GetPosition()
        self.st1.SetLabel(str(x))
        self.st2.SetLabel(str(y))


def main():
    ex = wx.App()
    Example(None)
    ex.MainLoop() 

if __name__ == '__main__':
    main()

这个例子显示了窗口的当前位置。

self.Bind(wx.EVT_MOVE, self.OnMove)

这里我们把wx.EVT_MOVE事件binder绑定到OnMove()方法。

def OnMove(self, event):
     x, y = event.GetPosition()
     self.st1.SetLabel(str(x))
     self.st2.SetLabel(str(y))

OnMove()方法的event 参数是一个具体到某一特定事件类型的对象。在我们例子里,它是类wx.MoveEvent的实例。这个对象持有关于这个事件的信息。比如事件对象或者窗口的位置。在我们的例子里,事件对象是wx.Frame的部件。我们通过调用事件的GetPosition()方法来知道当前位置。
这里写图片描述

事件绑定

绑定事件在wxPython里是很容易的。分为三个步骤:

  • 定义event binder 名字:wx.EVT_SIZE,wx.EVT_CLOSE等
  • 创建事件处理器,即方法。当一个事件产生时,该方法被调用。
  • 把事件绑定到事件处理器上。
    对于wxPython,可以通过调用Bind()方法绑定一个事件。这个方法有如下形参:
Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)

其中,event是 EVT_*对象的一种,它具体了事件的类型。handler是被调用的对象,也就是程序员绑定给事件的方法。source是用来区分来自不同部件的相同事件类型。id是用来区分多重按钮、菜单各项等。id2是用于可以绑定一个处理器给一系列id,比如EVT_MENU_RANGE。
  方法Bind()定义在EvtHandler类里。wx.Window继承于EVTHandler。而在wxPython里,wx.Window是大多数部件类的基类。同时也有相反的过程。如果你想把一个方法与事件解除绑定,可以调用Unbind()方法。它和Bind()有相同的参数。

否决事件

有时候我们需要停止处理某个事件,可以调用Veto()方法来实现。

class Example(wx.Frame):        
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        self.InitUI()

    def InitUI(self):
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        self.SetTitle('Event veto')
        self.Centre()
        self.Show(True)

    def OnCloseWindow(self, event):
        dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
            wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)  
        ret = dial.ShowModal()
        if ret == wx.ID_YES:
            self.Destroy()
        else:
            event.Veto()

def main():
    ex = wx.App()
    Example(None)
    ex.MainLoop() 

if __name__ == '__main__':
    main()

这里写图片描述
  在上面的例子里,我们产生一个wx.CloseEvent。当点击标题栏上的X按钮、按快捷键Alt+F4或者从系统菜单里选择关闭时,这个事件被调用。在很多应用程序里,如果我们做了一些变化,我们想阻止意外关闭窗口。为了实现这个,我们必须绑定wx.EVT_CLOSE event binder。

dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
ret = dial.ShowModal()

在处理关闭事件的处理过程中,我们弹出一个消息框。

if ret == wx.ID_YES:
      self.Destroy()
else:
      event.Veto()

依赖对话框的返回值,我们销毁这个窗口,或者否决关闭事件。我们必须调用Destroy()方法来关闭窗口。通过调用Close()方法,我们结束不断循环。

事件传播

有两种类型事件:基本事件和命令事件。它们不同于传播方式。事件传播是指事件从子部件传到父部件和父窗口的父窗口等。基本事件不传播,命令事件传播。比如wx.CloseEvent是一个基本事件。它没有传到父窗口的一样。默认情况下, 这种事件在一个事件处理器里就停止传播。如果想继续传播,我们必须调用Skip()方法。

import wx

class MyPanel(wx.Panel):
    def __init__(self, *args, **kw):
        super(MyPanel, self).__init__(*args, **kw) 
        self.Bind(wx.EVT_BUTTON, self.OnButtonClicked)

    def OnButtonClicked(self, event):
        print 'event reached panel class'
        event.Skip()

class MyButton(wx.Button):
    def __init__(self, *args, **kw):
        super(MyButton, self).__init__(*args, **kw) 
        self.Bind(wx.EVT_BUTTON, self.OnButtonClicked)

    def OnButtonClicked(self, event):
        print 'event reached button class'
        event.Skip()

class Example(wx.Frame):
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        self.InitUI()

    def InitUI(self):
        mpnl = MyPanel(self)
        MyButton(mpnl, label='Ok', pos=(15, 15))
        self.Bind(wx.EVT_BUTTON, self.OnButtonClicked)
        self.SetTitle('Propagate event')
        self.Centre()
        self.Show(True) 

    def OnButtonClicked(self, event):
        print 'event reached frame class'
        event.Skip()

def main():
    ex = wx.App()
    Example(None)
    ex.MainLoop() 

if __name__ == '__main__':
    main() 

这里写图片描述
在上面的例子,在panel上有一个按钮。这个panel被放置在一个窗口部件上。我们为所有的部件都定义了一个处理器。

def OnButtonClicked(self, event):
        print 'event reached panel class'
        event.Skip()

在我们定制的按钮类处理按钮点击事件。Skip()事件把事件进一步传给panel类。
这里写图片描述
上面是运行结果。当我们点击按钮时,事件从按钮传到了panel,在传到frame。可以尝试删掉一些Skip()方法,会发生什么。

窗口ID

窗口ID是整数,在事件系统里唯一决定窗口的标识符。有三种方法创建窗口ID。

  • 系统自动创建
  • 使用标准ID
  • 创建自己的ID
    每个部件类都有一个id参数。这个数字在事件系统里是独一无二的。如果我们处理多重部件,必须区分它们。
wx.Button(parent, -1)
wx.Button(parent, wx.ID_ANY)

如果设置id参数为-1或wx.ID_ANY,wxPyhton会自动创建id。这些自动创建的id通常是负数。然后用户创建的id通常必须为正。当我们不必修改部件的状态,常常使用这个选项。举例,在程序生命周期里,静态文本永远不会变化。我们也能够得到id,如果我们想。GetId()方法能够为我们获取id。

# -*- coding: utf-8 -*-

import wx

class Example(wx.Frame):
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        self.InitUI()

    def InitUI(self):
        pnl = wx.Panel(self)
        exitButton = wx.Button(pnl, wx.ID_ANY, 'Exit', (10, 10))
        self.Bind(wx.EVT_BUTTON, self.OnExit, id=exitButton.GetId())
        self.SetTitle("Automatic id")
        self.Centre()
        self.Show(True)

    def OnExit(self, event):
        self.Close()

def main():
    ex = wx.App()
    Example(None)
    ex.MainLoop() 

if __name__ == '__main__':
    main()

这里写图片描述
这个例子里,我们不关心id的实际数值。

self.Bind(wx.EVT_BUTTON, self.OnExit, id=exitButton.GetId())

通过调用GetId()方法得到自动生成的id。
推荐使用标准标识符。这些ID能够提供一些标准图形或行为在一些平台上。

import wx

class Example(wx.Frame):
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        self.InitUI()

    def InitUI(self):
        pnl = wx.Panel(self)
        grid = wx.GridSizer(3, 2)
        grid.AddMany([(wx.Button(pnl, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9),
        (wx.Button(pnl, wx.ID_DELETE), 0, wx.TOP, 9),
        (wx.Button(pnl, wx.ID_SAVE), 0, wx.LEFT, 9),
        (wx.Button(pnl, wx.ID_EXIT)),
        (wx.Button(pnl, wx.ID_STOP), 0, wx.LEFT, 9),
        (wx.Button(pnl, wx.ID_NEW))])
        self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT)
        pnl.SetSizer(grid)
        self.SetSize((220, 180))
        self.SetTitle("Standard ids")
        self.Centre()
        self.Show(True)

    def OnQuitApp(self, event):
        self.Close()

def main():
    ex = wx.App()
    Example(None)
    ex.MainLoop() 

if __name__ == '__main__':
    main()

这里写图片描述
在这个例子里按钮都是使用标准标识符。在Linux上,这些按钮都图标。(本人是Windows)

 grid.AddMany([(wx.Button(pnl, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9),
        (wx.Button(pnl, wx.ID_DELETE), 0, wx.TOP, 9),
        (wx.Button(pnl, wx.ID_SAVE), 0, wx.LEFT, 9),
        (wx.Button(pnl, wx.ID_EXIT)),
        (wx.Button(pnl, wx.ID_STOP), 0, wx.LEFT, 9),
        (wx.Button(pnl, wx.ID_NEW))])

我们添加六个按钮到网格管理器。wx.ID_CANCEL,wx.ID_DELETE,wx.ID_SAVE,wx.ID_EXIT,wx.ID_STOP和wx.ID_NEW都是标准ID。

self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT)

我们将按钮点击事件绑定到OnQuitApp()事件处理器上。这个ID用来与其他按钮区分开来。我们能够独一无二地标识这件事件源。最后一个按钮使用自己定义的ID。
我们定义自己的全局ID。

# -*- coding: utf-8 -*-

import wx

ID_MENU_NEW = wx.NewId()
ID_MENU_OPEN = wx.NewId()
ID_MENU_SAVE = wx.NewId()

class Example(wx.Frame):
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        self.InitUI()

    def InitUI(self):
        self.CreateMenuBar()
        self.CreateStatusBar()
        self.SetSize((250, 180))
        self.SetTitle('Global ids')
        self.Centre()
        self.Show(True) 

    def CreateMenuBar(self):
        mb = wx.MenuBar()
        fMenu = wx.Menu()
        fMenu.Append(ID_MENU_NEW, 'New')
        fMenu.Append(ID_MENU_OPEN, 'Open')
        fMenu.Append(ID_MENU_SAVE, 'Save')
        mb.Append(fMenu, '&File')
        self.SetMenuBar(mb)
        self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_NEW)
        self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_OPEN)
        self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_SAVE) 

    def DisplayMessage(self, e):
        sb = self.GetStatusBar()
        eid = e.GetId()
        if eid == ID_MENU_NEW:
            msg = 'New menu item selected'
        elif eid == ID_MENU_OPEN:
            msg = 'Open menu item selected'
        elif eid == ID_MENU_SAVE:
            msg = 'Save menu item selected'
        sb.SetStatusText(msg)

def main():
    ex = wx.App()
    Example(None)
    ex.MainLoop() 

if __name__ == '__main__':
    main()

这里写图片描述
在这个例子,我们创建了三个菜单子项的菜单。这些菜单子项的ID都是全局的。

ID_MENU_NEW = wx.NewId()
ID_MENU_OPEN = wx.NewId()
ID_MENU_SAVE = wx.NewId()

方法wx.NewId()生成一个新的独一无二地id。

 self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_NEW)
 self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_OPEN)
 self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_SAVE) 

这三个菜单子项都被它们的id唯一标识。

        eid = e.GetId()
        if eid == ID_MENU_NEW:
            msg = 'New menu item selected'
        elif eid == ID_MENU_OPEN:
            msg = 'Open menu item selected'
        elif eid == ID_MENU_SAVE:
            msg = 'Save menu item selected'

根据事件对象,我们重新得到id。根据id的数值,我们准备显示在程序状态栏的信息。
这里写图片描述

绘制事件

绘制事件在窗口被重绘的时候产生。当我们重新设置窗口尺寸或者最大化时窗口重新绘制。一个绘制绘制事件也可以编程产生。比如,当我们调用方法SetLabel()来改变wx.StaticText部件。注意最小化窗口时,没有绘制事件产生。

# -*- coding: utf-8 -*-

import wx

class Example(wx.Frame):
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        self.InitUI()

    def InitUI(self):
        self.count = 0
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.SetSize((250, 180))
        self.Centre()
        self.Show(True) 

    def OnPaint(self, e):
        self.count += 1
        self.SetTitle(str(self.count))

def main():
    ex = wx.App()
    Example(None)
    ex.MainLoop() 

if __name__ == '__main__':
    main()

这里写图片描述
在上面例子里,我们计算绘制事件的个数,把当前产生的事件总数显示在主窗口的标题上。

 self.Bind(wx.EVT_PAINT, self.OnPaint)

将EVT_PAINT事件绑定到OnPaint()方法。

    def OnPaint(self, e):
        self.count += 1
        self.SetTitle(str(self.count))

在OnPaint()事件里,增加统计数并设置到主窗口的标题上。

焦点事件

  焦点指示程序中当前被选中的部件。来自键盘或者剪切板的文本会传给具有焦点的部件。有两种与 焦点相关的事件类型。wx.EVT_SET_FOCUS事件当部件获得焦点时产生。当部件失去焦点时,wx.EVT_KILL_FOCUS时间产生。焦点被鼠标点击或者按键盘键,通常是Tal 或者 Shift + Tab。

# -*- coding: utf-8 -*-

import wx

class MyWindow(wx.Panel):
    def __init__(self, parent):
        super(MyWindow, self).__init__(parent)
        self.color = '#b3b3b3'
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

    def OnPaint(self, e):
        dc = wx.PaintDC(self)
        dc.SetPen(wx.Pen(self.color))
        x, y = self.GetSize()
        dc.DrawRectangle(0, 0, x, y)

    def OnSize(self, e):
        self.Refresh()

    def OnSetFocus(self, e):
        self.color = '#0099f7'
        self.Refresh()

    def OnKillFocus(self, e):
        self.color = '#b3b3b3'
        self.Refresh()

class Example(wx.Frame):
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        self.InitUI()

    def InitUI(self):
        grid = wx.GridSizer(2, 2, 10, 10)
        grid.AddMany([(MyWindow(self), 0, wx.EXPAND|wx.TOP|wx.LEFT, 9),
        (MyWindow(self), 0, wx.EXPAND|wx.TOP|wx.RIGHT, 9), 
        (MyWindow(self), 0, wx.EXPAND|wx.BOTTOM|wx.LEFT, 9), 
        (MyWindow(self), 0, wx.EXPAND|wx.BOTTOM|wx.RIGHT, 9)])
        self.SetSizer(grid)
        self.SetSize((350, 250))
        self.SetTitle('Focus event')
        self.Centre()
        self.Show(True) 

    def OnMove(self, e):
        print e.GetEventObject()
        x, y = e.GetPosition()
        self.st1.SetLabel(str(x))
        self.st2.SetLabel(str(y))


def main():
    ex = wx.App()
    Example(None)
    ex.MainLoop() 

if __name__ == '__main__':
    main()

在上面的例子里,我们有四个panel。焦点到上面时 ,panel高亮。

        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

我们绑定了两个焦点事件给事件处理器。

def OnPaint(self, e):
    dc = wx.PaintDC(self)
    dc.SetPen(wx.Pen(self.color))
    x, y = self.GetSize()
    dc.DrawRectangle(0, 0, x, y)

在OnPaint()方法里,我们在窗口上绘制。框边的颜色由窗口是否获得焦点决定。获得焦点的窗口边框被绘制成蓝色。

    def OnSetFocus(self, e):
        self.color = '#0099f7'
        self.Refresh()

在OnSetFocus()方法中,将self.color变量设置一些蓝色颜色。当我们刷新主窗口,会为 它所有的子部件产生一个绘制事件。所有窗口被绘制,获得焦点的那个 边框变成新的颜色。
这里写图片描述

键盘事件

当我们按下键盘上的一个键,wx.KeyEvent被产生。该事件会 传给当前获得焦点的部件。有三种不同的按键处理器:
wx.EVT_KEY_DOWN
wx.EVT_KEY_UP
wx.EVT_CHAR
当Esc键被按下,通常产生一个关闭应用程序的请求。

# -*- coding: utf-8 -*-

import wx

class Example(wx.Frame):
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 
        self.InitUI()

    def InitUI(self):
        pnl = wx.Panel(self)
        pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        pnl.SetFocus()
        self.SetSize((250, 180))
        self.SetTitle('Key event')
        self.Centre()
        self.Show(True) 

    def OnKeyDown(self, e):
        key = e.GetKeyCode()
        if key == wx.WXK_ESCAPE:
            ret = wx.MessageBox('Are you sure to quit?', 'Question', 
                                wx.YES_NO | wx.NO_DEFAULT, self)
            if ret == wx.YES:
                self.Close() 

def main():
    ex = wx.App()
    Example(None)
    ex.MainLoop() 

if __name__ == '__main__':
    main()

这里写图片描述
在这个例子里,我们处理Esc键被按下。无论我们是否真地关闭应用程序,会显示一个信息对话框让却确认。

pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)

我们给EVT_KEY_DOWN事件绑定一个 时间处理器。

key = e.GetKeyCode()

上面我们得到被按下键的键码。

if key == wx.WXK_ESCAPE:

我们检查键码。Esc键的键码是wx.WXK_ESCAPE。
这里写图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值