文章目錄
ps:python版本爲3.6.2
簡介
. 這裏主要使用的GUI工具包是Python默認的GUI庫Tk,通過接口Tkinter來訪問,Tk並不是最新和最好的,也沒有包含最強大的GUI構建模塊集,但是足夠易用。
查看自己的解釋器是否安裝了Tkinter的方法是import一下,不過在python3的版本下應該是自動安裝的,另外模塊的名字是tkinter。
Tkinter和Python編程
Tkinter模塊:添加Tk到應用中
. 讓一個GUI應用能夠啓動和運行起來需要以下5個步驟:
1.Tkinter模塊;
2.創建一個頂層窗口對象,以容納整個GUI應用;
3.在頂層窗口對象上(或其中)構建所有的GUI組件(及功能);
4.通過底層的應用代碼將這些組件連接起來;
5.進入主事件循環。
GUI編程介紹
. 就像畫家作畫一樣,首先需要的是一張乾淨的畫布,然後才能在上面畫出不同的畫面,這個基礎就像是Tkinter中的頂層窗口對象。
窗口和控件
. 在GUI編程中,頂層的根窗口對象包含組成GUI應用的所有小窗口對象,它們可能是標籤、按鈕、列表框等,這些獨立的GUI組件稱爲控件。一般創建一個頂層窗口所用的語句是:
top = tkinter.Tk()
. 這裏返回的對象通常稱爲根窗口,頂層窗口是那些在應用中獨立顯示的部分。GUI程序中可以有多個頂層窗口,但是只能有一個根窗口。
控件可以獨立存在,也可以作爲容器存在,如果一個控件包含其他控件,就可以將其認爲是那些控件的父控件,那些控件也認爲是這個控件的子控件。
通常,控件有一些相關的行爲,比如按鈕可以點擊,這些用戶行爲被稱爲事件,而GUI對這些事件的響應稱爲回調。
事件驅動處理
. 一個GUI應用從開始到結束就是通過整套事件體系來驅動的,這種方式稱爲事件驅動處理。
比如說最簡單的鼠標移動的例子。假設鼠標處於靜止狀態,然後被手移動到另一個位置,鼠標移動的行爲會被複制到屏幕的光標上,於是看起來像是根據你的手來移動的,系統必須處理這些鼠標移動事件來繪製窗口上的光標移動,當鼠標再次停下來,沒有事件需要處理的時候,屏幕會重新恢復到閒置狀態。
佈局管理器
. Tk有三種佈局管理器來幫助控件集進行定位。
第一種稱爲Placer:做法很直接,開發者提供控件的大小和擺放位置,然後管理器就會將其擺放好。問題是開發者必須對所有控件進行這些操作,這會加大開發者的負擔;
第二種是Packer,這將是主要使用的:它會將控件填充到正確的位置(即指定的父控件中),然後對於之後的控件,回去尋找剩餘的空間填充,就像是打包行李的過程;
第三種是Grid:基於網格座標來指定控件的位置,Grid會在它們的網格位置上渲染GUI應用中的每個對象。
當所有控件擺放好後,就可以讓應用進入到無限主循環中,代碼一般如下:
tkinter.mainloop()
. 一般這是程序的最後一句話。當程序進入到主循環後,GUI就從這裏開始接管程序,所有其他行爲都通過回調來處理,甚至包括退出。
Tk控件
. 以下是書中提到的控件:
控件 | 描述 |
---|---|
Label | 用於包含文本或圖像 |
Button | 與 Label 類似,但提供額外的功能,如鼠標懸浮、按下、釋放以及鍵盤活動/事件 |
Canvas | 提供繪製形狀的功能(線段、橢圓、多邊形、矩形),可以包含圖像或位圖 |
Checkbutton | 一組選框,可以勾選其中的任意個(與 HTML 的 checkbox 輸入類似) |
Entry | 單行文本框,用於收集鍵盤輸入(與 HTML 的文本輸入類似) |
Frame | 包含其他控件的純容器 |
LabelFrame | 標籤和框架的組合,擁有額外的標籤屬性 |
Listbox | 給用戶顯示一個選項列表來進行選擇 |
Menubutton | 用於包含菜單(下拉、級聯等) |
Menu | 按下Menubutton 後彈出的選項列表,用戶可以從中選擇 |
Message | 消息。與 Label 類似,不過可以顯示成多行 |
PanedWindow | 一個可以控制其他控件在其中擺放的容器控件 |
Radiobutton | 一組按鈕,其中只有一個可以“按下”(與 HTML 的 radio 輸入類似) |
Scale | 線性“滑塊”控件,根據已設定的起始值和終止值,給出當前設定的精確值 |
Text | 多行文本框,用於收集(或顯示)用戶輸入的文本(與 HTML 的 textarea 類似) |
Scrollbar | 爲 Text、Canvas、Listbox、Enter 等支持的控件提供滾動功能 |
Spinbox | Entry 和 Button 的組合,允許對值進行調整 |
Toplevel | 與 Frame 類似,不過它提供了一個單獨的窗口容器 |
Tkinter示例
Label、Button和Scale控件
. 以下是一個包含標籤、按鈕以及滑塊的GUI程序,通過滑塊調節標籤中字的大小,通過按鈕退出程序:
import tkinter
def resize(ev = None): #滑動滑塊時的回調函數
label.config(font = 'Helvetica -%d bold' % scale.get())
top = tkinter.Tk()
top.geometry('250x150') #設置根窗口的大小,是x,不是*
label = tkinter.Label(top,text = 'Hello World',font = 'Helvetica -12 bold')
label.pack(fill = tkinter.Y,expand = 1)
scale = tkinter.Scale(top,from_ = 10,to = 40,orient = tkinter.HORIZONTAL,
command = resize)
scale.set(12) #滑塊位置的初始值設置
scale.pack(fill = tkinter.X,expand = 1)
quit = tkinter.Button(top,text = '退出',command = top.quit,
activeforeground = 'white',
activebackground = 'red')
quit.pack(fill = tkinter.X,expand = 1)
tkinter.mainloop()
. 控件一般都有默認參數,如果不在意可以不用都寫,另外在按鈕和滑塊的pack函數中對fill賦值,表示填充剩餘的水平空間, expand 參數則會引導它填充整個水平可視空間,將按鈕拉伸到左右窗口邊緣。command參數表示給控件安裝的回調函數。
在pack沒有收到其它指示的時候,控件都默認是垂直排列的,如果想要水平佈局則需要創建一個新的Frame對象來添加按鈕。
偏函數應用示例
. 偏函數是函數式編程一系列改進中的一部分。使用偏函數,可以有效的“凍結”那些預先確定的參數來緩存函數參數,然後再運行時,當獲得需要的剩餘參數後,可以將它們解凍,傳遞到最終的參數中,從而使用最終確定的所有參數去調用函數。
偏函數不止可適用於函數,也可以使用於可調用對象(重載了圓括號的那種類或結構體吧),對於那些反覆使用相同的參數的可調用對象,使用偏函數會更合適。
GUI編程是一個很好的偏函數用例,因爲通常會希望界面的風格能夠一致,比如按鈕擁有一致的前景色和背景色。
以下是一個偏函數的示例,通過交通路標的的不同顏色方案來展示,分爲三種:嚴重、警告和提醒,三種路標有不同的文字、前景色、背景色和回調函數:
from functools import partial as pt #偏函數相關庫
from tkinter import Tk,Button,X,messagebox
WARN = 'warn'
CRIT = 'crit'
REGU = 'regu'
SIGNS = {
'do not enter':CRIT,
'railroad crossing':WARN,
'55\hspeed limit':REGU,
'wrong way':CRIT,
'merging traffic':WARN,
'one way':REGU
}
#點擊按鈕的回調
critCB = lambda : messagebox.showerror('Error','Error Button Pressed')
warnCB = lambda : messagebox.showwarning('Warn','Warn Button Pressed')
reguCB = lambda : messagebox.showinfo('Regu','Regu Button Pressed')
top = Tk()
top.title('Road Signs')
Button(top,text = 'QUIT',command = top.quit,bg = 'red',fg = 'white').pack()
MyButton = pt(Button,top)
CritButton = pt(MyButton,command = critCB,bg = 'white',fg = 'red')
WarnButton = pt(MyButton,command = warnCB,bg = 'goldenrod1')
ReguButton = pt(MyButton,command = reguCB,bg = 'white')
for eachsign in SIGNS:
signtype = SIGNS[eachsign]
cmd = "{0}Button(text = '{1}').pack(fill = X,expand = True)".format(
signtype.title(),eachsign)
eval(cmd)
top.mainloop()
. 其中使用了兩階偏函數,第一階模板化了Button類和根窗口,意味着每次調用MyButton時都會調用Button類,並將top作爲它的第一個參數。將它“凍結”爲MyButton。
第二階偏函數使用第一階偏函數,並對其模板化,創建不同的按鈕時,它就會調用適當的MyButton。最後的eval() 函數用來執行一個字符串表達式。
中級Tkinter示例
. 接下來是一個更復雜的示例,這是一個目錄樹遍歷工具,除了之前的空間還使用到了列表框、文本框和滾動條。此外,還增加了鼠標單擊、鍵盤按下、滾動操作等回調函數:
import os
from time import sleep
from tkinter import *
class DirList(object):
def __init__(self,initdir = None):
self.top = Tk()
self.label = Label(self.top,text = 'Directory Lister v1.1')
self.label.pack()
self.cwd = StringVar(self.top) #用於保存當前所在的目錄名
self.dirl = Label(self.top,fg = 'blue',font = ('Helvetica' ,12,'bold'))
self.dirl.pack()
self.dirfm = Frame(self.top) #中間部分,包括顯示列表,滑塊
self.dirsb = Scrollbar(self.dirfm)
self.dirsb.pack(side = RIGHT,fill = Y)
self.dirs = Listbox(self.dirfm,height = 15,width = 50,
yscrollcommand = self.dirsb.set)
#通過使用 Listbox 的 bind()方法,
#Listbox 的列表項可以與回調函數(setDirAndGo)連接起來
self.dirs.bind('<Double-1>',self.setDirAndGo)
self.dirsb.config(command = self.dirs.yview)
self.dirs.pack(side = LEFT,fill = BOTH)
self.dirfm.pack()
#輸入框,可以從這裏手動輸入目錄跳轉
self.dirn = Entry(self.top,width = 50,textvariable = self.cwd)
#綁定了回車鍵,除了點擊按鈕,敲擊回車也能達到同樣效果
self.dirn.bind('<Return>',self.doLS)
self.dirn.pack()
self.bfm = Frame(self.top)
self.clr = Button(self.bfm,text = 'clear',
command = self.clrDir,
activeforeground = 'white',
activebackground = 'blue')
self.ls = Button(self.bfm,text = 'List Directory',
command = self.doLS,
activeforeground='white',
activebackground='blue')
self.quit = Button(self.bfm, text='quit',
command=self.top.quit,
activeforeground='white',
activebackground='red')
self.clr.pack(side = LEFT)
self.ls.pack(side=LEFT)
self.quit.pack(side=LEFT)
self.bfm.pack()
if initdir:
self.cwd.set(os.curdir)
self.doLS()
#清空輸入框
def clrDir(self,ev = None):
self.cwd.set('')
#設置要遍歷的目錄
def setDirAndGo(self,ev = None):
self.last = self.cwd.get()
self.dirs.config(selectbackground = 'red')
check = self.dirs.get(self.dirs.curselection())
if not check:
check = os.curdir
self.cwd.set(check)
self.doLS()
def doLS(self,ev = None):
#安全檢查
error = ''
tdir = self.cwd.get()
if not tdir:
tdir = os.curdir
if not os.path.exists(tdir):
error = tdir + 'no such file'
elif not os.path.isdir(tdir):
error = tdir + 'is not a dir'
if error:
self.cwd.set(error)
self.top.update()
sleep(2)
if not (hasattr(self,'last') and self.last):
self.last = os.curdir
self.cwd.set(self.last)
self.dirs.config(selectbackground = 'LightSkyBlue')
self.top.update()
return
#如果一切正常,就會調用 os.listdir()獲取實際文件列表並在 Listbox 中進行替換
self.cwd.set('Fetching directory contents...')
self.top.update()
dirlist = os.listdir(tdir)
dirlist.sort()
os.chdir(tdir)
self.dirl.config(text = os.getcwd())
self.dirs.delete(0,END)
self.dirs.insert(END,os.curdir)
self.dirs.insert(END, os.pardir)
for eachFile in dirlist:
self.dirs.insert(END,eachFile)
self.cwd.set(os.curdir)
self.dirs.config(selectbackground = 'LightSkyBlue')
def main():
d = DirList(os.curdir)
mainloop()
if __name__ == '__main__':
main()
. 其中bind函數起到綁定的作用,綁定意味着將一個回調函數與按鍵、鼠標操作或一些其他事件連接起來,當用戶發起這類事件時,回調函數就會執行。當雙擊 Listbox 中的任意條目時,就會調用 setDirAndGo()函數。而 Scrollbar 通過調用 Scrollbar.config()方法與 Listbox 連接起來。