总目录:wxPython 教程目录 本节内容:wxPython 提示和技巧 本节译自:zetcode 上一篇:wxPython 教程 (十三): 自定义控件 下一篇:wxPython 教程 (十五): wxPython Gripts
本节,我们将讲解一些 wxPython 有趣的技巧。
交互按钮
当我们用鼠标进入到按钮区域时, wx.EVT_ENTER_WINDOW 事件将被触发。类似的,当鼠标离开按钮时,wx.EVT_LEAVE_WINDOW 也会被触发。我们对这两个事件进行绑定。
#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
translated by achen @2017/10
===
ZetCode wxPython tutorial
This example shows an interactive button.
author: Jan Bodnar
website: www.zetcode.com
last modified: September 2011
'''
import wx
from wx.lib.buttons import GenButton
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
def InitUI(self):
panel = wx.Panel(self)
btn = GenButton(panel, label='Button',
pos=(100, 100))
btn.SetBezelWidth(1)
btn.SetBackgroundColour('DARKGREY')
wx.EVT_ENTER_WINDOW(btn, self.OnEnter)
wx.EVT_LEAVE_WINDOW(btn, self.OnLeave)
self.SetSize((300, 200))
self.SetTitle('Interactive button')
self.Centre()
self.Show(True)
def OnEnter(self, e):
btn = e.GetEventObject()
btn.SetBackgroundColour('GREY79')
btn.Refresh()
def OnLeave(self, e):
btn = e.GetEventObject()
btn.SetBackgroundColour('DARKGREY')
btn.Refresh()
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
我们用了 GenButton 代替了基础的 wx.Button。
from wx.lib.buttons import GenButton
GenButton 位于 wx.lib.buttons 模块。
btn.SetBezelWidth(1)
SetBezelWidth() 方法会创建 3D 效果。
def OnEnter(self, e):
btn = e.GetEventObject()
btn.SetBackgroundColour('GREY79')
btn.Refresh()
当鼠标移至按钮时,我们修改按钮的背景色。
Isabelle
当应用出错时,一般会弹出一个错误对话框,这可能会有些讨厌。SAP 系统中有一种更好的解决办法:当用户输入无效命令时,状态栏会变红,并输出错误信息。红色比较吸引眼球,用户可以很容易的读取错误信息。下面的代码模拟了该场景。
#!/usr/bin/python
# Isabelle
import wx
ID_TIMER = 1
ID_EXIT = 2
ID_ABOUT = 3
ID_BUTTON = 4
class Isabelle(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.timer = wx.Timer(self, ID_TIMER)
self.blick = 0
file = wx.Menu()
file.Append(ID_EXIT, '&Quit\tCtrl+Q', 'Quit Isabelle')
help = wx.Menu()
help.Append(ID_ABOUT, '&About', 'O Programe')
menubar = wx.MenuBar()
menubar.Append(file, '&File')
menubar.Append(help, '&Help')
self.SetMenuBar(menubar)
toolbar = wx.ToolBar(self, -1)
self.tc = wx.TextCtrl(toolbar, -1, size=(100, -1))
btn = wx.Button(toolbar, ID_BUTTON, 'Ok', size=(40, 28))
toolbar.AddControl(self.tc)
toolbar.AddSeparator()
toolbar.AddControl(btn)
toolbar.Realize()
self.SetToolBar(toolbar)
self.Bind(wx.EVT_BUTTON, self.OnLaunchCommandOk, id=ID_BUTTON)
self.Bind(wx.EVT_MENU, self.OnAbout, id=ID_ABOUT)
self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT)
self.Bind(wx.EVT_TIMER, self.OnTimer, id=ID_TIMER)
self.panel = wx.Panel(self, -1, (0, 0), (500 , 300))
self.panel.SetBackgroundColour('GRAY')
self.sizer=wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(self.sizer)
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Welcome to Isabelle')
self.Centre()
self.Show(True)
def OnExit(self, event):
dlg = wx.MessageDialog(self, 'Are you sure to quit Isabelle?',
'Please Confirm', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
if dlg.ShowModal() == wx.ID_YES:
self.Close(True)
def OnAbout(self, event):
dlg = wx.MessageDialog(self, 'Isabelle\t\n' '2004\t', 'About',
wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def OnLaunchCommandOk(self, event):
input = self.tc.GetValue()
if input == '/bye':
self.OnExit(self)
elif input == '/about':
self.OnAbout(self)
elif input == '/bell':
wx.Bell()
else:
self.statusbar.SetBackgroundColour('RED')
self.statusbar.SetStatusText('Unknown Command')
self.statusbar.Refresh()
self.timer.Start(50)
self.tc.Clear()
def OnTimer(self, event):
self.blick = self.blick + 1
if self.blick == 25:
self.statusbar.SetBackgroundColour('#E0E2EB')
self.statusbar.Refresh()
self.timer.Stop()
self.blick = 0
app = wx.App()
Isabelle(None, -1, 'Isabelle')
app.MainLoop()
在状态栏有一个 wx.TextCtrl 控件,可以接受输入命令。我们定义了 3 个命令: /bye, /about, /beep。如果输错了,状态栏会变红,并输入错误。这些是通过 wx.Timeer 类完成的。
图:Isabelle
Undo/Redo 框架
许多应用允许用户撤销、反撤销他们的操作,下面的例子展示了如何在 wxPython 中完成这样的功能。

图:Undo/Redo
#!/usr/bin/python
# undoredo.py
from wx.lib.sheet import *
import wx
stockUndo = []
stockRedo = []
ID_QUIT = 10
ID_UNDO = 11
ID_REDO = 12
ID_EXIT = 13
ID_COLSIZE = 80
ID_ROWSIZE = 20
class UndoText:
def __init__(self, sheet, text1, text2, row, column):
self.RedoText = text2
self.row = row
self.col = column
self.UndoText = text1
self.sheet = sheet
def undo(self):
self.RedoText = self.sheet.GetCellValue(self.row, self.col)
if self.UndoText == None:
self.sheetSetCellValue('')
else: self.sheet.SetCellValue(self.row, self.col, self.UndoText)
def redo(self):
if self.RedoText == None:
self.sheet.SetCellValue('')
else: self.sheet.SetCellValue(self.row, self.col, self.RedoText)
class UndoColSize:
def __init__(self, sheet, position, size):
self.sheet = sheet
self.pos = position
self.RedoSize = size
self.UndoSize = ID_COLSIZE
def undo(self):
self.RedoSize = self.sheet.GetColSize(self.pos)
self.sheet.SetColSize(self.pos, self.UndoSize)
self.sheet.ForceRefresh()
def redo(self):
self.UndoSize = ID_COLSIZE
self.sheet.SetColSize(self.pos, self.RedoSize)
self.sheet.ForceRefresh()
class UndoRowSize:
def __init__(self, sheet, position, size):
self.sheet = sheet
self.pos = position
self.RedoSize = size
self.UndoSize = ID_ROWSIZE
def undo(self):
self.RedoSize = self.sheet.GetRowSize(self.pos)
self.sheet.SetRowSize(self.pos, self.UndoSize)
self.sheet.ForceRefresh()
def redo(self):
self.UndoSize = ID_ROWSIZE
self.sheet.SetRowSize(self.pos, self.RedoSize)
self.sheet.ForceRefresh()
class MySheet(CSheet):
instance = 0
def __init__(self, parent):
CSheet.__init__(self, parent)
self.SetRowLabelAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
self.text = ''
def OnCellChange(self, event):
toolbar = self.GetParent().toolbar
if (toolbar.GetToolEnabled(ID_UNDO) == False):
toolbar.EnableTool(ID_UNDO, True)
r = event.GetRow()
c = event.GetCol()
text = self.GetCellValue(r, c)
# self.text - text before change
# text - text after change
undo = UndoText(self, self.text, text, r, c)
stockUndo.append(undo)
if stockRedo:
# this might be surprising, but it is a standard behaviour
# in all spreadsheets
del stockRedo[:]
toolbar.EnableTool(ID_REDO, False)
def OnColSize(self, event):
toolbar = self.GetParent().toolbar
if (toolbar.GetToolEnabled(ID_UNDO) == False):
toolbar.EnableTool(ID_UNDO, True)
pos = event.GetRowOrCol()
size = self.GetColSize(pos)
undo = UndoColSize(self, pos, size)
stockUndo.append(undo)
if stockRedo:
del stockRedo[:]
toolbar.EnableTool(ID_REDO, False)
def OnRowSize(self, event):
toolbar = self.GetParent().toolbar
if (toolbar.GetToolEnabled(ID_UNDO) == False):
toolbar.EnableTool(ID_UNDO, True)
pos = event.GetRowOrCol()
size = self.GetRowSize(pos)
undo = UndoRowSize(self, pos, size)
stockUndo.append(undo)
if stockRedo:
del stockRedo[:]
toolbar.EnableTool(ID_REDO, False)
class Newt(wx.Frame):
def __init__(self,parent,id,title):
wx.Frame.__init__(self, parent, -1, title, size=(550, 500))
box = wx.BoxSizer(wx.VERTICAL)
menuBar = wx.MenuBar()
menu = wx.Menu()
quit = wx.MenuItem(menu, ID_QUIT, '&Quit\tCtrl+Q', 'Quits Newt')
quit.SetBitmap(wx.Bitmap('icons/exit16.png'))
menu.AppendItem(quit)
menuBar.Append(menu, '&File')
self.Bind(wx.EVT_MENU, self.OnQuitNewt, id=ID_QUIT)
self.SetMenuBar(menuBar)
self.toolbar = wx.ToolBar(self, id=-1, style=wx.TB_HORIZONTAL | wx.NO_BORDER |
wx.TB_FLAT | wx.TB_TEXT)
self.toolbar.AddSimpleTool(ID_UNDO, wx.Bitmap('icons/undo.png'),
'Undo', '')
self.toolbar.AddSimpleTool(ID_REDO, wx.Bitmap('icons/redo.png'),
'Redo', '')
self.toolbar.EnableTool(ID_UNDO, False)
self.toolbar.EnableTool(ID_REDO, False)
self.toolbar.AddSeparator()
self.toolbar.AddSimpleTool(ID_EXIT, wx.Bitmap('icons/exit.png'),
'Quit', '')
self.toolbar.Realize()
self.toolbar.Bind(wx.EVT_TOOL, self.OnUndo, id=ID_UNDO)
self.toolbar.Bind(wx.EVT_TOOL, self.OnRedo, id=ID_REDO)
self.toolbar.Bind(wx.EVT_TOOL, self.OnQuitNewt, id=ID_EXIT)
box.Add(self.toolbar, border=5)
box.Add((5,10), 0)
self.SetSizer(box)
self.sheet1 = MySheet(self)
self.sheet1.SetNumberRows(55)
self.sheet1.SetNumberCols(25)
for i in range(self.sheet1.GetNumberRows()):
self.sheet1.SetRowSize(i, ID_ROWSIZE)
self.sheet1.SetFocus()
box.Add(self.sheet1, 1, wx.EXPAND)
self.CreateStatusBar()
self.Centre()
self.Show(True)
def OnUndo(self, event):
if len(stockUndo) == 0:
return
a = stockUndo.pop()
if len(stockUndo) == 0:
self.toolbar.EnableTool(ID_UNDO, False)
a.undo()
stockRedo.append(a)
self.toolbar.EnableTool(ID_REDO, True)
def OnRedo(self, event):
if len(stockRedo) == 0:
return
a = stockRedo.pop()
if len(stockRedo) == 0:
self.toolbar.EnableTool(ID_REDO, False)
a.redo()
stockUndo.append(a)
self.toolbar.EnableTool(ID_UNDO, True)
def OnQuitNewt(self, event):
self.Close(True)
app = wx.App()
Newt(None, -1, 'Newt')
app.MainLoop()
stockUndo = [] stockRedo = []
有两个 list 对象。stockUndo 存储所有我们可以撤销的操作, stockRedo 存储所有可以反撤销的操作。改变的内容实例化至 UndoText 对象,该对象有两个方法: undo 和 redo。
class MySheet(CSheet):
def __init__(self, parent):
CSheet.__init__(self, parent)
我们的例子继承自 CSheet 类,它是一个有一些额外逻辑功能的 grid 控件。
self.SetRowLabelAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
我们对 label 做了居中对齐。
r = event.GetRow() c = event.GetCol() text = self.GetCellValue(r, c) # self.text - text before change # text - text after change undo = UndoText(self, self.text, text, r, c) stockUndo.append(undo)
每次我们进行修改时,都会创建一个 UndoText 对象,被附加到stockUndo list中。
if stockRedo:
# this might be surprising, but it is a standard behaviour
# in all spreadsheets
del stockRedo[:]
toolbar.EnableTool(ID_REDO, False)
基本上,如果我们撤销了一些修改,然后重新输入,所有的 redo 修改都会丢失,OpenOffice Calc 是这样的,Gnumeric 也一样。
if len(stockUndo) == 0:
self.toolbar.EnableTool(ID_UNDO, False)
...
self.toolbar.EnableTool(ID_REDO, True)
undo 和 redo 按钮各自 enable 或 disable,比如,如果没有什么可以撤销的,那撤销按钮就被禁用。
a = stockUndo.pop()
if len(stockUndo) == 0:
self.toolbar.EnableTool(ID_UNDO, False)
a.undo()
stockRedo.append(a)
如果我们点击 undo,我们会从 stockUndo list 中 pop 一个 UndoText 对象,然后调用 undo(),并将对象附加到 stockRedo list。
应用配置设置
很多应用允许用户配置他们的设置,可以开关工具栏、改变字体、修改默认下载路径等等。多数情况下,他们有一个菜单项叫做 preferences。应用设置被存储在硬盘中,所以用户不需要每次打开应用的时候都修改设置。
在 wxPython 中,我们有 wx.Config 来完成这些事情。
在 Linux 中,设置被存储在隐藏文件中,该文件默认位于主文件夹下,当然路径是可以修改的。文件名在 wx.Config 的构造函数中可以修改。在下面的例子中,我们可以配置窗口的大小。如果没有配置文件,高和宽会被默认配置为 250px。我们可以设置从 200 到 500px,保存后重启程序将看到效果。
#!/usr/bin/python
# myconfig.py
import wx
class MyConfig(wx.Frame):
def __init__(self, parent, id, title):
self.cfg = wx.Config('myconfig')
if self.cfg.Exists('width'):
w, h = self.cfg.ReadInt('width'), self.cfg.ReadInt('height')
else:
(w, h) = (250, 250)
wx.Frame.__init__(self, parent, id, title, size=(w, h))
wx.StaticText(self, -1, 'Width:', (20, 20))
wx.StaticText(self, -1, 'Height:', (20, 70))
self.sc1 = wx.SpinCtrl(self, -1, str(w), (80, 15), (60, -1), min=200, max=500)
self.sc2 = wx.SpinCtrl(self, -1, str(h), (80, 65), (60, -1), min=200, max=500)
wx.Button(self, 1, 'Save', (20, 120))
self.Bind(wx.EVT_BUTTON, self.OnSave, id=1)
self.statusbar = self.CreateStatusBar()
self.Centre()
self.Show(True)
def OnSave(self, event):
self.cfg.WriteInt("width", self.sc1.GetValue())
self.cfg.WriteInt("height", self.sc2.GetValue())
self.statusbar.SetStatusText('Configuration saved, %s ' % wx.Now())
app = wx.App()
MyConfig(None, -1, 'myconfig.py')
app.MainLoop()
我们在代码中获取到配置文件的内容,包括两个键值对。
$ cat .myconfig height=230 width=350

图:MyConfig
鼠标手势
鼠标手势结合鼠标动作和点击来完成特定任务。我们可以在 FireFox 或者 Opera 中看到鼠标手势的应用,可以帮我们节省很多时间。在 wxPython 中,可以使用 wx.lib.gestures.MouseGestures 类来完成鼠标手势。
可用的手势:
- L for left
- R for right
- U for up
- D for down
- 7 for northwest
- 9 for northeast
- 1 for southwest
- 3 for southeast
如果你好奇为什么会选用这些数字,你可以看一下数字小键盘。当我们按下鼠标指针画一个正方形时,手势 ‘RDLU’ 将被触发。
可用的标识:
- wx.MOUSE_BTN_LEFT
- wx.MOUSE_BTN_MIDDLE
- wx.MOUSE_BTN_RIGHT
#!/usr/bin/python
# mousegestures.py
import wx
import wx.lib.gestures as gest
class MyMouseGestures(wx.Frame):
def __init__ (self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(300, 200))
panel = wx.Panel(self, -1)
mg = gest.MouseGestures(panel, True, wx.MOUSE_BTN_LEFT)
mg.SetGesturePen(wx.Colour(255, 0, 0), 2)
mg.SetGesturesVisible(True)
mg.AddGesture('DR', self.OnDownRight)
self.Centre()
self.Show(True)
def OnDownRight(self):
self.Close()
app = wx.App()
MyMouseGestures(None, -1, 'mousegestures.py')
app.MainLoop()
在上面的例子中,我们注册了一个鼠标手势。当左键点击,同时鼠标向下、右滑动时,这个手势会被触发,然后会关闭应用。
mg = gest.MouseGestures(panel, True, wx.MOUSE_BTN_LEFT)
如果我们要使用鼠标手势,我们需要创建一个 MouseGesture 对象。第一个参数是一个窗口,手势要向该窗口注册。第二个参数定义了注册的方式,True 代表自动注册,False 代表手动注册。最后一个参数定义了鼠标的按键,表明当触发的时候需要按的键。之后可以通过 SetMouseButton() 来修改。
mg.SetGesturePen(wx.Colour(255, 0, 0), 2)
我们的手势将会被绘制成红色的线,宽为 2px。
mg.SetGesturesVisible(True)
使用上面的方法可以让手势可见。
mg.AddGesture('DR', self.OnDownRight)
我们使用 AddGesture() 方法注册一个鼠标手势。第一个参数是手势,第二个参数是需要触发的方法。
在本节,我们讲解了一些 wxPython 的技巧。
上一节:wxPython 教程 (十三): 自定义控件 下一节:wxPython 教程 (十五): wxPython Gripts