wxPython中的事件
事件是每个GUI应用程序不可缺少的一部分。所有GUI应用程序都是事件驱动的。一个应用程序对在其生命周期中产生的不同事件类型做出反应。事件主要由应用程序的用户产生。但它们也可以通过其他方式产生,例如互联网连接、窗口管理器或定时器。所以当我们调用MainLoop()方法时,我们的应用程序会等待事件的产生。当我们退出应用程序时,MainLoop()方法就结束了。
定义
事件是来自底层框架的一段应用级信息,通常是GUI工具包。事件循环是一种编程结构,它在程序中等待并派发事件或消息。事件循环反复寻找要处理的事件。派遣器是一个将事件映射到事件处理程序的过程。事件处理程序是对事件做出反应的方法。
事件对象是与事件相关联的对象。它通常是一个窗口。事件类型是一个已经生成的唯一事件。事件绑定器是将事件类型与事件处理程序绑定的对象。
wxPython wx.EVT_MOVE 例子
在下面的例子中,我们对wx.MoveEvent事件做出反应。当我们将一个窗口移动到一个新的位置时,就会产生这个事件。这个事件是wx.EVT_MOVE。
#evt_move.py
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((350, 250))
self.SetTitle('Move event')
self.Centre()
def OnMove(self, e):
x, y = e.GetPosition()
self.st1.SetLabel(str(x))
self.st2.SetLabel(str(y))
def main():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
该示例显示窗口的当前位置。
self.Bind(wx.EVT_MOVE, self.OnMove)
这里我们将wx.EVT_MOVE
事件绑定到OnMove()
方法。
def OnMove(self, e):
x, y = e.GetPosition()
self.st1.SetLabel(str(x))
self.st2.SetLabel(str(y))
OnMove()方法中的事件参数是一个特定事件类型的对象。在我们的例子中,它是一个wx.MoveEvent类的实例。这个对象保存了有关事件的信息,例如事件对象或窗口的位置。在我们的例子中,事件对象是 wx.Frame 组件。我们可以通过调用事件的GetPosition()方法找出当前位置。
wxPython 事件binding
wxPython中处理事件的三个步骤是。
- 确定事件binder名称:
wx.EVT_SIZE
,wx.EVT_CLOSE
等。 - 创建一个事件处理程序。当一个事件生成时,该方法被调用。
- 将一个事件绑定到一个事件处理程序。
在wxPython中,我们说要把一个方法绑定到一个事件上。有时会使用hook这个词。你可以通过调用Bind()
方法来绑定一个事件。该方法有以下参数。
Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)
event
是EVT_*对象之一。它指定了事件的类型。handler
是一个要被调用的对象,换句话说,它是一个与事件绑定的方法。source
参数用于当我们想区分不同小组件的同一事件类型时。id
参数用于我们有多个按钮、菜单项等时。id
参数用于区分它们。id2
用于当需要将一个处理程序绑定到一系列id时,例如EVT_MENU_RANGE
。
请注意,方法Bind()
定义在类EvtHandler
中。它是一个继承自wx.Window
的类。wx.Window
是wxPython中大多数组件的基类。还有一个反向过程。如果我们想从一个事件中解绑一个方法,我们调用Unbind()
方法。它的参数和上面的方法一样。
Vetoing events(否决事件)
有时我们需要停止处理一个事件。为此,我们调用Veto()
方法。
#event_veto.py
import wx
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()
def OnCloseWindow(self, e):
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:
e.Veto()
def main():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
在我们的例子中,我们处理了一个wx.CloseEvent
。当我们点击标题栏上的X按钮时,这个事件被调用。在许多应用程序中,我们希望防止在做了一些修改后意外关闭窗口。要做到这一点,我们必须绑定wx.EVT_CLOSE
事件绑定器。
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()
方法,我们最终会陷入无休止的循环。
wxPython event 传播
有两种类型的事件:基本事件和命令事件。它们的传播方式不同。事件传播是指事件从子部件到父部件和祖父部件的传播。基本事件不传播。命令事件会传播。例如wx.CloseEvent
是一个基本事件。这个事件传播到父部件是没有意义的。
默认情况下,事件处理程序中捕获的事件会停止传播。为了继续传播,我们调用Skip()
方法。
#event_propagation.py
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, e):
print('event reached panel class')
e.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, e):
print('event reached button class')
e.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()
def OnButtonClicked(self, e):
print('event reached frame class')
e.Skip()
def main():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
event reached button class
event reached panel class
event reached frame class
在我们的例子中,我们在一个面板上有一个按钮。该面板被放置在一个Frame部件中。我们为所有组件定义一个处理程序。
def OnButtonClicked(self, e):
print('event reached button class')
e.Skip()
我们在自定义按钮类中处理按钮点击事件。Skip()
方法将事件进一步传播到面板类。
尝试省略一些Skip()
方法,看看会发生什么。
窗口标识符
窗口标识符是整数,在事件系统中唯一确定窗口标识。有三种方法可以创建窗口标识符。
- 让系统自动创建一个ID
- 使用标准标识符
- 创建自己的身份
wx.Button(parent, -1)
wx.Button(parent, wx.ID_ANY)
如果我们为id参数提供-1或wx.ID_ANY,我们就会让wxPython自动为我们创建一个id。自动创建的id总是负数,而用户指定的id必须总是正数。我们通常在不需要改变组件状态的时候使用这个选项。例如一个静态的文本,在应用程序的生命周期中永远不会被改变。如果我们想要的话,我们仍然可以获得id。有一个方法GetId()可以确定id。
#default_ids.py
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 ids")
self.Centre()
def OnExit(self, event):
self.Close()
def main():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
在这个例子中,我们不关心实际的id值。
self.Bind(wx.EVT_BUTTON, self.OnExit, id=exitButton.GetId())
我们通过调用GetId()方法来获取自动生成的id。
建议使用标准的标识符。标识符可以在一些平台上提供一些标准的图形或行为。
wxPython 标准ids
wxPython包含一些标准的id,如wx.ID_SAVE
或wx.ID_NEW
。
#standard_ids.py
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,0,0)
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.SetTitle("Standard ids")
self.Centre()
def OnQuitApp(self, event):
self.Close()
def main():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
在我们的例子中,我们在按钮上使用标准标识符。
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
是标准的标识符。
self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT)
我们将按钮点击事件绑定到OnQuitApp()事件处理程序。id参数用来区分不同的按钮。我们唯一地识别事件的来源。
wx.PaintEvent
当一个窗口被重新绘制时,会产生一个paint事件。这发生在我们调整窗口大小或最大化窗口时。一个paint事件也可以通过编程生成。例如,当我们调用SetLabel()
方法来改变一个wx.StaticText
部件时。请注意,当我们最小化一个窗口时,不会产生任何paint事件。
#paint_event.py
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.SetTitle('Paint events')
self.SetSize((350, 250))
self.Centre()
def OnPaint(self, e):
self.count += 1
dc = wx.PaintDC(self)
text = "Number of paint events: {0}".format(self.count)
dc.DrawText(text, 20, 20)
def main():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
在我们的例子中,我们统计paint事件的数量,并在窗口上绘制当前生成的事件数量。
self.Bind(wx.EVT_PAINT, self.OnPaint)
我们将 EVT_PAINT
事件绑定到 OnPaint()
方法。
def OnPaint(self, e):
self.count += 1
dc = wx.PaintDC(self)
text = "Number of paint events: {0}".format(self.count)
dc.DrawText(text, 20, 20)
在OnPaint()
事件里面,我们用DrawText()
方法增加计数器绘制窗口上的paint事件数量。
wx.FocusEvent
焦点表示应用程序中当前选定的组件。从键盘输入的文本或从剪贴板粘贴的文本会被发送到具有焦点的组件。关于焦点有两种事件类型。wx.EVT_SET_FOCUS
事件,当一个小组件收到焦点时产生。wx.EVT_KILL_FOCUS
事件是在小组件失去焦点时产生的。通过点击或键盘键改变焦点。
#focus_event.py
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 = '#ff0000'
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()
def OnMove(self, e):
print(e.GetEventObject())
x, y = e.GetPosition()
self.st1.SetLabel(str(x))
self.st2.SetLabel(str(y))
def main():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
在我们的例子中,我们有四个面板。重点的面板被突出显示。
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 = '#ff0000'
self.Refresh()
在OnSetFocus()
方法中,我们将self.color
变量设置为红色。我们刷新框架窗口,这将为其所有的子部件生成一个paint事件。窗口被重新绘制,有焦点的那个窗口的轮廓有了新的颜色。
def OnKillFocus(self, e):
self.color = '#b3b3b3'
self.Refresh()
当窗口失去焦点时,会调用OnKillFocus()
方法。我们改变颜色值并刷新。
wx.KeyEvent
当我们在键盘上按下一个键时,会生成一个wx.KeyEvent
。这个事件会被发送到当前有焦点的组件。有三种不同的按键处理程序。
- wx.EVT_KEY_DOWN
- wx.EvT_KEY_UP.
- wx.EVT_CHAR
一个常见的要求是当按下Esc键时关闭应用程序。
#key_event.py
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((350, 250))
self.SetTitle('Key event')
self.Centre()
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():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
在本例中,我们处理Esc键的按压。显示一个消息框,以确认应用程序的终止。
pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
我们将一个事件处理程序绑定到wx.EVT_KEY_DOWN
事件上。
key = e.GetKeyCode()
在这里我们得到了被按下的键的键码。
if key == wx.WXK_ESCAPE:
我们检查一下键的代码。Esc键有wx.WXK_ESCAPE
代码。