<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 第五章 繪制藍圖 1. [創建你的藍圖](#A.2BUhte.2Bk9gdoSE3Vb.2B-) 1. [重構如何幫我改進我的代碼?](#A.2Bkc1nhFmCT1VeLmIRZTmP22IRdoRO43gB.2Fx8-) 1. [一個重構的例子](#A.2BTgBOKpHNZ4R2hE.2BLW1A-) 2. [開始重構](#A.2BXwBZy5HNZ4Q-) 3. [進一步重構](#A.2Bj9tOAGtlkc1nhA-) 2. [如何保持模型(Model)與視圖(View)分離?](#A.2BWYJPVU.2FdYwFqIVeL.28Model.29.2BTg6Jxlb.2B.28View.29.2BUgZ5u.2F8f-) 1. [MVC(Model-View-Controller)系統是什么?](#MVC.28Model-View-Controller.29.2BfPt.2B32YvTsBOSP8f-) 2. [一個wxPython模型:PyGridTableBase](#A.2BTgBOKg-wxPython.2BaiFXi.2F8a-PyGridTableBase) 3. [自定義模型](#A.2Bgepbmk5JaiFXiw-) 3. [如何對一個GUI程序進行單元測試?](#A.2BWYJPVVv5TgBOKg-GUI.2Begtej4.2FbiExTVVFDbUuL1f8f-) 1. [unittest模塊](#unittest.2BaiFXVw-) 2. [一個unittest范例](#A.2BTgBOKg-unittest.2BgwNPiw-) 3. [測試用戶事件](#A.2BbUuL1XUoYjdOi072-) 4. [本章小結](#A.2BZyx64FwPftM-) 眾所周知,`GUI`代碼是難于閱讀和維護的,并且看上去總是一塌糊涂。本章我們將討論三個馴服你的`UI`代碼的技術。我們將討論重構代碼以使其易于閱讀、管理和維護。另一個方面是顯示代碼和基本的處理對象之間的處理,這也是`UI`程序員頭痛的地方。`MVC`(`Model`/`View`/`Controller`)設計模式是這樣一種結構,它保持顯示和數據分離以便各自的改變相互不影響。最后,我們討論對你的`wxPython`代碼進行單元測試的技術。盡管本章的所有例子將使用`wxPython`,但是其中的多數原則是可以應用到任何`UI`工具的,代碼的設計和體系結構就是所謂的藍圖。一個深思熟慮的藍圖將使得你的應用程序建造起來更簡單和更易維護。本章的建議將幫助你為你的程序設計一個可靠的藍圖。 ## 重構如何幫我改進我的代碼? 好的程序員為什么也會寫出不好的界面或界面代碼?這有很多原因。甚至一個簡單的用戶界面可能都要求很多行來顯示屏幕上的所有元素。程序員通常試圖用單一的方法來實現這些,這種方法迅速變得長且難于控制。此外界面代碼是很容易受到不斷改變的影響的,除非你對管理這些改變訓練有素。由于寫界面代碼可能是很枯燥的,所以界面程序員經常會使用設計工具來生成代碼。機器生成的代碼相對于手工代碼來說是很差。 原則上講,保持`UI`代碼在控制之下是不難的。關鍵是重構或不斷改進現有代碼的設計和結構。重構的目的是保持代碼在以后易讀和易于維護。下表5.1說明了在重構時需要記住的一些原則。最重要的是要記住,某人以后可能會不得不讀和理解你的代碼。努力讓他人的生活更容易些,畢竟那有可能是你。 **表5.1** **重構的一些重要原則** 不要重復:你應該避免有多個相同功能的段。當這個功能需要改變時,這維護起來會很頭痛。 一次做一件事情:一個方法應該并且只做一件事情。各自的事件應該在各自的方法中。方法應該保持短小。 嵌套的層數要少:盡量使嵌套代碼不多于2或3層。對于一個單獨的方法,深的嵌套也是一個好的選擇。 避免字面意義上的字符串和數字:字面意義上的字符串和數字應使其出現在代碼中的次數最小化。一個好的方法是,把它們從你的代碼的主要部分中分離出來,并存儲于一個列表或字典中。 這些原則在`Python`代碼中特別重要。因為`Python`的縮進語法、小而簡潔的方法是很容易去讀的。然而,長的方法對于理解來說是更困難的,尤其是如果它們在一個屏幕上不能完全顯示出來時。類似的,`Python`中的深的嵌套使得跟蹤代碼塊的開始和結尾很棘手。然而,`Python`在避免重復方面是十分好的一種語言,特別是因為函數和方法或以作為參數傳遞。 ### 一個重構的例子 為了展示給你如何在實際工作中應用這些原則,我們將看一個重構的例子。圖5.1顯示了一個窗口,它可用作訪問微軟`Access`類數據庫的前端。 圖5.1 ![](https://box.kancloud.cn/2016-08-21_57b99609d7320.gif) 它的布置比之前我們的所見過的那些要復雜一些。但是按現實中的應用程序的標準,它仍然十分簡單。例5.1的代碼的結構很差。 **例5.1** **產生圖5.1的沒有重構的代碼** ``` #!/usr/bin/env python import wx class RefactorExample(wx.Frame): def __init__(self, parent, id): wx.Frame.__init__(self, parent, id, 'Refactor Example',size=(340, 200)) panel = wx.Panel(self, -1) panel.SetBackgroundColour("White") prevButton = wx.Button(panel, -1, " PREV", pos=(80, 0)) self.Bind(wx.EVT_BUTTON, self.OnPrev, prevButton) nextButton = wx.Button(panel, -1, "NEXT ", pos=(160, 0)) self.Bind(wx.EVT_BUTTON, self.OnNext, nextButton) self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) menuBar = wx.MenuBar() menu1 = wx.Menu() openMenuItem = menu1.Append(-1, " ", "Copy in status bar") self.Bind(wx.EVT_MENU, self.OnOpen, openMenuItem) quitMenuItem = menu1.Append(-1, " ", "Quit") self.Bind(wx.EVT_MENU, self.OnCloseWindow, quitMenuItem) menuBar.Append(menu1, " ") menu2 = wx.Menu() copyItem = menu2.Append(-1, " ", "Copy") self.Bind(wx.EVT_MENU, self.OnCopy, copyItem) cutItem = menu2.Append(-1, "C ", "Cut") self.Bind(wx.EVT_MENU, self.OnCut, cutItem) pasteItem = menu2.Append(-1, "Paste", "Paste") self.Bind(wx.EVT_MENU, self.OnPaste, pasteItem) menuBar.Append(menu2, " ") self.SetMenuBar(menuBar) static = wx.StaticText(panel, wx.NewId(), "First Name", pos=(10, 50)) static.SetBackgroundColour("White") text = wx.TextCtrl(panel, wx.NewId(), "", size=(100, -1), pos=(80, 50)) static2 = wx.StaticText(panel, wx.NewId(), "Last Name", pos=(10, 80)) static2.SetBackgroundColour("White") text2 = wx.TextCtrl(panel, wx.NewId(), "", size=(100, -1), pos=(80, 80)) firstButton = wx.Button(panel, -1, "FIRST") self.Bind(wx.EVT_BUTTON, self.OnFirst, firstButton) menu2.AppendSeparator() optItem = menu2.Append(-1, " ", "Display Options") self.Bind(wx.EVT_MENU, self.OnOptions, optItem) lastButton = wx.Button(panel, -1, "LAST", pos=(240, 0)) self.Bind(wx.EVT_BUTTON, self.OnLast, lastButton) # Just grouping the empty event handlers together def OnPrev(self, event): pass def OnNext(self, event): pass def OnLast(self, event): pass def OnFirst(self, event): pass def OnOpen(self, event): pass def OnCopy(self, event): pass def OnCut(self, event): pass def OnPaste(self, event): pass def OnOptions(self, event): pass def OnCloseWindow(self, event): self.Destroy() if __name__ == '__main__': app = wx.PySimpleApp() frame = RefactorExample(parent=None, id=-1) frame.Show() app.MainLoop() ``` 根據重構原則,上面這段代碼有一點是做到了,就是沒有深的嵌套。其它都沒有做到。 為了讓你有一個關于如何調整的一個思想,我們將把所有的按鈕代碼分別放到各自的方法中。 下表5.2歸納了我們重構原代碼應解決的問題 表5.2 **原則** **代碼要重構的地方** 不要重復幾個模式不斷重復,包括“增加按鈕,關聯一個方法”, * “增加菜單項并關聯一個方法”,“創建成對的標簽/文本條目” 一次只做一件事代碼做了幾件事情。除了基本的框架(`frame)`設置外,它創建了菜單欄,增加了按 * 鈕,增加了文本域。更糟糕的是,功能在代碼中混在一起。 避免避免字面意 義上的字符串和數字在構造器中每個按鈕、菜單項和文本框都有一個文字字符串和坐標常量 ### 開始重構 例5.2中只包含了前面用于創建按鍵欄的代碼。作為重構的第一步,我們在例5.2中把例5.1中創建按鈕欄這些代碼抽出來放在了它自己的方法中: **例5.2** **按鈕欄作為一個單獨的方法** ``` def createButtonBar(self): firstButton = wx.Button(panel, -1, "FIRST") self.Bind(wx.EVT_BUTTON, self.OnFirst, firstButton) prevButton = wx.Button(panel, -1, " PREV", pos=(80, 0)) self.Bind(wx.EVT_BUTTON, , self.OnPrev, prevButton) nextButton = wx.Button(panel, -1, "NEXT ", pos=(160, 0)) self.Bind(wx.EVT_BUTTON, self.OnNext, nextButton) lastButton = wx.Button(panel, -1, "LAST", pos=(240, 0)) self.Bind(wx.EVT_BUTTON, self.OnLast, lastButton) ``` 向上面這樣把代碼分離出后,所有按鈕添加代碼之間的共性就很容易看出來了。我們可以把添加按鈕的代碼寫成一個公用的方法來調用 ,而避免了重復。如例5.3所示: **例5.3** **一個公用的改進了的按鈕欄方法** ``` def createButtonBar(self, panel): self.buildOneButton(panel, "First", self.OnFirst) self.buildOneButton(panel, " PREV", self.OnPrev, (80, 0)) self.buildOneButton(panel, "NEXT ", self.OnNext, (160, 0)) self.buildOneButton(panel, "Last", self.OnLast, (240, 0)) def buildOneButton(self, parent, label, handler, pos=(0,0)): button = wx.Button(parent, -1, label, pos) self.Bind(wx.EVT_BUTTON, handler, button) return button ``` 例5.3代替例5.2有兩個好處。第一,簡短的方法和有意義的方法名使得代碼的可讀性更清晰了。第二,它避免了局部變量(誠然,你也可以通過使用`ID`來避免使用局部變量,但那容易導致重復的`ID`問題)。不使用局部變量是有好處的,它減少了代碼的復雜程序,并且也因為這樣幾乎排除了通常由剪切和粘貼部分代碼而忘記了改變所有變量的名字帶來的錯誤。(在實際的應用中,你可能需要存儲按鈕為實例變量以備后來訪問,但是本例不需要。)另外,`buildOneButton()`方法容易放進一個工具模塊中并可以在別的框架或項目中重用。 ### 進一步重構 上面的例子,已經得到了很多的改善。但是在多處仍有許多常量。其一,就是用于定位的點坐標,當另一 個按鈕被添加到按鈕欄時可能使代碼產生錯誤,尤其是新的按鈕被放置在按鈕欄的中間。因此讓我們再往 前進一步,我們把這些字面意義上的數據從處理中分離出來。下例5.4展示了一個用于創建按鈕的數據驅 動機制。 例5.4 使用分離自代碼的數據創建按鈕 ``` def buttonData(self): return (("First", self.OnFirst), (" PREV", self.OnPrev), ("NEXT ", self.OnNext), ("Last", self.OnLast)) def createButtonBar(self, panel, yPos=0): xPos = 0 for eachLabel, eachHandler in self.buttonData(): pos = (xPos, yPos) button = self.buildOneButton(panel, eachLabel, eachHandler, pos) xPos += button.GetSize().width def buildOneButton(self, parent, label, handler, pos=(0,0)): button = wx.Button(parent, -1, label, pos) self.Bind(wx.EVT_BUTTON, handler, button) return button ``` 在例5.4中,用于不同按鈕的數據被存儲在內嵌于`buttonData()`方法的元組中。所選的數據結構及常量方 法的使用不是必然的。數據也可以被存儲在一個類級的變量或模塊級的變量中,而非一個方法的結果,或 存儲于一個外部的文件中。使用方法的好處就是,如果你的按鈕數據存儲在另一個地方而不是方法中的話 ,只需要改變這個方法而使它返回外部的數據。 `createButtonBar()`方法遍歷`buttonData()`返回的列表并創建相關數據的按鈕。這個方法集依次根據列表 自動計算按鈕的x坐標。這是很有幫助的,因為它保證了代碼中按鈕的次序與將顯示在屏幕中的次序一樣 ,使得代碼更清晰并減少出錯的機會。如果你需要將一個按鈕添加到按鈕欄的中間的話,你只需把數據添 加到這個列表的中間,這個代碼確保了所加按鈕被放置在中間。 數據的分離有其它的好處。在一個更精心制作的例子中,數據可以被存儲到一個外部的資源或`XML`文件中 。這使得在改變界面的時候不用去關心代碼,并且使國際化更容易,很容易改變文本。移除了數據以后, `createButtonBar`方法現在成了一個公用方法了,它可以容易地在其它框架或項目中被重用。 在經過整合相同的過程,并從菜單和文本域代碼中分離出數據后,所得的結果顯示在如下例5.5中。 例5.5 一個重構的例子 ``` #!/usr/bin/env python import wx class RefactorExample(wx.Frame): def __init__(self, parent, id): wx.Frame.__init__(self, parent, id, 'Refactor Example',size=(340, 200)) panel = wx.Panel(self, -1) panel.SetBackgroundColour("White") self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) self.createMenuBar() #簡化的init方法 self.createButtonBar(panel) self.createTextFields(panel) def menuData(self): #菜單數據 return (("&File", ("&Open", "Open in status bar", self.OnOpen), ("&Quit", "Quit", self.OnCloseWindow)), ("&Edit", ("&Copy", "Copy", self.OnCopy), ("C&ut", "Cut", self.OnCut), ("&Paste", "Paste", self.OnPaste), ("", "", ""), ("&Options", "DisplayOptions", self.OnOptions))) #創建菜單 def createMenuBar(self): menuBar = wx.MenuBar() for eachMenuData in self.menuData(): menuLabel = eachMenuData[0] menuItems = eachMenuData[1:] menuBar.Append(self.createMenu(menuItems), menuLabel) self.SetMenuBar(menuBar) def createMenu(self, menuData): menu = wx.Menu() for eachLabel, eachStatus, eachHandler in menuData: if not eachLabel: menu.AppendSeparator() continue menuItem = menu.Append(-1, eachLabel, eachStatus) self.Bind(wx.EVT_MENU, eachHandler, menuItem) return menu def buttonData(self): #按鈕欄數據 return (("First", self.OnFirst), ("<< PREV", self.OnPrev), ("NEXT >>", self.OnNext), ("Last", self.OnLast)) #創建按鈕 def createButtonBar(self, panel, yPos = 0): xPos = 0 for eachLabel, eachHandler in self.buttonData(): pos = (xPos, yPos) button = self.buildOneButton(panel, eachLabel,eachHandler, pos) xPos += button.GetSize().width def buildOneButton(self, parent, label, handler, pos=(0,0)): button = wx.Button(parent, -1, label, pos) self.Bind(wx.EVT_BUTTON, handler, button) return button def textFieldData(self): #文本數據 return (("First Name", (10, 50)), ("Last Name", (10, 80))) #創建文本 def createTextFields(self, panel): for eachLabel, eachPos in self.textFieldData(): self.createCaptionedText(panel, eachLabel, eachPos) def createCaptionedText(self, panel, label, pos): static = wx.StaticText(panel, wx.NewId(), label, pos) static.SetBackgroundColour("White") textPos = (pos[0] + 75, pos[1]) wx.TextCtrl(panel, wx.NewId(), "", size=(100, -1), pos=textPos) # 空的事件處理器放在一起 def OnPrev(self, event): pass def OnNext(self, event): pass def OnLast(self, event): pass def OnFirst(self, event): pass def OnOpen(self, event): pass def OnCopy(self, event): pass def OnCut(self, event): pass def OnPaste(self, event): pass def OnOptions(self, event): pass def OnCloseWindow(self, event): self.Destroy() if __name__ == '__main__': app = wx.PySimpleApp() frame = RefactorExample(parent=None, id=-1) frame.Show() app.MainLoop() ``` 從例5.1改變到例5.5,沒有費多少力,但我們所得到的卻是很多——代碼非常的清楚且減少了出錯的機會。代碼的布置與數據的布置在邏輯上是匹配的。那些普通的做法(它們劣質的代碼結構可能導致錯誤——如采用大量的復制和粘貼來創建新的對象)已經被去掉。多數函數現在可以很容易地被移到一個超類或公用模塊中,以保存代碼便于以后繼續利用。另外,數據的分離使得把這個布局作為不同數據的模板很容易,包括國際化的數據。 重構雖說完成了,但是例5.5中的代碼仍然忽略了一些重要的事情:實際用戶的數據。你的應用程序要做很多事依賴于處理數據響應用戶要求。你的程序的結構還可以向著靈活性和穩定性方向發展。`MVC`模式對于管理界面和數據之間的交互是公認的標準。 ## 如何保持模型(Model)與視圖(View)分離? 最早可追溯到1970年代后期和`Smalltalk`-80語言,`MVC`模式大概是最早明確指出面向對象設計的模式。它是最流行的一種,被幾乎所有`GUI`工具包所采用。`MVC`模式是結構化程序的標準,包括處理和顯示信息。 ### MVC(Model-View-Controller)系統是什么? `MVC`系統有三個子系統。`Model`包含經常被調用的業務邏輯或由你的系統處理的所有數據和信息。`View`包含顯示數據的對象,`Controller`管理與用戶的交互(`Controller`處于`Model`和`view`中間)。下表5.3歸納了這些組分。 **表5.3** **標準`MVC`體系的組成** **組分** `Model`:包含業務邏輯,包含所有由系統處理的數據。它包括一個針對外部存儲(如一個數據庫)的接口。通常模型(`model)`只暴露一個公共的`API`給其它的組分。 `View`:包含顯示代碼。這個窗口部件實際用于放置用戶在視圖中的信息。在`wxPython`中,處于`wx.Window`層級中的所有的東西都是視圖(`view)`子系統的一部分。 `Controller`:包含交互邏輯。該代碼接受用戶事件并確保它們被系統處理。在`wxPython`中,這個子系統由`wx.EvtHandler`層級所代表。 在現代的`UI`工具包中,`View`和`Controller`組分是被集成在一起的。這是因為`Controller`組分自身需要被顯示在屏幕上,并且因為經常性的你想讓顯示數據的窗口部件也響應用戶事件。在`wxPython`中,這種關系實際上已經被放置進去了(所有的`wx.Window`對象也都是`wx.EvtHandler`的子類),這意味著它們同時具有`View`元素和`Controller`元素的功能。相比之下,大部分`web`應用架構對于`View`和`Controller`有更嚴格的分離,因為其交互邏輯發生在服務器的后臺。 圖5.2中顯示了數據和信息是如何在`MVC`體系中傳遞的。 ![](https://box.kancloud.cn/2016-08-21_57b99609ea315.gif) 一個事件通知被`Controller`系統處理(它把事件通知放到一個合適的地方)。如我們在第三章中所看到的,`wxPython`使用`wx.EvtHandler`的方法`ProcessEvent()`管理這個機制。在一個嚴格的`MVC`?杓浦校愕拇砥骱贍鼙簧髟諞桓齙ザ賴目刂破鞫韻籩校竊誑蚣芾嘧隕碇小? 對于事件的響應,這個模型(`model)`對象可以對應用程序數據做一些處理。當處理完成時,模型對象發送一個更新通知。如果這兒有一個控制器(`controller)`對象,那么該通知通常發送回這個控制器,同時這個控制器對象通知視圖(`view)`對象自我更新。在一個較小的系統或一個較簡單的體系中,通知通常直接被視圖對象所接受。在`wxPython`中,來自于模型的更新的關鍵在于你。你的選擇包括從模型或控制器顯式地引發自定義的`wxPython`事件,使模型維護的對象的列表接受更新通知,或使與模型關聯的視圖接受更新通知。 一個成功的`MVC`設計的關鍵不在于每個對象都彼此了解。相反,一個成功的`MVC`程序,它的不同部分之間顯式地隱藏了一些東西。其目的是使系統最低限度地交互,和方法之間的明確的界定。尤其,這個`Model`組分應該被完全從`View`和`Controller`中脫離出來。你應該只改變那些系統而不改變你的`Model`類。理想上來講,你甚至應該能夠使用相同`Model`類來驅動非`wxPython`的界面,但是那很難。 從`View`方面,你應該能夠在`Model`對象的實現中做改變而不改變`View`或`Controller`。而`View`依賴于某些公共的方法的存在,它不應該看見`Model`內私有的東西。無可否認,這在`Python`中實施是有困難的,但有一個方法可以幫助我們,那就是創建一個抽象的`Model`類,它定義`View`可以看見的`API`。`Model`的子類可以扮演一個內部的類的代理而被改變,或可以簡單地自身包含內部的工作。這第一個方案更結構化些,第二個更容易實現。 下一節,我們將看一看內建于`wxPython`中的`Model`類中的一個:`wx.grid.PyGridTableBase`。這個類使得在一個`MVC`設計架構中使用`grid`控件成為可能。這之后,我們將關注一下對于一個定制的窗口部件建造和使用定制的模型類。 ### 一個wxPython模型:PyGridTableBase 類`wx.grid.Grid`是一個電子表格式樣的`wxPython`控件。下圖5.3顯示了它的外觀。 ![](https://box.kancloud.cn/2016-08-21_57b9960a084ce.gif) 這個網格(`grid)`控件有很多有趣的特點,包括能夠在一個單元一個單元的基礎上創建自定義的渲染器和編輯器,以及可拖拽的行和列。這些特性將在第十三章中做更詳細的討論。在這一章,我們將針對基礎和展示如何使用一個模型去填充網格。例5.6顯示在一個網格中設置單元值的簡單的非模型方法。在此例中,網格中的值是1984年芝加哥小熊隊的陣容。 **例5.6** **填充網格(沒有使用模型)** ``` import wx import wx.grid class SimpleGrid(wx.grid.Grid): def __init__(self, parent): wx.grid.Grid.__init__(self, parent, -1) self.CreateGrid(9, 2) self.SetColLabelValue(0, "First") self.SetColLabelValue(1, "Last") self.SetRowLabelValue(0, "CF") self.SetCellValue(0, 0, "Bob") self.SetCellValue(0, 1, "Dernier") self.SetRowLabelValue(1, "2B") self.SetCellValue(1, 0, "Ryne") self.SetCellValue(1, 1, "Sandberg") self.SetRowLabelValue(2, "LF") self.SetCellValue(2, 0, "Gary") self.SetCellValue(2, 1, "Matthews") self.SetRowLabelValue(3, "1B") self.SetCellValue(3, 0, "Leon") self.SetCellValue(3, 1, "Durham") self.SetRowLabelValue(4, "RF") self.SetCellValue(4, 0, "Keith") self.SetCellValue(4, 1, "Moreland") self.SetRowLabelValue(5, "3B") self.SetCellValue(5, 0, "Ron") self.SetCellValue(5, 1, "Cey") self.SetRowLabelValue(6, "C") self.SetCellValue(6, 0, "Jody") self.SetCellValue(6, 1, "Davis") self.SetRowLabelValue(7, "SS") self.SetCellValue(7, 0, "Larry") self.SetCellValue(7, 1, "Bowa") self.SetRowLabelValue(8, "P") self.SetCellValue(8, 0, "Rick") self.SetCellValue(8, 1, "Sutcliffe") class TestFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "A Grid",size=(275, 275)) grid = SimpleGrid(self) if __name__ == '__main__': app = wx.PySimpleApp() frame = TestFrame(None) frame.Show(True) app.MainLoop() ``` 在例5.6中,我們產生了`SimpleGrid`類,它是`wxPython`類`wx.grid.Grid`的子類。如前所述,`wx.grid.Grid`有很多種方法,這我們以后再討論。現在,我們只關心方法`SetRowLabelValue()`,`SetColLabelValue()`和`SetCellValue()`,它們實際上設置顯示在網格中的值。通過對比圖5.3和例5.6你可以明白,`SetCellValue()`方法要求一個行索引、一個列索引和一個值。而其它兩個方法要求一個索引和一個值。 上面的代碼使用了`set`***的方法直接把值賦給了網格。然而如果對于一個較大的網格使用這種方法,代碼將冗長乏味,并很容易導致錯誤的出現。即使我們創建公用程序以減輕負擔,但是根據重構的原則,代碼仍有問題。數據與顯示混在一起,對于將來代碼的修改是困難的,如增加一列或更換數據。 解決的答案就是`wx.grid.PyGridTableBase`。根據之前我們所見過的其它的類,前綴`Py`表明這是一個封裝了C++類的特定的`Python`類。就像我們在第三章中所見的`PyEvent`類,`PyGridTableBase`的實現是基于簡單封裝一個`wxWidgets` C++類,這樣的目的是使得能夠繼續聲明該類(`Python`形式的類)的子類。`PyGridTableBase`對于網格是一個模型類。也就是說,網格對象可能使用`PyGridTableBase`所包含的方法來繪制自身,而不必了解有關繪制數據的內部結構。 **`PyGridTableBase`的方法** `wx.grid.PyGridTableBase`有一些方法,它們中的許多你不會用到。這個類是抽象的,并且不能被直接實 例化。每次你創建一個`PyGridTableBase`時,有五個必要的方法必須被定義。表5.4說明了這些方法。 **表5.4** **`wx.grid.PyGridTableBase`的必須的方法** `GetNumberRows()`:返回一個表明`grid`中行數的整數。 `GetNumberCols()`:返回一個表明`grid`中列數的整數。 `IsEmptyCell(row`, `col)`:如果索引(`row`,`col)`所表示的單元是空的話,返回`True`。 `GetValue(row`, `col)`:返回顯示在單元(`row`,`col)`中的值。 `SetValue(row`, `col`,`value)`:設置單元(`row`,`col)`中的值。如果你想要只讀模式,你仍必須包含這個方法,但是你可以在該函數中使用`pass`。 表(`table)`通過使用網格(`grid)`的`SetTable()`方法被附加在`grid`上。在屬性被設置后,`grid`對象將調用表 的方法來得到它繪制網格所需要的信息。`grid`不再顯式使用`grid`的方法來設置值。 **使用`PyGridTableBase`** 一般情況下,有兩種使用`PyGridTableBase`的方法。你可以顯式地使你的模型類是`PyGridTableBase`的子類,或你可以創建一個單獨的`PyGridTableBase`的子類,它關聯你的實際的模型類。當你的數據不是太復雜 的時候,第一種方案較簡單并且直觀。第二種方案需要對模型和視圖做很好的分離,如果你的數據復雜的話,這第二種方案是更好的。如果你有一個預先存在的數據類,你想把它用于`wxPython`,那么這第二種方案也是更好的,因為這樣你可以創建一個表而不用去改變已有的代碼。在下面一節我們將展示包含這兩種方案的一個例子。 **使用`PyGridTableBase`:特定于應用程序(不通用)的子類** 我們的第一個例子將使用`PyGridTableBase`的一個特定于應用程序的子類作為我們的模型。由于我們小熊 隊陣容的相對簡單些,所以我們使用它。我們把這些數據組織到一個派生自`PyGridTableBase`的類。我們 把這些實際的數據配置在一個二維`Python`列表中,并且配置另外的方法來從列表中讀。下例5.7展示了生 成自一個模型類的小熊隊的陣容。 **例5.7** **生成自`PyGridTableBase`模型的一個表** ``` import wx import wx.grid class LineupTable(wx.grid.PyGridTableBase): data = (("CF", "Bob", "Dernier"), ("2B", "Ryne", "Sandberg"), ("LF", "Gary", "Matthews"), ("1B", "Leon", "Durham"), ("RF", "Keith", "Moreland"), ("3B", "Ron", "Cey"), ("C", "Jody", "Davis"), ("SS", "Larry", "Bowa"), ("P", "Rick", "Sutcliffe")) colLabels = ("Last", "First") def __init__(self): wx.grid.PyGridTableBase.__init__(self) def GetNumberRows(self): return len(self.data) def GetNumberCols(self): return len(self.data[0]) - 1 def GetColLabelValue(self, col): return self.colLabels[col] def GetRowLabelValue(self, row): return self.data[row][0] def IsEmptyCell(self, row, col): return False def GetValue(self, row, col): return self.data[row][col + 1] def SetValue(self, row, col, value): pass class SimpleGrid(wx.grid.Grid): def __init__(self, parent): wx.grid.Grid.__init__(self, parent, -1) self.SetTable(LineupTable()) #設置表 class TestFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "A Grid",size=(275, 275)) grid = SimpleGrid(self) if __name__ == '__main__': app = wx.PySimpleApp() frame = TestFrame(None) frame.Show(True) app.MainLoop() ``` 在例5.7中,我們已經定義了所有必須的`PyGridTableBase`方法,并加上了額外的方法`GetColLabelValue()`和`GetRowLabelValue()`。希望你不要對這兩個額外的方法感到詫異,這兩個額外的方法使得表(`table)`能 夠分別指定行和列的標簽。在重構一節中,使用模型類的作用是將數據與顯示分開。在本例中,我們已經 把數據移入了一個更加結構化的格式,它能夠容易地被分離到一個外部文件或資源中(數據庫容易被增加到這里)。 **使用`PyGridTableBase`:一個通用的例子** 實際上,上面的例子很接近一個通用的能夠讀任何二維`Python`列表的表了。下列5.8展示這通用的模型的 外觀: **例5.8** **一個關于二維列表的通用的表** ``` import wx import wx.grid class GenericTable(wx.grid.PyGridTableBase): def __init__(self, data, rowLabels=None, colLabels=None): wx.grid.PyGridTableBase.__init__(self) self.data = data self.rowLabels = rowLabels self.colLabels = colLabels def GetNumberRows(self): return len(self.data) def GetNumberCols(self): return len(self.data[0]) def GetColLabelValue(self, col): if self.colLabels: return self.colLabels[col] def GetRowLabelValue(self, row): if self.rowLabels: return self.rowLabels[row] def IsEmptyCell(self, row, col): return False def GetValue(self, row, col): return self.data[row][col] def SetValue(self, row, col, value): pass ``` `GenericTable`類要求一個數據的二維列表和一個可選的行和列標簽列表。這個類適合被導入任何`wxPython` 程序中。使用一個做了微小改變的格式,我們現在可以使用這通用的表來顯示陣容,如下例5.9所示: **例5.9** **使用這通用的表來顯示陣容** ``` import wx import wx.grid import generictable data = (("Bob", "Dernier"), ("Ryne", "Sandberg"), ("Gary", "Matthews"), ("Leon", "Durham"), ("Keith", "Moreland"), ("Ron", "Cey"), ("Jody", "Davis"), ("Larry", "Bowa"), ("Rick", "Sutcliffe")) colLabels = ("Last", "First") rowLabels = ("CF", "2B", "LF", "1B", "RF", "3B", "C", "SS", "P") class SimpleGrid(wx.grid.Grid): def __init__(self, parent): wx.grid.Grid.__init__(self, parent, -1) tableBase = generictable.GenericTable(data, rowLabels, colLabels) self.SetTable(tableBase) class TestFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "A Grid", size=(275, 275)) grid = SimpleGrid(self) if __name__ == '__main__': app = wx.PySimpleApp() frame = TestFrame(None) frame.Show(True) app.MainLoop() ``` **使用`PyGridTableBase`:一個獨立的模型類** 至于避免重復性,有另一種使用`PyGridTableBase`的方法值得在這展示給大家。這就是我們早先提到的第 二種方案,數據在一個單獨的模型類中,通過`PyGridTableBase`來訪問。`Python`的自我檢查功能在這是非 常有用的,使你能夠在每列顯示一個屬性的列表,然后使用內建函數`getattr()`去獲取實際的值。在這種 情況下,模型要求一個元素的列表。在`wxPython`中,使用單獨的模型對象結構化你的程序有一個大的優勢 。在通常的情形下,對于一個`grid`,你只能調用`SetTable()`一次,如果你想去改變表,你需要創建一個新 的`grid`,那是煩人的。然而,在接下來的例子中,你的`PyGridTableBase`僅存儲了對于你的實際數據類的 實例的引用,這樣一來,以后你就只需通過改變表中基本的數據對象,就可以更新表中的數據為新的數據 了。 下例5.10展示使用了關于陣容條目的單獨的數據類的`PyGridTableBase`。我們省去了框架的另一列表和數據 創建,它們是與前一例子十分類似的。 **例5.10** **使用了一個自定義的數據類的陣容顯示表** ``` import wx import wx.grid class LineupEntry: def __init__(self, pos, first, last): self.pos = pos self.first = first self.last = last class LineupTable(wx.grid.PyGridTableBase): colLabels = ("First", "Last") # 列標簽 colAttrs = ("first", "last") #1 屬性名 def __init__(self, entries): #2 初始化模型 wx.grid.PyGridTableBase.__init__(self) self.entries = entries def GetNumberRows(self): return len(self.entries) def GetNumberCols(self): return 2 def GetColLabelValue(self, col): return self.colLabels[col] #讀列標簽 def GetRowLabelValue(self, col): return self.entries[row].pos #3 讀行標簽 def IsEmptyCell(self, row, col): return False def GetValue(self, row, col): entry = self.entries[row] return getattr(entry, self.colAttrs[col]) #4 讀屬性值 def SetValue(self, row, col, value): pass ``` 說明: **#1**:這個列表包含了一些屬性,它們被引用去按列地顯示每列的值。 **#2**:這個模型要求一個條目的列表,每個條目都是`LineupEntry`的一個實例。(這里我們沒有做任何的錯誤檢查)。 **#3**:要得到行頭的標簽,我們查看條目的`pos`屬性。 **#4**:第一步是根據行來得到正確的條目。所要求的屬性來自于#1中的列表,然后`getattr()`被用來引用實際的值。這個機制是可擴展的,即使是在你不知道該名字是否引用一個屬性或方法的情況下,你也可以通 過檢查 `object` . `attribute` 來看是否其可調用。如果可調用,那么使用通常的`Python`函數語法來調用它 ,并返回它的值。 `grid`類是`wxPython`已有的一個有價值的模型組件來幫助你結構化你的應用程序的一個例子。下一節我們將 討論如何為別的`wxPython`對象創建模型組件。 ### 自定義模型 創建你的模型對象所基于的基本思想是簡單的。首先構造你的數據類而不要擔心它們將如何被顯示。然后 為數據類作一個公共接口,該接口對顯示對象是能夠被訪問的。很明顯,這個工程的大小和復雜性將決定 這個公共聲明的形式如何。在一個小的工程中,使用簡單的對象,可能足夠做簡單的事件和使視圖對象能 夠訪問該模型的屬性。在一個更復雜的對象中,對于這種使用你可能想定義特殊的方法,或創建一個分離 的模型類,該類是視圖唯一看到的東西(正如我們在例5.10所做的)。 為了使視圖由于模型中的改變而被得到通知,你也需要某種機制。例5.11展示了一個簡單的——一個抽象 的基類,你可以用它作為你的模型類的雙親。你可以把這看成`PyGridTableBase`用于當顯示不是一個網格 (`grid)`時的一個類似情況。 **例5.11** **用于更新視圖的一個自定義的模型** ``` class AbstractModel(object): def __init__(self): self.listeners = [] def addListener(self, listenerFunc): self.listeners.append(listenerFunc) def removeListener(self, listenerFunc): self.listeners.remove(listenerFunc) def update(self): for eachFunc in self.listeners: eachFunc(self) ``` 我們這里的`listener`應該是可調用的對象,它需要`self`作為參數(`eachFunc(self)`)——很明顯,`self`的 實際的類可以是不同的,因此你的`listenter`很靈活了。同樣,我們已經將`AbstractModel`配置成一個 `Python`的新型的類,事實上它是`object`的子類。因此本例要求`Python`的版本是`Python2.2`或更高。 我們如何使用這個抽象的類呢?圖5.4顯示了一個新的窗口,類似于我們本章早先講重構時所顯示的窗口 。這個窗口簡單。其中文本框是只讀的。敲擊上面的任一按鈕將在文本框中顯示相應的字符。 ![](img/w5.4.gif) 顯示這個窗口的程序使用了一個簡單的`MVC`結構。按鈕的處理器方法引起這個模型中的變化,模型中的更 新導致文本域的改變。例5.12展示了這個細節。 **例5.12** ``` #!/usr/bin/env python import wx import abstractmodel class SimpleName(abstractmodel.AbstractModel): def __init__(self, first="", last=""): abstractmodel.AbstractModel.__init__(self) self.set(first, last) def set(self, first, last): self.first = first self.last = last self.update() #1 更新 class ModelExample(wx.Frame): def __init__(self, parent, id): wx.Frame.__init__(self, parent, id, 'Flintstones', size=(340, 200)) panel = wx.Panel(self) panel.SetBackgroundColour("White") self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) self.textFields = {} self.createTextFields(panel) #------------------------------- #2 創建模型 self.model = SimpleName() self.model.addListener(self.OnUpdate) #------------------------------- self.createButtonBar(panel) def buttonData(self): return (("Fredify", self.OnFred), ("Wilmafy", self.OnWilma), ("Barnify", self.OnBarney), ("Bettify", self.OnBetty)) def createButtonBar(self, panel, yPos = 0): xPos = 0 for eachLabel, eachHandler in self.buttonData(): pos = (xPos, yPos) button = self.buildOneButton(panel, eachLabel, eachHandler, pos) xPos += button.GetSize().width def buildOneButton(self, parent, label, handler, pos=(0,0)): button = wx.Button(parent, -1, label, pos) self.Bind(wx.EVT_BUTTON, handler, button) return button def textFieldData(self): return (("First Name", (10, 50)), ("Last Name", (10, 80))) def createTextFields(self, panel): for eachLabel, eachPos in self.textFieldData(): self.createCaptionedText(panel, eachLabel, eachPos) def createCaptionedText(self, panel, label, pos): static = wx.StaticText(panel, wx.NewId(), label, pos) static.SetBackgroundColour("White") textPos = (pos[0] + 75, pos[1]) self.textFields[label] = wx.TextCtrl(panel, wx.NewId(), "", size=(100, -1), pos=textPos, style=wx.TE_READONLY) def OnUpdate(self, model): #3 設置文本域 self.textFields["First Name"].SetValue(model.first) self.textFields["Last Name"].SetValue(model.last) #------------------------------------------- #4 響應按鈕敲擊的處理器 def OnFred(self, event): self.model.set("Fred", "Flintstone") def OnBarney(self, event): self.model.set("Barney", "Rubble") def OnWilma(self, event): self.model.set("Wilma", "Flintstone") def OnBetty(self, event): self.model.set("Betty", "Rubble") #--------------------------------------------- def OnCloseWindow(self, event): self.Destroy() if __name__ == '__main__': app = wx.PySimpleApp() frame = ModelExample(parent=None, id=-1) frame.Show() app.MainLoop() ``` 說明: **#1**:這行執行更新 **#2**:這兩行創建這個模型對象,并且把`OnUpdate()`方法注冊為一個`listener`。現在當更新被調用時,`OnUpdate()`方法將被調用。 **#3**:`OnUpdate()`方法本身簡單地使用模型更新后的值來設置文本域中的值。該方法的代碼中可以使用`self.model`這個實例來代替`model`(它們是同一個對象)。使用方法作為參數,代碼是更健壯的,在這種情況下,同樣的代碼可以監聽多個對象。 **#4**:按鈕敲擊的處理器改變模型對象的值,它觸發更新。 在這樣一個小的例子中,使用模型更新機制似乎有點大才小用了。為什么按鈕處理器不能直接設置文本域的值呢。然而,當這個模型類存在一個更復雜的內部狀況和處理時,這個模型機制就變得更有價值了。例如,你將能夠將內部的分配從一個`Python`字典改變為一個外部的數據庫,而不在視圖中做任何改變。 假如你正在處理一個已有的類,而不能或不愿對其做改變,那么`AbstractModel`可以用作該類的代理,方法與例5.10中的陣容所用的方法大致相同。 另外,`wxPython`包含兩個單獨的類似`MVC`更新機制的實現,它們比我們這里說明的這個有更多的特性。第一個是模塊`wx.lib.pubsub`,它在結構上與我們先前給出的類`AbstractModel`十分相似。名為`Publisher`的模型類使得對象能夠監聽僅特定類型的消息。另一個更新系統是`wx.lib.evtmgr.eventManager`,它建立在 `pubsub`之上,并且有一些額外的特性,包括一個更精心的面向對象的設計和事件關聯的連接或去除的易用性。 ## 如何對一個GUI程序進行單元測試? 好的重構和`MVC`設計模式的一個主要的好處是,它使得使用“單元測試”來驗證你程序的性能更容易了。單元測試是對你的程序的單個的特定功能的測試。由于重構和`MVC`設計模式兩者的使用,使得你的程序被分成了小的塊,因此你更容易針對你程序的個別的部分寫特定的單元測試。當重構的時候,結合使用單元測試是特別有用的,因為完整的單元測試使得在你移動你的代碼后,你能夠檢驗你是否引入了任何錯誤。 接下來的難題是在單元測試中如何測試`UI`代碼。測試一個模型是相對簡單的,因為模型的大部分功能不依賴于用戶的輸入。測試界面本身的功能比較困難,因為界面的行為依賴于用戶的行為,而用戶的行為又是難以封裝在內的。在這一節,我們將給你展示如何在`wxPython`中使用單元測試。尤其是在單元測試期間手工產生事件去觸發行為的用法。 ### unittest模塊 當寫用戶測試的時候,使用已有的測試引擎來節省減少重復的寫代碼的運行你的測試是有幫助的。自2.1版以來,`Python`已發布了`unittest`模塊。`unittest`模塊使用一名為`PyUnit`的測試框架(參見`http:`//`pyunit.sourceforge.net`/)。`PyUnit`模塊由`Test`,`TestCase`,`TestSuite`組成。下表5.5說明了這三個組成。 **表5.5** `Test`:被`PyUnit`引擎調用的一個單獨的方法。根據約定,一個測試方法的名字以`test`開頭。測試方法通常執行一些代碼,然后執行一個或多個斷定語句來測試結果是否是預期的。 `TestCase`:一個類,它定義了一個或多個單獨的測試,這些測試共享一個公共的配置。這個類定義在`PyUnit`中以管理一組這樣的測試。`TestCase`在測試前后對每個測試都提供了公共配置支持,確保每個測試分別運行。`TestCase`也定義了一些專門的斷定方法,如`assertEqual`。 `TestSuite`:為了同時被執行而組合在一起的一個或多個`test`方法或`TestCase`對象。當你告訴`PyUnit`去執行測試時,你傳遞給它一個`TestSuite`對象去執行。 單個的`PyUnit`測試可能有三種結果:`success`(成功), `failure`(失敗), 或`error`(錯誤)。`success`表明測試完成,所有的斷定都為真(通過),并且沒有引發錯誤。也就是說得到了我們所希望的結果。`Failure`和`error`表明代碼存在問題。`failure`意味著你的斷定之一返回`false`,表明代碼執行成功了,但是沒有做你預期的事。`error`意味著測試執行到某處,觸發了一個`Python`異常,表明你的代碼沒有運行成功。在單個的測試中,`failure`或`error`一出現,整個測試就終止了,即使在代碼中還有多個斷定要測試,然后測試的執行將移到到下一個單個的測試。 ### 一個unittest范例 下例5.13展示了一個使用`unittest`模塊的范例,其中對例5.12中的模型例子進行測試。 例5.13 對模型例子進行單元測試的一個范例 ``` import unittest import modelExample import wx class TestExample(unittest.TestCase): #1 聲明一個TestCase def setUp(self): #2 為每個測試所做的配置 self.app = wx.PySimpleApp() self.frame = modelExample.ModelExample(parent=None, id=-1) def tearDown(self): #3 測試之后的清除工作 self.frame.Destroy() def testModel(self): #4 聲明一個測試(Test) self.frame.OnBarney(None) self.assertEqual("Barney", self.frame.model.first, msg="First is wrong") #5 對于可能的失敗的斷定 self.assertEqual("Rubble", self.frame.model.last) def suite(): #6 創建一個TestSuite suite = unittest.makeSuite(TestExample, 'test') return suite if __name__ == '__main__': unittest.main(defaultTest='suite') #7 開始測試 ``` 說明: **#1**:聲明`unittest.TestCase`的一個子類。為了最好的使每個測試相互獨立,測試執行器為每個測試創建該類的一個實例。 **#2**: `setUp()`方法在每個測試被執行前被調用。這使得你能夠保證每個對你的應用程序的測試都處在相同的狀態下。這里我們創建了一個用于測試的框架(`frame)`的實例。 **#3** :`tearDown()`方法在每個測試執行完后被調用。這使得你能夠做一些清理工作,以確保從一個測試轉到另一個測試時系統狀態保持一致。通常這里包括重置全局數據,關閉數據庫連接等諸如此類的東東。這里我們對框架調用了`Destroy()`,以強制性地使`wxWidgets`退出,并且為下一個測試保持系統處在一個良好的狀態。 **#4** :測試方法通常以`test`作為前綴,盡管這處于你的控制之下(看#6)。測試方法不要參數。我們這里的測試方法中,通過調用`OnBarney`事件處理器方法來開始測試行為。 **#5** :這行使用`assertEqual()`方法來測試模型對象的改變是否正確。`assertEqual()`要兩個參數,如果這兩個參數不相等,則測試失敗。所有的`PyUnit`斷定方法都有一個可選的參數`msg`,如果斷定失敗則顯示`msg(msg`的默認值幾乎夠表達意思了) **#6**: 這個方法通過簡單有效的機制創建一組測試。`makeSuite()`方法要求一個`Python`的類的對象和一個字符串前綴作為參數,并返回一組測試(包含該類中所有前綴為參數“前綴”的方法)。還有其它的機制,它們使得可以更明確設置測試組中的內容,但是`makeSuite()`方法通過足夠了。我們這里寫的`suite()`方法是一個樣板模板,它可被用在你的所有測試模塊中。 **#7** :這行調用了`PyUnit`的基于文本的執行器。參數是一個方法的名字(該方法返回一測試組)。然后`suite`被執行,并且結果被輸出到控制臺。如果你想使用`GUI`測試執行器,那么這行調用應使用 `unittest.TextTestRunner`的方法而非`unittest.main`。 在控制臺中`PyUnit`測試的結果如下: . * * * `Ran` 1 `test` `in` 0.190s `OK` 這是一個成功的測試。第一行的"."號表明測試成功。每個測試都得到一個字符并顯示在這行。"."表明成功,"F"表明失敗,"E"表明錯誤。然后是一個簡單的列表,其中包含測試的數量、總的測試時間和`OK`,`OK`表明所有測試通過。 對于一個失敗或錯誤的測試,你將得到一堆跟蹤提示(顯示了`Python`得到的錯誤處的情況)。比如,如果你將#5改為`self.assertEqual("Fife", self.frame.model.first)`,我們將得到如下結果: ``` F ====================================================================== FAIL: testModel (__main__.TestExample) ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\wxPyBook\book\1\Blueprint\testExample.py", line 18, in testModel self.assertEqual("Fife", self.frame.model.last) File "c:\python23\lib\unittest.py", line 302, in failUnlessEqual raise self.failureException, \ AssertionError: 'Fife' != 'Rubble' ---------------------------------------------------------------------- Ran 1 test in 0.070s FAILED (failures=1) ``` "F"表明了失敗,“`testModel`”是產生失敗的方法名,下面的跟蹤顯示出18號上的斷定失敗,以及失敗的原因。你一般需要根據這些去找到產生失敗的實際位置。 ### 測試用戶事件 當然,上面的測試還不完整。我們還可以對框架中的`TextField`在模型更新后,其中值的更新情況進行測試。這個測試是很簡單的。另一個你可能想要做的測試是,自動生成按鈕敲擊事件,以及確保正確的處理器被調用。這個測試有點難度。下例5.14展示了一個例子: 例5.14 生成一個用戶事件的測試 ``` def testEvent(self): panel = self.frame.GetChildren()[0] for each in panel.GetChildren(): if each.GetLabel() == "Wilmafy": wilma = each break event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, wilma.GetId()) wilma.GetEventHandler().ProcessEvent(event) self.assertEqual("Wilma", self.frame.model.first) self.assertEqual("Flintstone", self.frame.model.last) ``` 本例開始的幾行尋找一個適當的按鈕(這里是"`Wilmafy`"按鈕)。由于我們沒有顯式地把這些按鈕存儲到變量中,所以我們就需要遍歷`panel`的孩子列表,直到我們找到正確的按鈕。接下來的兩行創建用以被按鈕發送的`wx.CommandEvent`事件,并發送出去。參數`wx.wxEVT_COMMAND_BUTTON_CLICKED`是一個常量,它表示一個事件類型,是個整數值,它被綁定到`EVT_BUTTON`事件綁定器對象。(你能夠在`wx.py`文件中發現這個整數常量)。`wilma.GetId()`的作用是設置產生該事件的按鈕`ID`。至此,該事件已具有了實際`wxPython`事件的所有相關特性。然后我們調用`ProcessEvent()`來將該事件發送到系統中。如果代碼按照計劃工作的話,那么模型的`first`和`last`中的名字將被改變為“`Wilma`” 和 “`Flintstone`”。 通過生成事件,你能夠從頭到尾地測試你的系統的響應性。理論上,你可以生成一個鼠標按下和釋放事件以確保響應按鈕敲擊的按鈕敲擊事件被創建。但是實際上,這不會工作,因為低級的`wx.Events`沒有被轉化為本地系統事件并發送到本地窗口部件。然而,當測試自定義的窗口部件時,可以用到類似于第三章中兩個按鈕控件的處理。此類單元測試,對于你的應用程序的響應性可以給你帶來信心。 ## 本章小結 1、眾所周知,`GUI`代碼看起來很亂且難于維護。這一點可以通過一點努力來解決,當代碼以后要變動時,我們所付出的努力是值得的。 2、重構是對現存代碼的改進。重構的目的有:避免重復、去掉無法理解的字面值、創建短的方法(只做一件事情)。為了這些目標不斷努力將使你的代碼更容易去讀和理解。另外,好的重構也幾乎避免了某類錯誤的發生(如剪切和粘貼導致的錯誤)。 3、把你的數據從代碼中分離出來,使得數據和代碼更易協同工作。管理這種分離的標準機制是`MVC`機制。用`wxPython`的術語來說,`V(View)`是`wx.Window`對象,它顯示你的數據;`C(Controller)`是`wx.EvtHandler`對象,它分派事件;`M(Model)`是你自己的代碼,它包含被顯示的信息。 4、或許`MVC`結構的最清晰的例子是`wxPython`的核心類中的`wx.grid.PyGridTableBase`,它被用于表示數據以在一個`wx.grid.Grid`控件中顯示。表中的數據可以來自于該類本身,或該類可以引用另一個包含相關數據的對象。 5、你可以使用一個簡單的機制來創建你自己的`MVC`設置,以便在模型被更新時通知視圖(`view)`。在`wxPython`中也有現成的模塊可以幫助你做這樣的事情。 6、單元測試是檢查你的程序的正確性的一個好的方法。在`Python`中,`unittest`模塊是執行單元測試的標準方法中的一種。使一些包,對一個`GUI`進行單元測試有點困難,但是`wxPython`的可程序化的創建事件使得這相對容易些了。這使得你能夠從頭到尾地去測試你的應用程序的事件處理行為。 在下一章中,我們將給你展示如何去建造一個小型的應用程序以及如何去做一些事情,這些對你將建造的`wxPython`應用程序將是通用的。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看