总目录: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