Python核心編程筆記————GUI編程(一)

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 連接起來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章