Home » 笔记 » 【笔记】Python3 原生 GUI 模组 Tkinter 的初始化和部件运用

【笔记】Python3 原生 GUI 模组 Tkinter 的初始化和部件运用

发布于 April 7, 2022 笔记

Tkinter 初始化

主对象的创建与事件循环的开始
import tkinter as tk

root = tk.Tk()
root.mainloop()

将 root 作为 tkinter 基层窗口的实例化 object,这步操作会初始化窗口。如果程序在分配窗口之前使用了其他 tkinter 的方法,则会报 RuntimeError。初始化到 .mainloop 方法之间的部分为部件设计,所以用 .mainloop 来给实例化object的设计部分结尾,而 .mainloop 则代表开始进入事件循环,监控窗口事件。

关于如何理解 .mainloop,参考 Tkinter 中的 mainloop 应该如何理解.

窗口全局设置
root.title("Tiger's Notes")
# 窗口标题
root.geometry("1920x1080")
# 窗口初始大小
root.geometry("500x300+200+50")
root.geometry("500x300+-90+-10")
# 移动窗口在屏幕上的位置

窗口名字可以是任意 string,初始大小中的两个数字中间的乘号是小写的x,同样也需要是 string。如果在.mainloop 之前不做全局设置,则窗口标题默认为 tk,窗口大小默认为基层容器的长和宽。

如果将 .geometry 的语法写成 "axb+c+d" ,A和B正常读取,C和D则读为窗口在屏幕上的位置。 root.geometry("500x300+200+50") 意思为,将大小设置为500*300并将其放在屏幕坐标 (200, -50) 处。屏幕坐标的原点是最左上角。加号后方的数字也可以是负数,负数代表反向移动。

root.geometry("500x300+200+50")
print(type(root.geometry(None)))
# <class 'str'>
print(root.geometry(None))
# 500x300+200+50

如果将 .geometry 的 arg 换成 None,则返回一个当前窗口大小与位置信息的 string。返回格式与设定时的输入格式一样。

全局设置的动态更新
import tkinter as tk
import random

root = tk.Tk()

def changeTtlandSize():
    xRange = random.randint(500, 1000)
    yRange = random.randint(300, 600)
    displaySize = str(xRange) + "x" + str(yRange)
    root.title(displaySize)
    root.geometry(displaySize)
    
root.title("Click that button!")
buttonA = tk.Button(root, text="Click to change size", command=changeTtlandSize)
buttonA.pack()
root.mainloop()

全局设置貌似不能用 .StringVar 动态更新,但是可以在 .mainloop 循环中使用 command= 重新设置。上面的代码创建了一个窗口XY值分别在 500-1000 以及 300-600 之间取随机数后,使用该数值刷新窗口大小并且刷新窗口标题。

.geometry 方法是默认忽略 .resizable 的,意思说就算已经禁止调整宽高,还可以使用 .geometry 调整窗口。在使用了 .resizable 后,只有用户直接在视窗上的操作受到影响。

关于 .resizable 的具体用法,参考 resizable() method in Tkinter | Python

Tkinter 添加窗口部件

配置部件
tkObject.pack()
# 部件放置
tkObject.pack_forget()
# 部件隐藏体积;设为不可见
tkObject.destroy()
# 部件永久删除

.pack() 的安置算法是完全按照顺序,默认占有整行,但可以使用 side=anchor= 等属性来实现同行或者其他形式的安置。任何部件在父容器中出现的位置不是由设定函数的位置所决定顺序,而是由安置方法的顺序决定。

tkObject.grid(row=3, column=2)
# 第四行第三列
tkObject.grid(row=0, sticky=W)
# 第一行靠左
tkObject.grid_forget()
# 部件隐藏体积;设为不可见

.grid() 的安置算法是在父容器中模拟出一个表格,使用坐标位置放置。此方法在横向放置上较为容易。可以使用 sticky= 属性在长宽不规则的单元中限制部件的为位置。

关于 .grid() 的各种属性,参考 Python Tkinter Grid 布局管理器详解

Label 标签
root = tk.Tk()
var = tk.StringVar()
var.set("Tiger's Notes")
textInfoA = tk.Label(root, text="Tiger's Notes", font=('Arial', 60))
textInfoA.pack()
textInfoB = tk.Label(root, textvariable=var)
textInfoB.pack()
imageTk = tk.PhotoImage(file="/Users/tiger/Downloads/JiaRanDiana.png")
imageInfo = tk.Label(root, image=imageTk)
imageInfo.pack()
root.mainloop()

Label 标签中可以添加文字、动态文字、图片,具体用法如上。使用 font= 参数设置字体与大小,使用 textvariable= 参数设置动态文字的对象,使用 image= 参数设定 PhotoImage 对象。这里注意,image= 后面不能直接跟图像路径,需要使用 tk.PhotoImage(file=) 将本地图片先转化为 Tkinter 自己的图片格式。另外,目前受直接支持的图片格式有 GIF, PGM, PPM, PNG。如果对象是其他格式的话可以使用 PIL 的 Image 工具转换。

关于使用 PIL 转换图片格式,参考 基于 Python PIL 实现简单图片格式转化器

import tkinter as tk
from PIL import Image, ImageTk
import io
import requests

root = tk.Tk()
# 对象照片链接
url = "https://img.gejiba.com/images/43b77154489b7b0548d7386381f6eb03.png"
imageTKForm = ImageTk.PhotoImage(Image.open(io.BytesIO(requests.get(url).content)))
imageInfo = tk.Label(root, image=imageTKForm)
imageInfo.pack()
root.mainloop()

Label 还可以配合 ImageTk、io、requests 实现网页图片爬取并在窗口中显示,上方代码的方案全程没有文件储存,是完全基于在线处理。首先通过 requests.get().content 获取编码形式的图片,io.BytesIO 方法将编码写入 RAM,最后使用 PIL 的 Image 解码后用 ImageTk 转化为 Tkinter 格式。

关于 io 模块的具体用法,参考 StringIO 和 BytesIO

Button 按钮 / Entry 输入域
root = tk.Tk()
def getAndPrint():
    print(textInputBox.get())
textInputBox = tk.Entry(root, show=None)
textInputBox.pack()
getButton = tk.Button(root, text="Get and Print!", command=getAndPrint)
getButton.pack()
root.mainloop()

Button 为实体按钮,上面可以部署文字,也可以部署触发指令。这个指令可以是自带环境中的函数例如 quit(),也可以是自定义函数,需要注意 command= 后面的函数名不用带正反括弧也不用加引号。

Entry 为文本输入框,用户可以在界面中输入文字。当使用 .get() 方法时,获取当前存在于 Entry 中的信息,返回 String。文本输入框也可以有 text= 或者 textvariable= 属性,设置文字用于提醒用户在文本框内需要输入什么。

Label 的一个缺点是无法复制,这个问题的其中一个解决方法是用 Entry 并把输入框设定为只读模式,具体用法为 entry['state'] = 'readonly' 。这样文本框内显示的内容就可以被选中。show=None 属性限制文本框显示明文,使用 entry['show'] = '*' 即可让特定符号 "*" 代替显示内容,常用于密码输入。

关于 Tkinter Button 的详细使用方法,参考 tkinter-button 详解

Variable 动态
# 使用 StringVar 方法创建动态 String
root = tk.Tk()
var = tk.StringVar()
var.set("Type something!")
dynamicLabel = tk.Label(root, textvariable=var)
dynamicLabel.pack()
def refreshText():
    var.set(usrInput.get())
usrInput = tk.Entry(root, show=None)
usrInput.pack()
refreshButton = tk.Button(root, text="Refresh", command=refreshText)
refreshButton.pack()
root.mainloop()

Variable 的存在是为了解决 Python 不支持变量回溯的问题。目前 Tkinter 提供了总共 4 种 Variable,分别是 [BooleanVar(), DoubleVar, IntVar, StringVar] ,它们主要是 Type 上的区别。

主要方法为 .set().get(),用于拉取数据和设置数据。需要注意,拉取动态函数的时候会得到动态函数指定的 Type。举个例子,如果在开发环境中有一个别的模组中不是 String 的文字段,虽然它和 String 有几乎一样的 Library,但是拉取的时候还是会变成 String。

关于 Variable 函数的具体实现方法,参考 Python基础知识-GUI编程-TK-StringVar

Frame 容器
root = tk.Tk()
var = tk.StringVar()
var.set("Hide")
mainMenu = tk.Frame(root, height=100, width=100)
mainMenu.pack()
textInfo = tk.Label(mainMenu, text="Tiger's Notes")
textInfo.pack()
def hideOrDisplay():
    if var.get() == "Hide":
        var.set("Display")
        textInfo.pack_forget()
    else:
        var.set("Hide")
        textInfo.pack()
buttonA = tk.Button(root, text="Hide/Display", command=hideOrDisplay)
buttonA.pack()
root.mainloop()

Tkinter 中的 Frame 用于组织小部件,简单来说就是一个像 root 基层容器的子集容器。Frame 具有不可见实体,所以在 .pack() 后位置是固定的。如果选择按顺序放置零部件,则可以使用 Frame 充当占位符。这种用法比较适合在小部件的变量无法在运行中动态更新的情况下,比如说特殊的 type 例如图片的更新。

正确的用法为 frameA = tk.Frame(parent, options)。合理运用 options 也可以达到优化界面的效果,比如设置背景、长宽等。当需要将小部件放入 Frame 容器中,小部件的 parent 需要设置为容器 Object。

# Frame 使用 .winfo_children().destory()
root = tk.Tk()
dynamicFrame = tk.Frame(root)
dynamicFrame.pack()
dynamicLabel = tk.Label(dynamicFrame, text="Type something!")
dynamicLabel.pack()
def refreshText():
    for dynamicItem in dynamicFrame.winfo_children():
        dynamicItem.destroy()
    dynamicLabel = tk.Label(dynamicFrame, text=usrInput.get())
    dynamicLabel.pack()
usrInput = tk.Entry(root, show=None)
usrInput.pack()
refreshButton = tk.Button(root, text="Refresh", command=refreshText)
refreshButton.pack()
root.mainloop()

假设 Label 没有 textvariable= 这个属性可用,可以使用上面的方法在让某个小部件不改变全局位置的情况下进行自身属性完全刷新。.winfo_children() 方法会返回 [<tkinter.Label object .!frame.!label>]。这是一个包含了 Frame 中所有 Object 的列表。

主要阅读

辅助参考

Add Comment