好,我們的第一個 GUI 程序用大家一般在 Python 學習階段都用過的一個簡單的模擬登錄。
- 預設賬號和密碼
- 輸入賬號
- 輸入密碼
- 點擊提交
- 判斷賬號和密碼是否匹配,都匹配則返回登錄成功,否則登錄失敗。
基本邏輯如下:
user = input('請輸入賬號:')
password = input('請輸入密碼:')
if user == 'admin' and password == '123':
print('登錄成功!')
else:
print('登錄失敗!')
相信很多同學都在控制檯上做過這個練習。接下來呢,我們用 GUI 實現。
寫一個 GUI 程序有以下步驟:
- 理清楚需求
- 畫出原型圖
- 分解原型圖中的元素,構造佈局生成窗口
- 獲取窗口事件和數據
- 運行事件和數據處理邏輯
- 反饋結果
- 重複 4~6 直到點擊退出
- 退出並銷燬窗口
定義需求
定義需求:
用戶在界面上輸入賬號和密碼,當點擊提交時判斷賬號和密碼是否匹配,匹配則彈出登錄成功,失敗則彈出紅字標識的登錄失敗。
畫原型圖
好,接下來我們畫一個原型圖,畫原型圖大家可以使用一些需求工具:
- Axure:很強大的原型圖工具,但是用起來學習成本也比較高
- Balsamiq Mockups:草圖工具,需要購買才能使用
- MockPlus:國產的原型圖工具,畫原型圖免費,但是要導出需要收費
還有其他很多相關的原型圖工具,請大家自行下載,當然你也可以直接在紙上畫:
醜是醜了點,意思到了就行。簡單的我們就直接畫了,如果複雜的界面還是需要藉助工具。
分解原型圖並構建佈局
我們來看一下原型圖中可能會用到的界面元素(也有地方叫做控件、部件、組件等,我們這裏統一叫做元素好了)。
- 窗口標題,還有窗口上的
-
縮小、□
適應屏幕、x
關閉。除了標題,其他自動構建; - 賬號輸入框,這裏有兩個元素:一個文本,顯示字符串“賬號”,一個輸入框
- 密碼輸入框,這裏也有兩個元素:一個文本,顯示字符串“密碼”,一個密碼輸入框
- 一個提交按鈕
- 兩個彈出窗口,一個彈出“登錄成功”,另一個彈出紅色文字的“登錄失敗”。
在PySimpleGUI 中佈局是按行來處理的。每個元素都處於佈局中的某一行。其中第 1 點中的標題欄不屬於佈局中的內容,第 5 點的彈出窗口屬於特殊元素,不用加入佈局。
我們來看看在 PySimpleGUI 中如何通過行來管理的:
- 整個佈局是一個大的列表
list
; - 每一行是這個大列表中的子列表;
- 每一行中的元素是這個列表的元素。
比如第 2 點的賬號輸入框,有兩個元素,我們需要構建一個列表:
[文本, 輸入框]
整個佈局應該是這樣:
[
[文本, 輸入框],
[文本, 輸入框],
[按鈕]
]
如果更復雜的佈局,就在這個列表中添加,比如你想在賬號輸入框後面添加一個複選框 checkbox
用來勾選是否保存賬號,那麼佈局會變成這樣:
[
[文本, 輸入框, 複選框],
[文本, 輸入框],
[按鈕]
]
是不是很簡單?
好了,我們還是回到我們這個小程序。
這裏我們用到了三個元素,文本、輸入框和按鈕:
- 文本: Text 或者 T,它接收的第一個參數是要顯示的文本;
- 輸入框:InputText 或 Input 或 In,可以不用輸入參數,是用來接收輸入的單行文本。如果要用作密碼輸入框,那麼用
password_char
參數來指定替換的字符串,比如你要用*
作爲屏蔽字符,就寫爲password_char='*'
; - 按鈕: Button 或 Btn 或 B,接收的第一個參數是按鈕顯示的文本。
這裏我們可以看到作者給出了很多簡寫,方便你使用但其實也增加了一些記憶負擔,你可以在可讀性的基礎上記住其中一個就行。
好,那麼接下來,我們就把這些元素替換到我們佈局的列表中去:
import PySimpleGUI as sg # 引入PySimpleGUI,注意大小寫
# 創建佈局
layout = [
[sg.Text('賬號'), sg.Input(key='_USER_')],
[sg.Text('密碼'), sg.Input(password_char='*', key='_PWD_')],
[sg.Btn('提交', key='_LOGIN_')]
]
這裏的一些元素加上了
key
參數,這有什麼用?是不是都要加?key有什麼用? key 參數主要用於後面接收事件和獲取用戶輸入。
是不是都要加? 像文本元素這種後續基本不會再處理的,可以不加。但是後續你如果要接收它的事件(比如按鈕被點擊),獲取用戶輸入(輸入框中錄入的數據),改變元素(比如某種情況觸發需要改變字體顏色等),那麼這些被操作的元素一定要用 key 參數指定一個唯一的標識。
如何加? 作者建議,使用大寫字母,前後加下劃線的方式增加標識的可讀性和可識別性。
好了,一個佈局就生成了,是不是很簡單。
接下來把佈局列表作爲參數傳入窗口實例就可以了:
# 創建窗口,生成窗口實例
window = sg.Window('登錄', layout=layout, finalize=True)
Window 第一個參數是窗口標題,第二個參數我們傳入了前面定義的佈局列表,第三個參數finalize
不是必須的,用來定型窗口,這主要有一些操作必須在窗口定型後才能執行。
接下來你運行一下就可以看到一個窗口閃了一下。
《震驚!這樣寫代碼居然會讓窗口閃一下就沒了!》
好吧,如果你不加 finalize
參數,甚至閃都不會閃。我們還是把過程寫完吧,這本來就是半成品。
事件循環
事件循環包含三個我們需要處理的過程:
- 獲取窗口事件和數據
- 運行事件和數據處理邏輯
- 反饋結果
爲什麼要用循環?任何 GUI 窗口都是利用循環機制,不斷循環接收用戶輸入和用戶操作事件,然後處理,直到用戶點擊退出爲止。如果不循環,窗口運行一次就會關閉。
在循環過程中,我們需要使用 Window
的 Read
方法來接收用戶的事件和輸入數據:
event, value = window.Read()
# event, value 的值分別是 _LOGIN_ {'_USER_': 'admin', '_PWD_': '123'}
返回的第一個值 event,其結果是接收界面的事件,這裏是接收按鈕點擊事件。如果按鈕被點擊,event 的值將是被點擊元素的 key 參數設置的標識符,比如我們這裏的提交按鈕的 key 設置爲 _LOGIN_
,那麼當提交按鈕被點擊時,event 的值就是 _LOGIN_
。
如果沒有設置 key,event 的值將會是按鈕文本,那這裏就會是中文的
提交
。
第二個返回值是 value,這是一個以字典形式記錄接收到界面上的用戶輸入。我們之前在元素中設置的 key 參數將作爲字典的鍵,比如_USER_
和_PWD_
,可以通過這些 key 來取對應的輸入值。
如果你沒有爲輸入元素設置 key,那麼字典的鍵爲按照元素在 layout 中的順序,用下標 0, 1, 2… 來記錄用戶輸入的值。你就需要用下標按順序去讀取,如果元素很多的情況下,你很難保證正確性,因此我 建議一定要在元素定義的時候加上 key 參數,並且爲元素設置一個唯一的標識。
好了,接下來我們就對事件進行處理。
if event == '_LOGIN_': # 當獲取到事件是提交按鈕被點擊時,處理賬號密碼判斷
user = value['_USER_'] # 從返回值字典中提取賬號輸入框的值
password = value['_PWD_'] # 從返回值字典中提取密碼輸入框的值
if user == 'admin' and password == '123':
sg.popup('登錄成功!') # 彈出框
else:
sg.popup('登錄失敗!', text_color='red') # 彈出框的字體設置爲紅色
popup()
是 PySimpleGUI 中提供的彈出框,用text_color
參數設置字體顏色。
如果你用的是 Pycharm,直接 Ctrl+鼠標懸停 在類名、函數名上都會看到其參數及返回值。你在調用時,也可以看到有哪些參數,大部分參數命名都能見名知義,用的時候多留心。大致你想要的絕大部分功能都能實現。
代碼敲完,運行一下,是不是就可以進行輸入了。但是輸入一次窗口就關閉了,因爲上面說過,窗口值運行一次就結束。要想持續保持窗口運行狀態就需要加入循環,在特定情況(比如右上角的x
和定義的元素事件)下退出循環,關閉窗口。
while True: # 設置一個循環
# 在特定條件下
break
那麼什麼特定條件下退出呢?比如某個按鈕被點擊或者右上角的x
被點擊。特定按鈕(比如界面上定義了一個按鈕,其 key 爲 _EXIT_
)的話,你直接寫爲:
while True:
if event == '_EXIT_':
break
對於右上角的x
被點擊,window.Read() 會接收到一個事件None
。對,沒錯就是你熟悉的那個 None。那我們要響應右上角的x
退出怎麼寫呢?
while True:
if event is None:
break
或者你想兩個地方(退出按鈕被點擊,右上角x
被點擊)接收到事件都退出,那麼寫爲:
while True:
if event in ['_EXIT_', None]:
break
然後在循環退出後,銷燬窗口。
其實你也發現了,就算我們不做任何處理,窗口也會自動關閉。爲什麼還要多寫一句呢?這是因爲在某些系統中會出現異常,因此保持良好的習慣,在循環後加上退出代碼。
window.close()
是不是比你用 input 寫命令行復雜不了多少呢?
點這裏獲取本節完整代碼。