Python GUI:Tkinter——04

Tkinter 中的數據類型

經過前面三章的學習,相信大家要看懂下面的代碼已經不是難事。如果沒有看過前面的章節,建議大家翻看Python GUI:Tkinter——03

下面這些代碼將演示如何使用 Tkinter 的數據類型。不得不說的是,前面的開發中,我們只是開發了一個空殼子。數據之間是如何傳遞的,我們尚未搞明白。但多少知道一個道理:Python 通過 tkinter 模塊來訪問 Tkinter,也即 API 的。因此,Tkinter 有自己的數據類型,我們在使用它是,要聲明,就像 C 語言和 Java 那樣。Python 則不然,它是動態類語言,因此不用聲明,但運行速度慢。

似乎有一種語言(順帶一提)結合了動態類和靜態類,它就是 Julia,據說不錯,我沒用過,也沒看過(你懂的)

import tkinter as tk
win = tk.Tk()

strVar = tk.StringVar()
strVar.set('Hello Zhuo muniao')
str_py = strVar.get()
print(str_py)    #輸出 Hello Zhuo muniao
"""讓我們看看各個 tkinter 變量的初始值吧"""
print(tk.IntVar())   #PY_VAR52
print(tk.DoubleVar())    #PY_VAR53
print(tk.BooleanVar())    #PY_VAR54
""" PY_VAR52,證明沒有值!!是這樣嗎,請繼續往下看"""
print(tk.IntVar().get())    #0
print(tk.DoubleVar().get())    #0.0
print(tk.BooleanVar().get())    #False

順帶一提,在聲明瞭 tkinter 變量後,可以在看到:
在這裏插入圖片描述

如何獲取控件的輸入呢?

一個帶有控件的輸入一般可以分爲:

  • 字符串輸入
  • 數值輸入

前者有 Entry、Scrolledtext;後者有 Combobox、Spinbox 等。當然,他們本質上都是字符串輸入。哈哈,皮了一下。也就是說,他們都能夠賦值給 StringVar 變量。然而,不需要多次一舉

比如在我們的定義的 Entry 、Combobx 控件中,我們曾經是這樣定義的:

......
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())
    mBox.showerror('錯誤提示框','你沒有錯,是我錯了')
aButton = ttk.Button(zhuo,text='click me',command=clickBut)
aButton.grid(column=2,row=1)
......

然後,在 clickBut 激活函數中,我們用 etyVar 和 comVar 來獲取控件的輸入。但這沒必要,只需要用 控件的 .get() 接口就行了。我們修改 clickBut 函數:

......
def clickBut():
    aButton.configure(text='hello'+aEntry.get()+aCombobox.get())
    mBox.showerror('錯誤提示框','你沒有錯,是我錯了')
......

然後發現功能還是一樣的。實際上,所有的控件,無論它什麼類型,都能用 .get() 接口來獲取其輸入。

OOP

爲什麼要面向對象?

  • 首先可以封裝,封裝的一大好處是,給變量弄靜態類。如上一節所述,能夠避免命名變量的麻煩
  • 其二,每個控件的 callback 函數可以隨便放,而不需要一定放在前面(也即打破了聲明)
  • 其三,多文件編程。在第二章中,我們做了一個偉大的、但失敗了的嘗試,然而 OOP 做到了。

Tkinter 的 GUI 是如何工作的

Tkinter 實現的 GUI ,或者說大部分的軟件,他們都是以一個無限循環事件來工作的。所有代碼都在循環執行。更確切地說,GUI 是靜止的,直到用戶發出某個事件時,才暗流湧動。比如,某個點擊事件,會觸發 GUI 程序的一系列響應。假若用戶將 GUI 高高掛起,或者“走開”了一段時間,那麼 GUI 就像洋娃娃一樣一動不動。

OOP 開發

我們將上一章的代碼改寫爲 OOP,如果沒有閱讀過上一章,點擊Python GUI:Tkinter——03跳轉

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as mBox
from tkinter import Menu
from sys import exit
from tooltips import *    #見上一章
from tkinter import scrolledtext
from tkinter import Spinbox

class OOP():
    def __init__(self):
        self.win = tk.Tk()
        self.win.title('標題')
        self.win.iconbitmap(r'../app.ico')
        self._createWidget()
        
    
    def _clickBut(self):
        self.aButton.configure(text='hello'+self.aEntry.get()+self.aCombobox.get())
        mBox.showerror('錯誤提示框','你沒有錯,是我錯了')
    def _quit(self):
        self.win.quit()
        self.win.destroy()
        exit()
    def _clickRad(self):
        flag = self._radVar.get()
        if flag==0:
            self.zhuo.configure(text=COLORs[0])
            mBox.showinfo('HH','w ai feiyueyin')
        elif flag==1:
            self.zhuo.configure(text=COLORs[1])
            mBox.showinfo('HH','w ai feiyueyin')
        elif flag==2:
            self.zhuo.configure(text=COLORs[2])
            mBox.showinfo('HH','w ai feiyueyin')
            
    def _createWidget(self):
        self.menuBar = Menu(self.win)
        self.win.configure(menu=self.menuBar)

        self.fileMenu = Menu(self.menuBar,tearoff=0)
        self.fileMenu.add_command(label='New')
        self.fileMenu.add_separator()
        self.subMenu = Menu(self.fileMenu,tearoff=0)
        self.subMenu.add_command(label='Save')
        self.subMenu.add_command(label='Save as')
        self.fileMenu.add_cascade(label='Save',menu=self.subMenu)
        self.fileMenu.add_command(label='Exit',command=self._quit)
        self.menuBar.add_cascade(label='File',menu=self.fileMenu)
        
        self.helpMenu = Menu(self.menuBar,tearoff=0)
        self.helpMenu.add_command(label='Help')
        self.helpMenu.add_command(label='About')
        self.menuBar.add_cascade(label='Help',menu=self.helpMenu)
        
        
        self.tabControl = ttk.Notebook(self.win)
        self.tab1 = ttk.LabelFrame(self.tabControl)
        self.tab2 = ttk.LabelFrame(self.tabControl)
        self.tabControl.add(self.tab1,text='頁面1')
        self.tabControl.add(self.tab2,text='頁面2')
        self.tabControl.pack(fill='both',expand=1)
        
        self.zhuo = ttk.LabelFrame(self.tab1,text='zhuo\'s GUI')
        self.zhuo.grid(column=0,row=0)
        
        ttk.Label(self.zhuo,text='Please put a name').grid(column=0,row=0)
        
        # self.etyVar = tk.StringVar()   知道了get接口後,可以不用多此一舉了。
        self.aEntry = ttk.Entry(self.zhuo)
        self.aEntry.grid(column=0,row=1)
        
        # self.comVar = tk.StringVar()
        self.aCombobox = ttk.Combobox(self.zhuo,state='readonly')
        self.aCombobox['value'] = (1,2,4,8)
        self.aCombobox.grid(column=1,row=1)
        
        self.aButton = ttk.Button(self.zhuo,text='click me',command=self._clickBut)
        self.aButton.grid(column=2,row=1)
        
        self.aCheck = tk.IntVar()    #問題:請問可以將這個也註釋掉嗎?
        self.aCheckbutton = tk.Checkbutton(self.zhuo,variable=self.aCheck,text='Disabled',state='disabled')
        self.aCheckbutton.select()
        self.aCheckbutton.grid(column=0,row=2)
        
        self.bCheck = tk.IntVar()
        self.bCheckbutton = tk.Checkbutton(self.zhuo,variable=self.bCheck,text='Unchecked')
        self.bCheckbutton.grid(column=1,row=2)
        
        self.cCheck = tk.IntVar()
        self.cCheckbutton = tk.Checkbutton(self.zhuo,variable=self.cCheck,text='Checked')
        self.cCheckbutton.select()
        self.cCheckbutton.grid(column=2,row=2)
        
        self._radVar = tk.IntVar()
        COLORs = ['Green','Gold','Red']
                
        for col in range(3):
            self.rad = 'rad'+str(col)
            self.rad = tk.Radiobutton(self.zhuo,variable=self._radVar
                                      ,value=col,text=COLORs[col],command=self._clickRad)
            self.rad.grid(column=col,row=3)
        
        scrW = 30
        scrH = 3
        self.aScrTxt = scrolledtext.ScrolledText(self.zhuo,height=scrH,width=scrW,wrap=tk.WORD)
        self.aScrTxt.grid(row=4,column=0,columnspan=3)
        
        createToolTip(self.aScrTxt,'I am a scrolledtext, I was created by zhuo muniao')
        
        self.aLabelFrame = ttk.LabelFrame(self.tab1,text='a label frame')
        self.aLabelFrame.grid(column=0,row=1,sticky='W')
        
        ttk.Label(self.aLabelFrame,text='a label with sooooooo much long').grid(column=0,row=0)
        ttk.Label(self.aLabelFrame,text='a label').grid(column=0,row=1)
        ttk.Label(self.aLabelFrame,text='a label').grid(column=0,row=2)
  
        self.aSpinbox = Spinbox(self.aLabelFrame)
        self.aSpinbox['value'] = (1,2,5,10)
        self.aSpinbox.grid(column=0,row=3)
        
        for child in self.aLabelFrame.winfo_children():
            child.grid_configure(sticky='W')
            
        for child in self.zhuo.winfo_children():
            child.grid_configure(sticky='W')
            
        """開發頁面2"""
        self.muniao = ttk.LabelFrame(self.tab2,text='我是frame')
        self.muniao.grid(column=0,row=0)
        """之後,所有的空間的 Master 就是 frame muniao了"""
        self.dFlag = tk.IntVar()
        self.eFlag = tk.IntVar()
        self.fFlag = tk.IntVar()
        self.dCheck = tk.Checkbutton(self.muniao,variable=self.dFlag,text='A',state='disabled')
        self.eCheck = tk.Checkbutton(self.muniao,variable=self.eFlag,text='B')
        self.eCheck.select()
        self.fCheck = tk.Checkbutton(self.muniao,variable=self.fFlag,text='C')
        
        i = 0
        for child in self.muniao.winfo_children():
            child.grid(column=i,row=0,sticky='W')
            i+=1
        
        self._radVar2 = tk.IntVar()
        for i in range(3):
            self.rad = 'rad'+str(i+3)
            self.rad = tk.Radiobutton(self.muniao,variable=self._radVar2,value=i,text='radbutton'+str(i))
            self.rad.grid(column=i,row=1,sticky='W')
            
            
oop = OOP()
oop.win.mainloop()

Python OOP“規則”

與 Java 不同,Python 不是一個純 OOP 的語言,這一點在其文件的命名上有所體現(Java 規定文件名必須是公有類名)。Python 既可以面向過程、也可以面向對象。另外,與 Java、C++ 的面向對象不同,Python 沒有規定所謂的共有類、私有類等概念。但是,爲了統一化編程,Python 通常是在變量(屬性)、函數(方法)的命名中做文章。

比如,__init__ 代表Python 中的固有方法,它不能隨便調用(然而你可以這樣做,雙下劃線只是建議你不要這樣做),單下劃線表示私有屬性和私有方法,比如 _var。另外,下劃線加在變量後面,則代表與 Python 中的預留字重疊。比如 len_。

當然,這些規則只是“道德”上的約束,所以你可以不遵守。你也可以隨便調用有下劃線的“私有”變量和方法,雖然開發者的初衷讓他是“私有”的。然而,無規矩不成方圓,之所以有規則,我相信也是爲了別人好。所以,還是乖乖遵守吧。

順帶一提,Java 在編譯成 .class 文件時(字節碼),都會查其安全性。我不知道 Python 有沒有,大神們,你們知道答案嗎?在評論區回覆一下吧!

另外 Python 默認所有類(其實Python所有東西都是類)都繼承自 Object,在定義 OOP 類的時候,其實也默認繼承了 Object 類了。

最後,就是類的所有屬性,最好在 __init__ 中都弄好,當然,這是不成文的規定。

雙文本開發與OOP

上第二章中,我們曾經做了一個美麗的嘗試——雙文本開發。畢竟,將所有東西都放在一個文件中,太讓人勞神了。然而,在第二章中,我們嘗試着用 import 實現雙文本開發,但是遺憾地折戟沉沙了。

不過,OOP 的出現,解決了這個問題。我們把 tab1 放在主文件下,把 tab2 的搭建單獨提取出來。首先是主文件

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as mBox
from tkinter import Menu
from sys import exit
from tooltips import *
from tkinter import scrolledtext
from tkinter import Spinbox
from tab2 import Tab2    #導入 文件 tab2
class OOP():
    def __init__(self):
        self.win = tk.Tk()
        self.win.title('標題')
        self.win.iconbitmap(r'../app.ico')
        self._createWidget()
        
   ......
       
		"""頁面2"""
        self.tab2Widget = Tab2(self.tab2)    
        i = 0
        for child in self.tab2Widget.muniao.winfo_children():
            child.grid(column=i,row=0,sticky='W')
            i+=1
        
        self._radVar2 = tk.IntVar()
        for i in range(3):
            self.rad = 'rad'+str(i+3)
            self.rad = tk.Radiobutton(self.tab2Widget.muniao,variable=self._radVar2,value=i,text='radbutton'+str(i))
            self.rad.grid(column=i,row=1,sticky='W')
            
            

oop = OOP()
oop.win.mainloop()

然後另起一個文件叫:tab2.py

import tkinter as tk
from tkinter import ttk
class Tab2():       
    """開發頁面2"""
    def __init__(self,tab):
        self._createWidget(tab)
    def _createWidget(self,tab):
        self.muniao = ttk.LabelFrame(tab,text='我是frame')
        self.muniao.grid(column=0,row=0)
        """之後,所有的空間的 Master 就是 frame muniao了"""
        self.dFlag = tk.IntVar()
        self.eFlag = tk.IntVar()
        self.fFlag = tk.IntVar()
        self.dCheck = tk.Checkbutton(self.muniao,variable=self.dFlag,text='A',state='disabled')
        self.eCheck = tk.Checkbutton(self.muniao,variable=self.eFlag,text='B')
        self.eCheck.select()
        self.fCheck = tk.Checkbutton(self.muniao,variable=self.fFlag,text='C')

就這樣,我們把文件分解成了兩個,這樣可以重複使用變量名,而不用當心變量的命名問題了。

畫布 Canvas 控件

基本操作

目前爲止,我們的控件都是展示字符串、展示數字用到。如何展示圖片、GIF 呢?這就要用到 Canvas 了。

然而,Canvas 一個比較不好的地方是,他只展示 GIF 圖片(動畫)。不過,我們先來看看其基本運用吧,首先我們再次創建一個 Tab,專門放置 Canvas 控件。然後,新建一個 tab3.py 文件,用於開發 Canvas,代碼如下:

import tkinter as tk
from tkinter import ttk

class Tab3():
    def __init__(self,tab):
        self._createWidget(tab)
        
    def _createWidget(self,tab):
        self.zhuo = tk.Frame(tab,bg='red')
        self.zhuo.grid(column=0,row=0)
        for i in range(2):
            self.canvas = tk.Canvas(self.zhuo,width=150,height=80,
                                    highlightthickness=0,bg='orange')
            self.canvas.grid(column=i,row=i)

之後,在 prac_oop 文件中,輸入:

from tab3 import *
......
self.tab3Widget = Tab3(self.tab3)
......

運行可得:
在這裏插入圖片描述

展示 GIF 圖像

Canvas 的可以用來展示GIF圖片,我們不妨在 tab3 中再次創建一個 canvas 控件:

......
        self.canvas2 = tk.Canvas(self.zhuo)
        self.image = tk.PhotoImage(master=self.zhuo,file=r'./test.gif')
        self.canvas2.create_image(0,0,anchor='nw',image=self.image)
        # 其中,anchor 參數爲圖像的左上角所在的位置。這裏 nw 表示 左上角,當然,也可以寫成大寫。
        self.canvas2.grid(row=3,column=0,columnspan=2)
......

效果如下:
在這裏插入圖片描述
但是,卻出現了兩個問題

  • GUI 變大了,使得 tab1、tab2 也變得非常大,以致於看起來很唐突
  • GIF 的動態屬性被砍了。我本來弄了一張動態圖,但 GUI 卻沒有動態地顯示他。

那麼,這兩個問題如何解決呢?大神們,發動你們的大腦,在評論區中回答一下吧!

展示非 GIF 圖像

遺憾的是,tk 並不支持除 GIF 以外的圖像展示,但是我們可以藉助其他的工具來實現它。

效果如下:
在這裏插入圖片描述
怎麼樣?大美女吧?哦不,感覺如何,是不是很震撼?不賣關子,我們上女…代碼

"""注意,由於圖像太大,爲了展示完整的妹妹,我們需要縮放一下圖像,以適應 canvas 的大小。"""
import tkinter as tk
from tkinter import ttk
from PIL import ImageTk,Image
class Tab3():
    def __init__(self,tab):
        self._createWidget(tab)
    
        
    def _createWidget(self,tab):
        self.zhuo = tk.Frame(tab,bg='red')
        self.zhuo.grid(column=0,row=0)
        for i in range(2):
            self.canvas = tk.Canvas(self.zhuo,width=150,height=80,
                                    highlightthickness=0,bg='orange')
            self.canvas.grid(column=i,row=i)
        
        self.canvas2 = tk.Canvas(self.zhuo)
        self._createImage()
        self.image = ImageTk.PhotoImage(self.pil_image)    #這裏就不是 tk.PhotoImage 了
        self.canvas2.create_image(0,0,anchor='nw',image=self.image)    #打開圖像
        self.canvas2.grid(row=3,column=0,columnspan=2)
        
    def _resize(self,w, h, w_box, h_box, pil_image):  
        f1 = 1.0*w_box/w 
        f2 = 1.0*h_box/h  
        factor = min([f1, f2])  
        width = int(w*factor)  
        height = int(h*factor)  
        self.pil_image = pil_image.resize((width, height), Image.ANTIALIAS) 

    
    def _createImage(self):        
        pil_image = Image.open(r'./test.jpg')    #記得在縮放圖像之前,要用 Image 模塊打開。
        w, h = pil_image.size
        w_box = 300   #大夥可以調節這個,來設置圖片的大小。當然,也建議讀者們把他設爲 公有的。
        h_box = 400
        self._resize(w,h,w_box,h_box,pil_image)

最終總結

本章最有料的,就是 OOP了。通過本章,我們學了:

  • 如何獲取控件的輸入
  • 把代碼轉換爲 OOP,相信讀者在自己轉換的過程中,已經體悟到了什麼(不可言傳的技術)
  • Canvas 控件展示: GIF、JPG(其他)等圖像

除了展示圖像之外,我們還希望可視化一些程序的處理結果,並將他們亦放到我們的 GUI 中。如何做呢?請看下一章:Python GUI:Tkinter——05

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