9. GUI界面开发(tkinter)

一、前言

使用python最舒服的地方就在于,它内部有大量的库可供我们直接使用,而且使用起来还相当的方便。

前面章节我们一直围绕的都是基本语法的讲解,到目前为止,python大部分的基础语法我们就已经学习完了,已经完全可以开始写软件了。

所以从本章开始,就要开始带大家了解超脱语言本身的知识点,并学会如何使用python,通过大量好用的库,来完全一些看起来很麻烦的任务。

二、GUI与CLI对比

GUIGraphical User Interface(图形用户界面)的缩写,也就是我们平常电脑、手机上看到的各种软件界面,比如qq、微信等等。

而与之相对的就是CLI,是Command Line Interface(命令行接口)的简写,也就是到目前为止我们一直看到的输出字符的命令行窗口(也称终端)。

两者的区别非常明显,GUI适用于给那些对编程并不了解的人使用,它的编写会更加的复杂,使用起来会更加的简单,可以通过鼠标进行操作。

CLI则更适用于给专业的人使用,你只能通过敲击字符来控制程序,编写起来更加的简单,但使用起来更加麻烦,甚至很多时候你还需要有相关的专业知识才行。

三、GUI原理

在正式写GUI程序前,了解它的原理是很有必要的,否则你写完代码依旧迷迷糊糊不知缘由,一旦出错,很难找到问题所在。

如果你了解C/C++,那么可以参考另外一篇文章学习如何从零制作一个自己的窗口:windows编程入门

从前面的编程中我们已经看到了,程序的执行流程就是从开始到结束,顺序执行下来的,所以你想要让一个程序一直运行起来,那就只能使用循环:

cmd = input('输入命令:')
while cmd != 'exit':
    print(f'你输入的命令为:{cmd}')
    cmd = input('输入命令:')

print('程序退出!')

结果为:

image-20240310081644034

这就是一个最简单的CLI程序,你可以通过用户输入的不同命令,来执行不同的代码就行了。

GUI程序虽然复杂一点,但本质上却同样如此:

  1. 运行程序、进入循环
  2. 等待用户的鼠标、键盘消息
  3. 处理鼠标键盘消息

这就是一个GUI程序的基本运行流程,它有一个非常重要的概念:消息

这个消息就是当你鼠标移动的时候、当你键盘敲击的时候,你的电脑系统就会自动触发,并将这个消息发送给你这个GUI程序中,然后你处理这个消息就行了!

就是这么的简单!

如果是C/C++语言,这一过程并不简单,因为消息纷繁复杂,但这是在python语言中,我们了解到这里就已经足够了

一个GUI程序的大概流程为:

  1. 初始化界面(提前布局好我们的界面)
  2. 进入消息循环(一般GUI库会提供这样一个函数,然后程序运行到此就会卡住,等待系统不断发送消息过来)
  3. 处理发送来的消息

GUI库有很多,原理都是如此(这是因为操作系统底层就是这么干的),而本文所要介绍的是python自带的GUI库:tkinter

如果觉得自带的不好用,当然你也可以去使用其它第三方的GUI库,都是可以的,这在python中非常简单,后面章节我会再进行介绍。

注意:如果你安装python的时候是自定义安装,没有勾选td/tk and IDLE这个组件,那么请重新安装、并勾选上该组件。

因为它就是我们本文要用到的tkinter模块。

四、tkinter基本使用

1.主窗口

其官方教程可以点击这里查看。

一个最简单的GUI程序代码如下:

from tkinter import Tk

window = Tk()

window.mainloop()

实际上就只有两行代码:

  • Tk():通过该模块下的这个函数生成一个窗口实例对象
  • 调用窗口的mainloop函数,进入消息循环

是不是非常的简单!

image-20240310082130060

这个函数(类的初始化函数)有很多参数,但大多数情况下,你应该都用不上他们,而且这会涉及到很多底层的内容,避免加重学习负担,所以这里也不再过多介绍。

首先第一步肯定就是调整这个窗口的名称、大小、位置。

window = Tk()
window.title("第一个窗口") #设置窗口标题
window.geometry('600x400+200+100') #设置窗口大小、位置
window.mainloop()

设置标题比较简单,只是设置大小、位置稍微复杂一点。

函数geometry接受一个这种格式的字符串:宽 x 高 + x轴偏移 + y轴偏移

所以这里的'600x400+200+100'意思就是创建一个宽600,高400,x轴偏移200,y轴偏移100的窗口。

注意在计算机中,一般默认左上角为原点,左上角向右为x轴的正轴,左上角向下为y轴的正轴,而大多数时候初高中大学的数学课里面坐标轴一般选取的左下角,这一点是有区别的。

当然,偏移量比较麻烦,因为不同电脑的屏幕大小并不一样,所以一般都省略,只设置宽高就行了:

window.geometry('600x400') #设置窗口大小

如果想要居中显示,那就需要稍微计算一下:

window = Tk()
window.title("第一个窗口") #设置窗口标题
# 窗口的宽高
win_width=600
win_height=400
# 获取屏幕的宽度和高度
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
# 计算窗口的位置
x = (screen_width - win_width) // 2  # 窗口的x坐标
y = (screen_height - win_height) // 2  # 窗口的y坐标

window.geometry(f'{win_width}x{win_height}+{x}+{y}')
window.mainloop()

主要是通过两个函数winfo_screenwidthwinfo_screenheight来获取当前电脑屏幕的宽高。

然后就可以用这两个数据计算出窗口左上角的坐标,也就是窗口偏移量。

注意这里除法用的是//,即整除的意思,比如3//2=1而不是1.5

计算思路比较简单,就是窗口的中心要在屏幕的中心,所以:x+win_width/2=screen_width/2,移个项就行了,y轴同理

这里简单介绍了一个窗口应该如何使用,但仅仅只是这么一个窗口,是干不了什么事情的。

想要真正与用户交互,那就得用控件才行,比如常见的按钮编辑框进度条等等都是控件,它们都需要被放入到窗口中才能使用。

2.button

首先最简单的一个控件就是按钮了:

from tkinter import Tk, Button
def btn_click():
    print('按钮被点击了!')

window = Tk()
window.title("第一个窗口")
window.geometry('600x400')

btn = Button(window, text='这位是一个按钮', command=btn_click)
btn.pack()

window.mainloop()

需要关注的几个点:

  • 使用控件,你得先将其导入,比如这里的from tkinter import Button

  • 然后你就可以创建个按钮对象了:Button(),它有很多参数,而其中最主要最常用的就是这里用到的三个参数:父窗口、文本、以及回调函数

父窗口填到第一个位置即可,text就是按钮显示的文本,command就是当按钮被点击时,要执行的函数(这就是在处理消息循环中的消息,接受到了用户点击按钮的消息,然后写一个函数在被点击的时候调用即可)

  • 最后还需要设置显示在父窗口的位置:btn.pack(),这是按照父窗口垂直方向默认放置的。

运行效果如图:

image-20240310082342604

但很显然,这样摆放控件也太难看了,所以我们还需要学习一点布局的知识。

3.布局

tkinter库控件一共有三种布局方式:

  • Pack布局管理器:按父窗口顺序摆放,一般用的较少,支持填充、扩展、对齐等选项。
  • Grid布局管理器:通过将控件放置在网格中的单元格来布局窗口,更为常用。
  • Place布局管理器:允许你以绝对位置和大小来放置控件,相比于Grid布局,会更加自由。

下面分别来介绍一下。

首先是pack布局,用到的函数就是pack这个函数:

from tkinter import Tk, Button, X

win = Tk()
win.title('测试布局')
win.geometry('600x400')

btn1 = Button(win, text="按钮1")
btn1.pack()
btn2 = Button(win, text="按钮2", )
btn2.pack(fill=X)  # 向x轴填充
btn3 = Button(win, text="按钮3")
btn3.pack(expand=True)  # 向四周膨胀,占据剩余的空间

win.mainloop()

这里用到了它两个比较常用的参数:

  • fill:向哪一个方向填充,注意其值有NoneXYBOTH等参数,并且是从库tkinter 中导出来的
  • expand:当父窗口大小变化时,它占据的范围变不变化?默认为False

上面的代码运行结果为:

image-20240310082427814

它当然不止有这两个参数,那么这些参数去哪里看呢?

这就用到了IDE另一个功能,可以跳转到函数定义去,你只需要启用Fn键的同时、按F12,即可跳转到对应的源码位置。

然后你就能看到下图的注释,看看你需要哪些就按照注释所说的内容进行添写即可:

image-20240310082651039

注意其源码的写法,pack通过连等,最终等于了pack_configure这个函数的,所以我们应该看这个函数的定义、注释信息。

如果还不会用,那就问问chatgpt、或者浏览器搜一搜,都很容易得到答案。

然后是Grid管理器,函数名就是grid

btn1 = Button(win, text="按钮1")
btn1.grid(row=0, column=0)
btn2 = Button(win, text="按钮2", )
btn2.grid(row=0, column=1, columnspan=2)
btn3 = Button(win, text="按钮3")
btn3.grid(row=1, column=0, rowspan=2)

它最重要的就两个参数:

  • row:放在第几行
  • column:放在第几列

至于columnspanrowspan两个参数,则是用来合并单元格的,即:这个控件占用几行、或者几列,但这里由于由于控件较少,看不出太大的效果:

image-20240310082824793

同样的,可按上面提到的方式查看其定义,就可以看到我们可以填写哪些参数:

image-20240310082901813

最后一个Place布局,它最自由,你可以随意放置,函数名字就为place

btn1 = Button(win, text="按钮1")
btn1.place(x=20, y=100)
btn2 = Button(win, text="按钮2", )
btn2.place(x=20, y=140, width=80)
btn3 = Button(win, text="按钮3")
btn3.place(x=20, y=180, height=30)

但也有麻烦的地方,那就是你得自己计算控件的大小、位置。

它主要用到的就是上面看到的四个参数,分别为x轴、y轴坐标、宽、高:

image-20240310082942719

它同样也还有其它功能的参数:

image-20240310083012639

一般来说,一个窗口界面程序,布局很重要,因为它决定了最终可以呈现的效果。

对于最简单的程序来说,你可能会觉得pack更加方便,你啥都不用管。

而如果是对于需要精确布局的应用程序来说,grid布局更加常用。

至于最终的place布局,用的会稍微少一点,因为大多数时候父窗口都是可以调整大小的,而你又是绝对布局,写死了位置和大小。

此时一旦调整了窗口大小,可能瞬间就会变得不好看了。

4.Frame(以微信布局为例)

上面用到的仅仅只是两三个按钮控件来进行布局,你或许就已经感觉到麻烦了,而大多数应用程序至少也会有几十上百个控件,如果都这样来进行布局的话,那也太过于折磨了。

而一般应用程序都是可以划分区域的,比如电脑端微信:

image-20240310123110225

从整体上来看,你就可以将其划分为左、中、右这三块区域。

然后左边又可以分为上、下两块区域来分别放置一些按钮控件(只不过是带图的按钮)。

通过对应用程序界面不断的进行划分,你就可以大大的简化布局流程,而这里介绍的Frame控件就来用来完成这一个目标的。

它意为框架,其本身并不具有任何实际上的功能,但它却可以像控件一样被放置在父窗口内部,同时其内部也可以像窗口那样放置控件。

还是以上面微信界面为例,左、中、右就可以看作三个框架控件,代码中可以像下面这样写:

from tkinter import Tk, Frame, NSEW

win = Tk()
win.title('测试Frame控件')
win.geometry('600x400')

left = Frame(win, width='50', bg='#f00')
left.grid(row=0, column=0, sticky=NSEW)
center = Frame(win, width='200', bg='#0f0')
center.grid(row=0, column=1, sticky=NSEW)
right = Frame(win, bg='#00f')
right.grid(row=0, column=2, sticky=NSEW)

win.columnconfigure(2, weight=1)
win.rowconfigure(0, weight=1)

win.mainloop()

在这段代码中,我使用的是grid布局。

和正常流程一样,你如果想要使用这个Frame,那就必须要先引入:

from tkinter import Frame

它的第一个参数同样为父窗口,直接填写即可,除此之外,它还有许多可选参数:

image-20240310123238599

如果不知道这些参数有什么用,那就浏览器搜一搜就好了,我这里用了其中的width,即宽度,以及bg(background-color),即背景颜色,用的十六进制表示法(你可以在一些网站上找到好看的配色来替换)

left = Frame(win, width='50', bg='#f00')

然后使用grid布局函数时,还用到了一个参数sticky,因为在grid布局中,所有控件都在网格当中,当网格太大的时候控件应该在网格中如何放置呢?

这就是这个参数的用处:靠近网格的哪一边放置。

NSEW就是英文单词北、南、动、西四个单词的首字母缩写,可以任何组合,并在包中将其导出来就可以使用了: