Python GUI:Tkinter——2


續上文:Python GUI:Tkinter——1

加入 Padding

所謂 Padding,就是棉片,哦不,空間的意思。通過控件的 .grid_configure接口,可以設置控件在 x,y軸方向上的間隔

大家可以接着Python GUI:Tkinter——1,在下面添加代碼

aLabelFrame.grid_configure(padx=10,pady=40)    #.grid_configure 接口可以設置 grid 排版格式
for child in aLabelFrame.winfo_children():    #遍歷 Frame 下的所有控件
	child.grid_configure(padx=8,pady=4)

效果如下,可以看到,aLabel 中的(子)控件的間隔變寬了,從而使得整個界面的排版變得小清新起來。
在這裏插入圖片描述

Frames within win

上一篇文章最後討論了,若某個控件佔用的長度過長,則會導致一整列都變長,進而導致界面排版混亂。如何解決呢?就是把控件的 Master,從 win 中改爲某個 Frame。 注意,Frame 可以是 LabelFrame 也可以直接是 Frame。前者有一個 Label 作爲“標題”,後者沒有。

用 Frame 取代 win

用 Frame 取代 win 有什麼作用呢?

  • 便於遍歷控制 Frame 中的所有控件
  • 有利於改善 GUI 界面的整體視覺效果

大家可以接着Python GUI:Tkinter——1,在下面添加代碼

"""在 win 下面加入下面的代碼"""
bLabelFrame = ttk.LabelFrame(win,text='Python GUI')
bLabelFrame.grid(column=0,row=0)

把所有的控件的 Master 改爲這個 Frame,即 bLabelFrame

這樣做有什麼好處呢?一是可以通過 .winfo_children() 來遍歷 bLabelFrame 下面的控件,然後只要修改子控件就可以修改每個控件的間隔,比如:

for child in bLabelFrame.winfo_children():
    child.grid_configure(sticky='W')

其中 sticky=‘W’ 等價於 tk.w,要記住,sticky 的英文是粘的意思,不是對其。所以,如果令 sticky='WSNE',則讓控件佔滿整個空間。

改善 GUI 界面的整體視覺效果

如果某個控件佔用了很多空間,那麼與之同列的控件就會收到影響。因此,不能像上面那樣,把所有控件放到一個Frame裏。如果一個控件佔用的空間太大,可以把他裝到另一個 Frame 裏,然後再把這個Frame 放入到 win 上。於是,就變成了 win 中有兩個 Frames,每個 Frame 分配不同的空間。於是,某一個 Frame 的列寬度或列數,就不會影響另一個 Frame 的寬度和列數。

添加 Menus

效果圖:

在這裏插入圖片描述

實現代碼

大家可以接着Python GUI:Tkinter——1,在下面添加代碼。

from tkinter import Menu
menuBar = Menu(win)    #示例話一個 Menu,Master 爲 win。
"""思考一下,Master可以爲 Frame 嗎?評論區評論一下試試?"""
win.configure(menu=menuBar)    #注意是 menu 參數

fileMenu = Menu(menuBar,tearoff=0)    # tearoff 參數如果不設置,就會發現多出一條橫線
"""提問: 你覺得這裏的 menuBar 是父類,還是某個參數?在評論區回答一下吧/(ㄒoㄒ)/~~"""
fileMenu.add_command(label='New')    #add_command 用來添加 menu 的“項”
"""嵌套"""
subFileMenu = Menu(fileMenu,tearoff=0)    # 這個是 Menu 中的 Menu 了,可以一直這樣嵌套下去
subFileMenu.add_command(label='Save')
subFileMenu.add_command(label='Save as')
fileMenu.add_cascade(label='Save as',menu=subFileMenu)
"""嵌套結束"""
fileMenu.add_separator()    #給菜單“項”加一條橫線

from sys import exit
def _quit():    #設置一個 menu 菜單項的激活函數
    win.quit()    #退出
    win.destroy()   #銷燬
    exit()   #結束程序

fileMenu.add_command(label='Exit',command=_quit)    #command參數設置激活函數,即退出。激活函數的定義同樣要放在
#前面,這是因爲 tk 控件是 callback 的緣故
menuBar.add_cascade(label='File',menu=fileMenu)    #將fileMenu 添加進 menuBar,這樣纔會顯示出來

helpMenu = Menu(menuBar,tearoff=0)
helpMenu.add_command(label='About')
menuBar.add_cascade(label='Help',menu=helpMenu)

助記總結

  • 所有的 Menu 類控件,都以 Menu 實例化開始
  • 除了 menuBar 以 win.configure(menu=menuBar)結束外,其他的 Menu 控件都以 .add_cascade(label='xxx' ,menu=menu名)結束。
  • 不同於其他空間,menu 無需 grid 就能夠顯示。換句話說,許多控件,包括 Frame 都需要 grid 或 pack 後才能排版在GUI 上。

NoteBook 與 Tab

Tab 能夠將軟件分開成兩個部分來使用,就像書本翻頁一樣。這兩頁的排版可以沒有任何關係,當然除了 Menus 是一樣的之外。先看一下效果:
在這裏插入圖片描述
在這裏插入圖片描述

原理

其實,tab 相當於一個新的 Frame,其分頁作用的,是 NoteBook 這個控件。

tabControl = ttk.Notebook(win)
tab1 = ttk.Frame(tabControl)
tab2 = ttk.Frame(tabControl)
tabControl.add(tab1,text='頁面1')
tabControl.add(tab2,text='頁面2')
tabControl.pack(fill='both',expand=1)

之後,只要把 tab1 但成 win 來用就行了,比如大家可以把 bLabelFrame 的 Master 設置成 tab1。

然後,再添加一個 cLabelFrame,再弄上幾個控件,在把 cLabelFrame 的 Master 設置成 tab2 。完事。 當然,也可以把 空間的 Master 設置成 tab2

記住,此時 Frames within win 就應該改寫爲 Frames within tab了。當然,不是 Frame within tabcontrol,希望大家能明白我的意思。

整體代碼:

可能有人被我的表述方式搞暈了,這是因爲這門教程是從 01 開始看起的。所以中途跳轉過來的同學,可能有點不知所云,那麼沒關係,我就把所有代碼再展示以便。(這個代碼可能與 01 有點不一樣,因爲是我重新敲的,並且加入了 02 的代碼(即上面的所有騷操作))

先看看效果吧:
在這裏插入圖片描述
在這裏插入圖片描述
這是我爲了強化記憶,再次碼了一遍代碼。其中

# -*- coding: utf-8 -*-
"""
Created on Thu May 28 21:01:29 2020

@author: Administrator
"""

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as mBox
from tkinter import Menu
win = tk.Tk()
win.title('標題')
win.iconbitmap(r'../app.ico')
menuBar = Menu(win)
win.configure(menu=menuBar)
from sys import exit
def _quit():
    win.quit()
    win.destroy()
    exit()

fileMenu = Menu(menuBar,tearoff=0)
fileMenu.add_command(label='New')
fileMenu.add_separator()
subMenu = Menu(fileMenu,tearoff=0)
subMenu.add_command(label='Save')
subMenu.add_command(label='Save as')
fileMenu.add_cascade(label='Save',menu=subMenu)
fileMenu.add_command(label='Exit',command=_quit)
menuBar.add_cascade(label='File',menu=fileMenu)

helpMenu = Menu(menuBar,tearoff=0)
helpMenu.add_command(label='Help')
helpMenu.add_command(label='About')
menuBar.add_cascade(label='Help',menu=helpMenu)


tabControl = ttk.Notebook(win)
tab1 = ttk.LabelFrame(tabControl)
tab2 = ttk.LabelFrame(tabControl)
tabControl.add(tab1,text='頁面1')
tabControl.add(tab2,text='頁面2')
tabControl.pack(fill='both',expand=1)

zhuo = ttk.LabelFrame(tab1,text='zhuo\'s GUI')
zhuo.grid(column=0,row=0)

ttk.Label(zhuo,text='Please put a name').grid(column=0,row=0)

etyVar = tk.StringVar()
aEntry = ttk.Entry(zhuo,textvariable=etyVar)
aEntry.grid(column=0,row=1)

comVar = tk.StringVar()
aCombobox = ttk.Combobox(zhuo,textvariable=comVar,state='readonly')
aCombobox['value'] = (1,2,4,8)
aCombobox.grid(column=1,row=1)

def clickBut():
    aButton.configure(text='hello'+etyVar.get()+comVar.get())
aButton = ttk.Button(zhuo,text='click me',command=clickBut)
aButton.grid(column=2,row=1)

aCheck = tk.IntVar()
aCheckbutton = tk.Checkbutton(zhuo,variable=aCheck,text='Disabled',state='disabled')
aCheckbutton.select()
aCheckbutton.grid(column=0,row=2)

bCheck = tk.IntVar()
bCheckbutton = tk.Checkbutton(zhuo,variable=bCheck,text='Unchecked')
bCheckbutton.grid(column=1,row=2)

cCheck = tk.IntVar()
cCheckbutton = tk.Checkbutton(zhuo,variable=cCheck,text='Checked')
cCheckbutton.select()
cCheckbutton.grid(column=2,row=2)

radVar = tk.IntVar()
COLORs = ['Green','Gold','Red']
def clickRad():
    flag = radVar.get()
    if flag==0:
        zhuo.configure(text=COLORs[0])
        mBox.showinfo('HH','w ai feiyueyin')
    elif flag==1:
        zhuo.configure(text=COLORs[1])
        mBox.showinfo('HH','w ai feiyueyin')
    elif flag==2:
        zhuo.configure(text=COLORs[2])
        mBox.showinfo('HH','w ai feiyueyin')
        
for col in range(3):
    rad = 'rad'+str(col)
    rad = tk.Radiobutton(zhuo,variable=radVar,value=col,text=COLORs[col],command=clickRad)
    rad.grid(column=col,row=3)
from tkinter import scrolledtext
scrW = 30
scrH = 3
aScrTxt = scrolledtext.ScrolledText(zhuo,height=scrH,width=scrW,wrap=tk.WORD)
aScrTxt.grid(row=4,column=0,columnspan=3)

aLabelFrame = ttk.LabelFrame(tab1,text='a label frame')
aLabelFrame.grid(column=0,row=1,sticky='W')

ttk.Label(aLabelFrame,text='a label with sooooooo much long').grid(column=0,row=0)
ttk.Label(aLabelFrame,text='a label').grid(column=0,row=1)
ttk.Label(aLabelFrame,text='a label').grid(column=0,row=2)

"""這個控件叫 Spinbox,用於上下調整的那種"""
from tkinter import Spinbox
aSpinbox = Spinbox(aLabelFrame,from_=0,to=10)
# aSpinbox = Spinbox(aLabelFrame,values=(1,2,5,10))    #也可以用 value 參數來設置
aSpinbox.grid(column=0,row=3)

for child in aLabelFrame.winfo_children():
    child.grid_configure(sticky='W')
    
for child in zhuo.winfo_children():
    child.grid_configure(sticky='W')
    
"""開發頁面2"""
muniao = ttk.LabelFrame(tab2,text='我是frame')
muniao.grid(column=0,row=0)
"""之後,所有的空間的 Master 就是 frame muniao了"""
dFlag = tk.IntVar()
eFlag = tk.IntVar()
fFlag = tk.IntVar()
dCheck = tk.Checkbutton(muniao,variable=dFlag,text='A',state='disabled')
eCheck = tk.Checkbutton(muniao,variable=eFlag,text='B')
eCheck.select()
fCheck = tk.Checkbutton(muniao,variable=fFlag,text='C')

i = 0
for child in muniao.winfo_children():
    child.grid(column=i,row=0,sticky='W')
    i+=1

radVar2 = tk.IntVar()
for i in range(3):
    rad = 'rad'+str(i+3)
    rad = tk.Radiobutton(muniao,variable=radVar2,value=i,text='radbutton'+str(i))
    rad.grid(column=i,row=1,sticky='W')
    
win.mainloop()

多文件開發

引子

首先要知道 Python 是順序執行的,因此如果不是 def 裏面的東西,Python 都會執行下去。那麼 if name == “main”: 是幹什麼用的呢?他是用來測試函數的。因此,一般開發中,不會在 if name == “main” 以外的地方寫除了 def 以外的代碼,當然,一般情況下(別噴我,我的確很菜)。比如下面的代碼,運行一下會是?

"""script1.py"""
A = 'zhuo'
def fun():
    print(A)
# import script2
if __name__ ==  "__main__":
    fun()
    print(A)

結果爲兩個 zhuo。

若有另一個代碼文件:

script2.py
A = 'muniao'
def fun():
    print('My name is zhuo mu niao')
    
if __name__ == '__main__':
    fun()

然後返回 script1.py,把# import script2取消註釋,再次運行,你猜會發現什麼?輸出還是兩個 zhuo,也就是說,每個腳本的環境都不是一樣的。因此,不能將 import 看成簡單的拷貝。於是,在不同腳本中,應該可以使用同一個變量名而不產生歧義

雙腳本開發

對於上面的很長的、兩個 tab 的代碼。我們是否可以把 tab2 單獨寫在另一個腳本文件中呢?這樣,在命名變量的時候,就不需要記住** “咦?我排序到哪了?應該是 e 吧” **這樣的傻瓜問題了。因此,試一下把?

把 tab2 開發代碼放到一個叫 prac2.py 的文件中:
在這裏插入圖片描述
答案是不行,因爲 prac2 中的 tab2 not defined!如果在 prac2 用 from prac1 import tab2呢?感覺可以,但實際上會導致彈出兩個窗口。難道我們就真的要屈服嗎?

這進一步說明了雙文本開發 GUI,可能還需要技巧?這個技巧是什麼呢?高手們,回答一下唄,評論區歡迎你!!

總結

經過上面的處理,相信我們的 gui 界面已經比較完善了。但是,內在的東西還是有所匱乏。比如,咱們把討厭的羽毛LOGO 給弄掉?如何彈窗?有沒有可能實現令一個鼠標停留之後就會跳出提示框的功能?完善再完善,是我們的目的。如何做呢?請看下一章:Python GUI:Tkinter——03

學習手記

我重新翹了一遍代碼,犯了某些錯誤:

  • Combobox 裏的參數是 textvariable
  • Checkbutton 而不是 Checkbox
  • from tkinter import scrolledtext,庫名總是小寫的。而類名中是大寫開頭,之後全小寫。對象名是駝峯標記發。所以,在實例化的時候,是 scrolledtext.Scrolledtext 而不是 xxx.ScrolledText
  • 在該頁面名的時候,不應該是 tab1 = ttk.Frame(tabControl, text=‘頁面1’),而是 tabControl.add(tab1, text='頁面1')

再說一次:當然,整個 GUI 經過上述完善之後,雖然有所改觀,但仍舊有進步的空間。比如怎麼彈窗?怎麼久置後彈出提示?怎麼修改 ICON,也即 LOGO?等等,都需要我們更進一步地改進。如果讀者們想要有進一步地提高,請看下一篇吧:Python GUI:Tkinter——03

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