事件驱动
一个桌面程序不单是控件的罗列,更重要的是对外部的刺激——包括用户的操作做出反应。如果把窗体和控件比作是桌面程序的躯体,那么响应外部刺激就是它的灵魂。wxPython的灵魂是事件驱动机制:当某事件发生时,程序就会自动执行预先设定的动作。
4.1 事件
所谓事件,就是我们的程序在运行中发生的事儿。事件可以是低级的用户动作,如鼠标移动或按键按下,也可以是高级的用户动作(定义在wxPython的窗口部件中的),如单击按钮或菜单选择。事件可以产生自系统,如关机,,也可以由用户自定义事件。
除了用户自定义事件,在wxPython中我习惯把事件分为4类:
鼠标事件:鼠标左右中键和滚轮动作,以及鼠标移动等事件
键盘事件:用户敲击键盘产生的事件
控件事件:发生在控件上的事件,比如按钮被按下、输入框内容改变等
系统事件:关闭窗口、改变窗口大小、重绘、定时器等事件
事实上,这个分类方法不够严谨。比如,wx.Frame作为一个控件,关闭和改变大小也是控件事件,不过这一类事件通常都由系统绑定了行为。基于此,可以重新定义所谓的控件事件,是指发生在控件上的、系统并未预定义行为的事件。
常用的鼠标事件包括:
wx.EVT_LEFT_DOWN - 左键按下
wx.EVT_LEFT_UP - 左键弹起
wx.EVT_LEFT_DCLICK - 左键双击
wx.EVT_RIGHT_DOWN - 右键按下
wx.EVT_RIGHT_UP - 右键弹起
wx.EVT_RIGHT_DCLICK - 右键双击
wx.EVT_MOTION - 鼠标移动
wx.EVT_MOUSEWHEEL - 滚轮滚动
wx.EVT_MOUSE_EVENTS - 所有的鼠标事件
常用的键盘事件有:
wx.EVT_KEY_DOWN - 按键按下
wx.EVT_KEY_UP - 按键弹起
常用的系统事件包括:
wx.EVT_CLOSE - 关闭
wx.EVT_SIZE - 改变大小
wx.EVT_TIMER - 定时器事件
wx.EVT_PAINT - 重绘
wx.EVT_ERASE_BACKGROUND -背景擦除
常用的控件事件包括:
wx.EVT_BUTTON - 点击按钮
wx.EVT_CHOICE - 下拉框改变选择
wx.EVT_TEXT - 输入框内容改变
wx.EVT_TEXT_ENTER - 输入框回车
wx.EVT_RADIOBOX - 单选框改变选择
wx.EVT_CHECKBOX - 点击复选框
4.2 事件绑定
事件驱动机制有三个要素:事件、事件函数和事件绑定。比如,当一个按钮被点击时,就会触发按钮点击事件,该事件如果绑定了事件函数,事件函数就会被调用。所有的事件函数都以事件对象为参数,事件对象提供了事件的详细信息,比如键盘按下事件的事件对象就包含了被按下的键的信息。
下面这个例子演示了如何定义事件函数,以及绑定事件和事件函数之间的关联关系。
import wx
class MainFrame(wx.Frame):
"""从wx.Frame派生主窗口类"""
def __init__(self, parent):
"""构造函数"""
wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE)
self.SetTitle('事件和事件函数的绑定')
self.SetIcon(wx.Icon('res/wx.ico'))
self.SetBackgroundColour((224, 224, 224)) # 设置窗口背景色
self.SetSize((520, 220))
self._init_ui
self.Center
def _init_ui(self):
"""初始化界面"""
wx.StaticText(self, -1, '第一行输入框:', pos=(40, 50), size=(100, -1), style=wx.ALIGN_RIGHT)
wx.StaticText(self, -1, '第二行输入框:', pos=(40, 80), size=(100, -1), style=wx.ALIGN_RIGHT)
self.tip = wx.StaticText(self, -1, u'', pos=(145, 110), size=(150, -1), style=wx.ST_NO_AUTORESIZE)
self.tc1 = wx.TextCtrl(self, -1, '', pos=(145, 50), size=(150, -1), name='TC01', style=wx.TE_CENTER)
self.tc2 = wx.TextCtrl(self, -1, '', pos=(145, 80), size=(150, -1), name='TC02', style=wx.TE_PASSWORD|wx.ALIGN_RIGHT)
btn_mea = wx.Button(self, -1, '鼠标左键事件', pos=(350, 50), size=(100, 25))
btn_meb = wx.Button(self, -1, '鼠标所有事件', pos=(350, 80), size=(100, 25))
btn_close = wx.Button(self, -1, '关闭窗口', pos=(350, 110), size=(100, 25))
self.tc1.Bind(wx.EVT_TEXT, self.on_text) # 绑定文本内容改变事件
self.tc2.Bind(wx.EVT_TEXT, self.on_text) # 绑定文本内容改变事件
btn_close.Bind(wx.EVT_BUTTON, self.on_close, btn_close) # 绑定按键事件
btn_close.Bind(wx.EVT_MOUSEWHEEL, self.on_wheel) # 绑定鼠标滚轮事件
btn_mea.Bind(wx.EVT_LEFT_DOWN, self.on_left_down) # 绑定鼠标左键按下
btn_mea.Bind(wx.EVT_LEFT_UP, self.on_left_up) # 绑定鼠标左键弹起
btn_meb.Bind(wx.EVT_MOUSE_EVENTS, self.on_mouse) # 绑定所有鼠标事件
self.Bind(wx.EVT_CLOSE, self.on_close) # 绑定窗口关闭事件
self.Bind(wx.EVT_SIZE, self.on_size) # 绑定改变窗口大小事件
self.Bind(wx.EVT_KEY_DOWN, self.on_key_down) # 绑定键盘事件
def on_text(self, evt):
"""输入框事件函数"""
obj = evt.GetEventObject
objName = obj.GetName
text = evt.GetString
if objName == 'TC01':
self.tc2.SetValue(text)
elif objName == 'TC02':
self.tc1.SetValue(text)
def on_size(self, evt):
'''改变窗口大小事件函数'''
print('你想改变窗口,但是事件被Skip了,所以没有任何改变')
evt.Skip # 注释掉此行(事件继续传递),窗口大小才会被改变
def on_close(self, evt):
"""关闭窗口事件函数"""
dlg = wx.MessageDialog(None, '确定要关闭本窗口?', '操作提示', wx.YES_NO | wx.ICON_QUESTION)
if(dlg.ShowModal == wx.ID_YES):
self.Destroy
def on_left_down(self, evt):
"""左键按下事件函数"""
self.tip.SetLabel('左键按下')
def on_left_up(self, evt):
"""左键弹起事件函数"""
self.tip.SetLabel('左键弹起')
def on_wheel(self, evt):
"""鼠标滚轮事件函数"""
vector = evt.GetWheelRotation
self.tip.SetLabel(str(vector))
def on_mouse(self, evt):
"""鼠标事件函数"""
self.tip.SetLabel(str(evt.EventType))
def on_key_down(self, evt):
"""键盘事件函数"""
key = evt.GetKeyCode
self.tip.SetLabel(str(key))
if __name__ == '__main__':
app = wx.App
frame = MainFrame(None)
frame.Show
app.MainLoop
代码运行界面如下图所示。
两个输入框,一个明文居中,一个密写右齐,但内容始终保持同步。输入焦点不在输入框的时候,敲击键盘,界面显示对应的键值。最上面的按钮响应鼠标左键的按下和弹起事件,中间的按钮响应所有的鼠标事件,下面的按钮响应滚轮事件和按钮按下的事件。另外,程序还绑定了窗口关闭事件,重新定义了关闭函数,增加了确认选择。
程序框架
5.1 菜单栏、工具栏和状态栏
通常,一个完整的窗口程序一般都有菜单栏、工具栏和状态栏。下面的代码演示了如何创建菜单栏、工具栏和状态栏,顺便演示了类的静态属性的定义和用法。不过,说实话,wx的工具栏有点丑,幸好,wx还有一个 AUI 的工具栏比较漂亮,我会在后面的例子里演示它的用法。
import wx
class MainFrame(wx.Frame):
"""从wx.Frame派生主窗口类"""
id_open = wx.NewIdRef
id_save = wx.NewIdRef
id_quit = wx.NewIdRef
id_help = wx.NewIdRef
id_about = wx.NewIdRef
def __init__(self, parent):
"""构造函数"""
wx.Frame.__init__(self, parent, style=wx.DEFAULT_FRAME_STYLE)
self.SetTitle('菜单、工具栏、状态栏')
self.SetIcon(wx.Icon('res/wx.ico'))
self.SetBackgroundColour((224, 224, 224)) # 设置窗口背景色
self.SetSize((360, 180))
self._create_menubar # 菜单栏
self._create_toolbar # 工具栏
self._create_statusbar # 状态栏
self.Center
def _create_menubar(self):
"""创建菜单栏"""
self.mb = wx.MenuBar
# 文件菜单
m = wx.Menu
m.Append(self.id_open, '打开文件')
m.Append(self.id_save, '保存文件')
m.AppendSeparator
m.Append(self.id_quit, '退出系统')
self.mb.Append(m, '文件')
self.Bind(wx.EVT_MENU, self.on_open, id=self.id_open)
self.Bind(wx.EVT_MENU, self.on_save, id=self.id_save)
self.Bind(wx.EVT_MENU, self.on_quit, id=self.id_quit)
# 帮助菜单
m = wx.Menu
m.Append(self.id_help, '帮助主题')
m.Append(self.id_about, '关于...')
self.mb.Append(m, '帮助')
self.Bind(wx.EVT_MENU, self.on_help,id=self.id_help)
self.Bind(wx.EVT_MENU, self.on_about,id=self.id_about)
self.SetMenuBar(self.mb)
def _create_toolbar(self):
"""创建工具栏"""
bmp_open = wx.Bitmap('res/open_mso.png', wx.BITMAP_TYPE_ANY) # 请自备按钮图片
bmp_save = wx.Bitmap('res/save_mso.png', wx.BITMAP_TYPE_ANY) # 请自备按钮图片
bmp_help = wx.Bitmap('res/help_mso.png', wx.BITMAP_TYPE_ANY) # 请自备按钮图片
bmp_about = wx.Bitmap('res/info_mso.png', wx.BITMAP_TYPE_ANY) # 请自备按钮图片
self.tb = wx.ToolBar(self)
self.tb.SetToolBitmapSize((16,16))
self.tb.AddTool(self.id_open, '打开文件', bmp_open, shortHelp='打开', kind=wx.ITEM_NORMAL)
self.tb.AddTool(self.id_save, '保存文件', bmp_save, shortHelp='保存', kind=wx.ITEM_NORMAL)
self.tb.AddSeparator
self.tb.AddTool(self.id_help, '帮助', bmp_help, shortHelp='帮助', kind=wx.ITEM_NORMAL)
self.tb.AddTool(self.id_about, '关于', bmp_about, shortHelp='关于', kind=wx.ITEM_NORMAL)
self.tb.Realize
def _create_statusbar(self):
"""创建状态栏"""
self.sb = self.CreateStatusBar
self.sb.SetFieldsCount(3)
self.sb.SetStatusWidths([-2, -1, -1])
self.sb.SetStatusStyles([wx.SB_RAISED, wx.SB_RAISED, wx.SB_RAISED])
self.sb.SetStatusText('状态信息0', 0)
self.sb.SetStatusText('', 1)
self.sb.SetStatusText('状态信息2', 2)
def on_open(self, evt):
"""打开文件"""
self.sb.SetStatusText(u'打开文件', 1)
def on_save(self, evt):
"""保存文件"""
self.sb.SetStatusText(u'保存文件', 1)
def on_quit(self, evt):
"""退出系统"""
self.sb.SetStatusText(u'退出系统', 1)
self.Destroy
def on_help(self, evt):
"""帮助"""
self.sb.SetStatusText(u'帮助', 1)
def on_about(self, evt):
"""关于"""
self.sb.SetStatusText(u'关于', 1)
if __name__ == '__main__':
app = wx.App
frame = MainFrame(None)
frame.Show
app.MainLoop
代码里面用到了4个16x16的工具按钮,请自备4个图片文件,保存路径请查看代码中的注释。代码运行界面如下图所示。