An Expression Evaluator in 30 Lines(30行代碼的表達(dá)式計(jì)算器) 這個(gè)應(yīng)用程序是采用30行代碼(不包括空白行與注釋代碼)編寫的一個(gè)完整的“對(duì)話框樣式”應(yīng)用程序。“對(duì)話框樣式”表示這個(gè)應(yīng)用程序沒(méi)有菜單欄,通常也沒(méi)有工具欄或者狀態(tài)欄,常見(jiàn)的情況是有一些按鈕控件,但沒(méi)有中心窗口控件(central widget)。相應(yīng)地,“主窗口樣式”應(yīng)用程序通常有菜單欄、工具欄、狀態(tài)欄,在一些情況下也有按鈕;并且它們擁有中心窗口控件(能容納其他控件)。我們將在第6章學(xué)習(xí)“主窗口樣式”應(yīng)用程序。 這個(gè)應(yīng)用程序使用了兩個(gè)控件:QTextBrowser控件,是一個(gè)具有只讀屬性的多行文本框,它可以顯示純文本與HTML文本;QLineEdit控件,是一個(gè)只能顯示純文本的單行文本框。PyQt中控件使用的所有文本都采用Unicode編碼,如果有必要,它們可以被轉(zhuǎn)換成其他編碼。 計(jì)算器應(yīng)用程序(如圖4.3所示)的調(diào)用方式和其他常規(guī)的GUI應(yīng)用程序一樣,通過(guò)鼠標(biāo)點(diǎn)擊(或者雙擊,取決于操作系統(tǒng)平臺(tái)與設(shè)置)它的程序圖標(biāo)。(當(dāng)然它也可以通過(guò)控制臺(tái)啟動(dòng)運(yùn)行)。一旦應(yīng)用程序運(yùn)行,用戶可以在行編輯框中簡(jiǎn)單地輸入數(shù)學(xué)表達(dá)式,當(dāng)用戶按下回車(Enter)鍵,表達(dá)式與它的結(jié)果會(huì)以追加模式顯示在QTextBrowser控件中。任何由于無(wú)效的表達(dá)式或者無(wú)效的數(shù)學(xué)運(yùn)算(例如被零除)引起的異常將會(huì)轉(zhuǎn)變成錯(cuò)誤消息,并追加在QTextBrowser中顯示。 如同上一節(jié)一樣,我們將分塊去瀏覽整個(gè)代碼。這個(gè)例子所遵循的模式是我們?cè)谝院笏械腉UI應(yīng)用程序開(kāi)發(fā)中使用的模式:“窗體”采用一個(gè)類來(lái)表示,響應(yīng)用戶交互的行為由“方法”來(lái)處理,“主程序”部分盡可能的保持簡(jiǎn)潔。
因?yàn)槲覀冊(cè)谧鰯?shù)學(xué)計(jì)算,并不想遇到任何的諸如除法截?cái)嗟纫馔馇闆r,因此我們采用浮點(diǎn)數(shù)除法。通常我們導(dǎo)入非PyQt模塊時(shí)采用import moduleName語(yǔ)句,但因?yàn)槲覀兿胧褂盟械膍ath包里的函數(shù)與常量,因此我們簡(jiǎn)單地把math包里所有的內(nèi)容都導(dǎo)入到當(dāng)前命名空間。與上一節(jié)例子中一樣,我們導(dǎo)入sys模塊來(lái)獲取sys.argv參數(shù)列表,同時(shí)我們從QtCore與QtGui模塊中導(dǎo)入所有內(nèi)容。
正如我們所見(jiàn),任何一個(gè)控件都可以作為頂級(jí)窗體。但大多數(shù)情況下,我們通過(guò)子類QDialog或者QMainWindow創(chuàng)建頂級(jí)窗體,有時(shí)候我們也采用QWidget。QDialog和QMainWindow,以及所有的PyQt控件,都是從QWidget派生出來(lái)的,它們都屬于Python中的新式類。通過(guò)繼承QDialog類我們可以得到一個(gè)空白的窗口,即一個(gè)灰色的矩形窗口,它只具有一些簡(jiǎn)單的行為或者方法。例如,如果用戶點(diǎn)擊關(guān)閉的X按鈕,對(duì)話框會(huì)關(guān)閉。默認(rèn)情況下,當(dāng)控件被關(guān)閉時(shí),它僅僅只是隱藏了,當(dāng)然,我們也可以改變這一行為,這將在以后的章節(jié)中介紹。 我們傳遞給Form類的初始化方法__init__()默認(rèn)的參數(shù)parent為None值,并采用super()方法進(jìn)行初始化。如果一個(gè)窗體控件沒(méi)有父類,那么這個(gè)控件就是一個(gè)頂級(jí)窗體,在這個(gè)例子中正是我們希望的。接下來(lái)我們創(chuàng)建兩個(gè)控件,并且保存它們的引用以便以后我們?cè)赺_init__()外部可以訪問(wèn)它們。因?yàn)槲覀儧](méi)有傳遞父類給這兩個(gè)控件,似乎它們會(huì)成為頂級(jí)窗體——這是沒(méi)有意義的。我們將簡(jiǎn)單地介紹它們是如何在初始化過(guò)程中獲取父類的。我們給QLineEdit提供一個(gè)初始顯示文本,并選取全部文本,這樣做可以保證當(dāng)用戶開(kāi)始輸入時(shí),我們提供的初始文本被替換。 我們希望窗體控件垂直的顯示,一個(gè)在另一個(gè)上方。這可以通過(guò)創(chuàng)建一個(gè)QVBoxLayout布局并添加這兩個(gè)窗體控件實(shí)現(xiàn),然后設(shè)置好窗體的布局。如果你運(yùn)行程序并改變窗體的大小,會(huì)發(fā)現(xiàn)增加的垂直空間被分配給了QTextBrowser控件,兩個(gè)控件都會(huì)沿水平方向增長(zhǎng)。這是通過(guò)布局管理器自動(dòng)實(shí)現(xiàn)的,也可以通過(guò)設(shè)置布局進(jìn)行調(diào)整。 使用布局的一個(gè)重要的作用是PyQt自動(dòng)代理所有被展示的控件。因此,盡管我們沒(méi)有定義控件的父類為主窗體,但當(dāng)我們調(diào)用setLayout()函數(shù)時(shí),布局管理器會(huì)擁有控件的所有權(quán),同時(shí)將自己的所有權(quán)給主窗體,此外,它也會(huì)擁有它內(nèi)部的布局管理器的所有權(quán)。這表示所有被展示的控件都不是頂級(jí)窗體,它們都擁有父類,這也是我們希望看到的。所以當(dāng)主程序被釋放時(shí),它的所有的子控件和布局管理器都會(huì)以正確的順序被釋放。 窗體中的控件的布局可以有多久方式。我們可以使用resize()或者move()函數(shù)來(lái)定義它們的絕對(duì)大小和位置;也可以重新執(zhí)行resizeEvent()函數(shù)來(lái)自動(dòng)計(jì)算它們的大小和位置,或者使用PyQt的布局管理器。使用絕對(duì)大小或者位置非常的麻煩。一方面,我們需要進(jìn)行大量的人工計(jì)算,另一方面,如果我們改變了窗體的布局,我們需要重新進(jìn)行所有的計(jì)算。自動(dòng)計(jì)算控件的大小與位置是一個(gè)更好的方式,但我們同樣需要編寫大量的計(jì)算代碼。 使用布局管理器讓一切事情變得簡(jiǎn)單。布局管理器非常的智能:它們自動(dòng)地適應(yīng)調(diào)整大小事件和內(nèi)容改變。任何人如果使用過(guò)不同版本的Windows的對(duì)話框,會(huì)很感謝對(duì)話框可改變大小所帶來(lái)的好處,因?yàn)楫?dāng)用戶的內(nèi)容太大時(shí),使用小的而且不可改變大小的對(duì)話框會(huì)帶來(lái)非常多的不方便。對(duì)于國(guó)際化應(yīng)用程序需要根據(jù)語(yǔ)言調(diào)整內(nèi)容時(shí),布局管理器也可以讓事情變得簡(jiǎn)單,而不用擔(dān)心如果翻譯過(guò)后的語(yǔ)言長(zhǎng)度大于原始的語(yǔ)言時(shí)被截?cái)唷?/p> PyQt提供三種布局管理器:垂直布局管理器,水平布局管理器以及網(wǎng)格布局管理器。布局管理器可以嵌套,這讓非常復(fù)雜的布局也變成可能。還有其他的布局方式,例如使用splitter或者tab控件。所有的這些方式都會(huì)在第9章更深入地介紹。 出于對(duì)用戶使用上的方便,我們?cè)诔绦蜻\(yùn)行的開(kāi)始將焦點(diǎn)設(shè)置為QLineEdit控件;可以通過(guò)調(diào)用setFocus()函數(shù)實(shí)現(xiàn),這必須在設(shè)置好布局管理器后調(diào)用。 函數(shù)connect()的調(diào)用我們將在以后的章節(jié)中深入地介紹。目前只要知道每一個(gè)控件(或者一些其他的QObject)通過(guò)發(fā)送一個(gè)“信號(hào)(signals)”來(lái)聲明它們的狀態(tài)改變就足夠了。這些信號(hào)(與Unix系統(tǒng)的信號(hào)沒(méi)有任何關(guān)系)通常被忽略。然而,當(dāng)我們選擇關(guān)注我們感興趣的那些信號(hào)時(shí),我們可以通過(guò)這個(gè)方式識(shí)別我們想要知道的那些QObject對(duì)象,它們發(fā)送出的信號(hào)是我們感興趣的,當(dāng)信號(hào)發(fā)出時(shí),我們希望執(zhí)行哪些函數(shù)或者方法。 在這個(gè)例子中,當(dāng)用戶在QLineEdit中按下“回車(Enter)”鍵時(shí),會(huì)觸發(fā)returnPressed()信號(hào),因?yàn)檎{(diào)用了connect()函數(shù),當(dāng)觸發(fā)信號(hào)時(shí),用調(diào)用updateUi()方法。我們將在一會(huì)看到發(fā)生了什么改變。 最后一件事是通過(guò)__init__()函數(shù)來(lái)設(shè)置窗體的標(biāo)題。 正如我們一會(huì)將看到,窗體被創(chuàng)建并且調(diào)用了show()方法。一旦事件循環(huán)開(kāi)始,窗體會(huì)顯示出來(lái)——沒(méi)有更多的事情發(fā)生。應(yīng)用程序只是簡(jiǎn)單地執(zhí)行事件循環(huán),等待用戶點(diǎn)擊鼠標(biāo)或者按下按鍵。一旦用戶開(kāi)始交互,它們交互的結(jié)果將被處理。因此如果用戶輸入一個(gè)表達(dá)式,QLineEdit控件會(huì)關(guān)注用戶輸入的內(nèi)容,一旦用戶按下“回車(Enter)“鍵,我們定義的updateUi()方法將被調(diào)用。
當(dāng)調(diào)用updateUi()時(shí),首先解析QLineEdit中的文本字符,然后將其轉(zhuǎn)換為unicode對(duì)象。接下來(lái)我們利用Python自帶的eval()函數(shù)來(lái)計(jì)算字符表達(dá)式的結(jié)果。如果成功,將追加該字符表達(dá)式,等號(hào)符號(hào)和用粗體顯示的表達(dá)式結(jié)果到QTextBrowser對(duì)象中。盡管我們通常會(huì)盡快地將QStrings對(duì)象轉(zhuǎn)換為unicode對(duì)象,我們?nèi)匀豢蓚鬟fQStrings、unicode和strs對(duì)象到PyQt方法中,該方法期望的對(duì)象是QStrings對(duì)象,但PyQt會(huì)自動(dòng)完成必要的轉(zhuǎn)換。如果任何異常發(fā)生,相應(yīng)地,我們追加一個(gè)錯(cuò)誤消息。使用catch-all-except代碼塊通常并不是很好的方式,但在這個(gè)30行的程序代碼中它是合理的。 通過(guò)使用eval()函數(shù)我們可以避免所有的解析與錯(cuò)誤檢查工作,而如果使用編譯型語(yǔ)言,這部分工作需要我們自己去實(shí)現(xiàn)。
現(xiàn)在我們定義好了Form類,在calculate.pyw文件的最后,我們創(chuàng)建一個(gè)QApplication對(duì)象以及一個(gè)Form類的實(shí)例form,增加一個(gè)繪圖的計(jì)劃,并開(kāi)始事件循環(huán)。 這就是完整的應(yīng)用程序。然而一切并沒(méi)有結(jié)束。我們并沒(méi)有說(shuō)明如何去終止應(yīng)用程序,但因?yàn)槲覀兊膄orm是從QDialog對(duì)象派生出來(lái)的,它繼承了某些行為。例如,如果用戶點(diǎn)擊窗體的關(guān)閉按鈕X,或者用戶按下了Esc按鍵,窗體form會(huì)關(guān)閉。當(dāng)窗體form關(guān)閉時(shí),它是隱藏著的。PyQt會(huì)自動(dòng)檢查應(yīng)用程序是否有非隱藏的窗體,以及是否有進(jìn)一步交互的可能。如果沒(méi)有,那么會(huì)釋放掉整個(gè)窗體form并終止應(yīng)用程序。 在一些情況下,我們希望在窗體不可見(jiàn)的情況下讓應(yīng)用程序繼續(xù)運(yùn)行——例如,服務(wù)器。在這些情況下,我們可以調(diào)用QApplication.setQuitOnLastWindowClosed(False)函數(shù)來(lái)實(shí)現(xiàn)。 在Mac OS X,或者一些X Windows的窗口管理器例如twm中,本節(jié)中的例子并沒(méi)有關(guān)閉按鈕,在Mac平臺(tái)上,選擇菜單欄的Quit菜單也不起作用。在這個(gè)情況下,可以按下Esc鍵來(lái)終止應(yīng)用程序,或者使用Command+.來(lái)終止。考慮到這一點(diǎn),對(duì)于在Mac或者使用如twm作為窗口管理器的操作系統(tǒng)平臺(tái)上開(kāi)發(fā)GUI應(yīng)用程序時(shí),最好提供一個(gè)Quit按鈕。為對(duì)話框增加按鈕將在本章的最后一節(jié)介紹。 本例子的完整代碼為:
|
|
|
來(lái)自: River_LaLaLa > 《Python》