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

猜你喜欢

转载自blog.csdn.net/weixin_42141390/article/details/106492897