Ross Wan's World!

Python, Ajax, PHP and Linux.

wxPython:经验和技巧

Posted by Ross Wan 于 2008/05/05

       在这个章节里,我们将在代码实例中讲述 wxPython 的一些经验和技巧。

最瘦小的 wxPython 应用程序(The tiniest wxPython application)

       这个例子,纯粹为了乐趣 :)

#!/usr/bin/python

import wx

i = wx.App()
wx.Frame(None).Show()
i.MainLoop()

交互式按钮(Interactive Button)

       下面将讲述如何创建一个交互式按钮。在我们的例子中,当鼠标进入到按钮上面的时候,按钮的背景色会随之改变。要完成这个功能,我们只需要将改变 widgets 属性(如颜色、形状)的方法绑定到 widgets 的两个事件即可:wx.EVT_ENTER_WINDOW 和 wx.EVT_LEAVE_WINDOW,它们分别在鼠标进入和离开 widget 所处区域时触发。

#!/usr/bin/python
# interactivebutton.py

import wx
from wx.lib.buttons import GenButton

class InteractiveButton(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)

        panel = wx.Panel(self, -1)

        self.btn = GenButton(panel, -1, ‘button’, pos=(100, 100), size=(-1, -1))
        self.btn.SetBezelWidth(1)
        self.btn.SetBackgroundColour( ‘DARKGREY’)

        wx.EVT_ENTER_WINDOW(self.btn, self.func)
        wx.EVT_LEAVE_WINDOW(self.btn, self.func1)

        self.Centre()
        self.Show(True)

    def func(self, event):
        self.btn.SetBackgroundColour( ‘GREY79’)
        self.btn.Refresh()

    def func1(self, event):
        self.btn.SetBackgroundColour( ‘DARKGREY’)
        self.btn.Refresh()

app = wx.App()
InteractiveButton(None, -1, ‘interactivebutton.py’)
app.MainLoop()

       我们使用 GenButton 而不是 wx.Button,因为 GenButton 可以改变其边框属性,这样效果更引人注目。当然,用 wx.Button 也是可以完成这个任务的。

Isabelle

       当一个应用程序发生错误的时候,经常会弹出一个出错对话框,这实在令人厌烦。下面,我们将改变这种交互方式——当我们在应用程序输入了一个无效的命令,状态栏将会立刻变抢眼的红色,并将出错信息显示在上面。

#!/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()

    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。

撤销/重做(Undo/Redo framework)

       这是目前大多数应用软件都提供有的功能,下面看看在 wxPython 如何实现。

#!/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 = []

       分别用两个列表来保存撤销和重做的命令对象,每个保存的对象都具有两个方法:undo() 和 redo()。

 class MySheet(CSheet):
     def __init__(self, parent):
         CSheet.__init__(self, parent)

       继承一个 grid 控件 CSheet,它提供了很多附加的逻辑功能。

 self.SetRowLabelAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)

       将标题居中(默认是向右对齐的)。

 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 列表。

 if stockRedo:
     # this might be surprising, but it is a standard behaviour
     # in all spreadsheets
     del stockRedo[:]
         toolbar.EnableTool(ID_REDO, False)

       当我们撤销某个动作并重新输入时,所有的 redo 都会清空(OpenOffice.org 是以这个方式工作的)。

 if len(stockUndo) == 0:
     self.toolbar.EnableTool(ID_UNDO, False)
 …
 self.toolbar.EnableTool(ID_REDO, True)

       根据实际情况,使 undo 和 redo 按钮可用或者变灰。如果 undo 列表为空,其按钮将变灰,不可用。

 a = stockUndo.pop()
 if len(stockUndo) == 0:
     self.toolbar.EnableTool(ID_UNDO, False)

 a.undo()
 stockRedo.append(a)

       每当点击一次 undo 按钮,将会从 stockUndo 列表 pop 一个 UndoText 对象出来,然后调用 UndoText 对象的 undo() 方法,并将 UndoText 对象追加到 stockRedo 列表。

参数配置(Configuring application settings)

       这也是大多数应用程序提供的供能,方便用户改变应用程序的一些属性,并将这些改变保存在硬盘文件上,这样用户就不必在每次程序启动时再次设置。在 wxPython,有一个 wx.Config 类可以完成这个任务。

       在 Linux 系统下,配置文件被保存为一个隐藏文件,默认是位于系统用户目录下的。当然,配置文件的路径和文件名,可以在 wx.Config 的构造函数里指定的。

       在我们的示例里,程序启动的时候将会查找配置文件,如果没找到,窗口的宽高将会设置为250px。如果在程序里设置了并保存配置,下次程序启动的时候窗口的大小会自动改变为我们的预设值。

#!/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

鼠标手势(Mouse gestures)

       我们可以在一些成功的软件上找到鼠标手势,例如 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’。

可用的 flags:

  • 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)

       创建一个 MouseGestures 对象,第一个参数是窗口对象(wx.Window)的引用,表示鼠标手势将会注册到这个窗口对象上。第二个参数定义注册鼠标手势的方式,True 表示自动,False 表示手动。最后一个参数指定一个鼠标按键,表示我们要触发鼠标手势时需要按下的键,也可以在 SetMouseButton() 方法里改变为其它键。

 mg.SetGesturePen(wx.Colour(255, 0, 0), 2)

       设置鼠标手势的轨迹,这里是2px 宽的红线。

 mg.SetGesturesVisible(True)

       使鼠标手势的轨迹可见。

 mg.AddGesture(‘DR’, self.OnDownRight)

       注册一个鼠标手势。

Advertisements

一条回应 to “wxPython:经验和技巧”

  1. Ross Wan said

    翻译自:The wxPython tutorial

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

 
%d 博主赞过: