python笔记:可视化界面写作尝试

0. 引言

故事的起因在于和一个朋友的聊天,他拜托我帮他看看一个系统的操作界面代码,然后我就想啊,能不能干脆我自己来写一个简单的操作界面呢?

工欲善其事,必先利其器。因此,这里我们先尝试基于莫烦python中的可视化界面写作教程来熟悉一下python中的可视化界面写作库tkinter。

所有的代码实现我们会放置到我们的GitHub仓库当中,仓库链接如下:


BTW:

  • 后来发现tkinter库事实上是一个非常简单的可视化界面写作仓库,实现简单但是功能相对单一,对于真正复杂的行为设计支持不充分,事实上在大型的可视化界面开发中基本不会使用这个仓库,挽尊。。。

又及:

  • 发现tkinter库的文档非常的不友好,可能就是用的人太少了吧,还不如直接看源码,然后看了一下别人的教程,发现居然基本和莫烦的教程一模一样,连代码样例都基本一致,也不知道谁抄谁的,后面就不多放了,参考链接中只给出莫烦的b站视频链接了。

1. 可视化界面的实现

首先,我们来看一下使用python中的tkinter库进行可视化界面编程的一般流程。

他有点像是使用matplotlib进行绘图的方式,首先创建一个基础画布(基础的窗口),然后定义不同的绘图曲线(窗口插件),然后将定义的曲线(窗口插件)放置到画布(窗口)上进行显示。

因此,这里,我们先使用一个简单的显示窗口来走一遍整个流程,介绍一下窗口创建的流程以及插件安放的方法。

1. 可视化界面的创建

这里,我们给出基于tkinter写作可视化界面的最基础代码如下。

我们的目标是创建一个窗口,然后在上面放上一个显示面板,显示面板中打印出hello world文本。

这个功能算是一个最为基础的可视化界面写作了。

我们给出代码实现如下:

import tkinter

class Window:
    def __init__(self):
        self.window = self.build_window()
        self.add_panel()

    def build_window(self):
        window = tkinter.Tk() # 创建窗口实例
        window.title("user interface") # 设置窗口名称
        window.geometry("400x300") # 设置窗口大小
        return window

    def add_panel(self):
        self.panel = tkinter.Label(self.window, text="hello world!", bg="red", font=("Arial", 12), width=20, height=4) # 创建显示面板
        self.panel.pack() # 将显示面板放置到窗口当中

    def run(self):
        self.window.mainloop()
        return

if __name__ == "__main__":
    window = Window()
    window.run()

可以看到:

  • 窗口的实例化主要通过tkinter.Tk()进行实例化,而后我们只需要在其中加入组件就行了;
  • 窗口可以通过title函数进行窗口命名;
  • 窗口可以通过geometry方法进行窗口大小设置。

窗口类包含的主要方法包括:

  1. mainloop():启动窗口,并循环更新状态;
  2. quit():关闭窗口;

Tk类事实上还包含有大量其他的方法,但是文档中没有很好的说明,而且似乎也并不常用,有兴趣的读者可以直接阅读源码进行学习,这里就请容我直接跳过了。

2. 组件的部署

如前,我们事实上已经初步看到了界面中组件的使用方法,它基本包含两个步骤:

  1. 定义组件;
  2. 将组件安放到可视化界面窗口当中;

而要将组件加载到界面当中,我们只需要使用使用pack()方法即可。

pack()方法的一些常用参数定义如下:

  • anchor: 组件的定位核心锚点定义;
  • side: 组件的安放位置;

更多相关的定义可以直接查看源码,源码路径如下:

3. 组件位置调整

最后,我们来看一下组件位置的调整方法。

除了在定义过程中进行位置指定之外,我们也可以使用place()grid()两个方法进行组件位置的设置。

我们来分别考察一下这两个函数:

  1. place(x, y, anchor="nw")
    • x: 从左上角开始的横向像素点坐标;
    • y: 从左上角开始的纵向像素点坐标;
    • anchor: 目标组件的定位点,默认为左上角(nw)
  2. grid(row, column, padx, pady)
    • row: 横坐标
    • column: 纵坐标
    • padx, pady: 两个网格点之间的间隔;

这里,**比较建议使用place()**方法。

grid()方法的坑还是蛮多的,包括但不限于:

  1. grid的坐标是全局指定的,他会对所有的组件的grid网格进行统计,然后取最小的网格点作为起始坐标进行计数,也就是说,当只有一个网格点时,无论你指定坐标为多少,事实上都是绘制在(0,0)坐标上,因为起始点就是当前坐标;
  2. 当有中间间隔时,比如(0,0)(2,2),grid不会自动去计算间隔,而是非常粗暴地将其视作(1,1)进行处理,即按照坐标往后平移一个位置,也就是说,坐标点相互之间只有相对关系,而不代表绝对关系
  3. padx, pady事实上是部分全局的,同一横坐标的pady和同一纵坐标下的padx是相同的,而两行(列)之间的距离事实上是相邻两行的pady(padx)之和,而padx,pady的单位倒同样是像素;

另外,需要特别注意的是:

  • place以及grid方法必须在组件已经配置到窗口界面之后才会起效,即其必须写在pack函数之后!

2. 一些主要组件的使用

下面,在介绍完了整体的可视化界面的使用方法之后,我们就来考察一下可以使用在界面中的具体组件。

结合上面介绍的组件使用方法,我们就可以编写一些基本的gui界面了。

1. 变量

tkinter中的所有变量事实上都是tk.Variable类的一个子类,和C语言中的参数定义没啥区别,就是实现申明一个变量,然后进行赋值和获取。

所有的变量都包含下述两个方法:

  1. set():设置变量的值;
  2. get():获取变量的值;

下面,我们给出tkinter的变量类型如下:

  1. StringVar:default=""
  2. IntVar:default=0
  3. DoubleVar:default=0.0
  4. BooleanVar:default=False

2. 文本框类型组件

1. Label组件

Label组件有点类似于labview里面的indicator组件,作用是对某个定义好的参数或者常量进行显示,他不提供交互功能,仅仅提供显示功能。

上面,事实上我们已经使用过了,tkinter中的显示窗口定义方式就是实例化一个Label类。

我们给出其定义如下:

label = tk.Label(window, text="hello world", bg="red", font=("Arial", 12), width=15, height=3)
label.pack()

其中,text用于表示显示的文本,如果是一个常量,那么直接使用text进行赋值即可,如果是一个变量,则需要通过一个StringVar类进行间接赋值。

代码语言如下:

var = tk.StringVar(value="hello world")
component = tk.Label(window, textvariable=var, **kwargs)

它的好处在于可以通过var.set()方法对需要显示的内容进行调整。

另外,font是字体设置,而width和height是组件的长宽设置参数,他们的单位和像素的对应关系目前还没有一个比较确定的映射关系,不过经过尝试总结,有像素对应关系如下:

W = w i d t h × 10 + δ 1 H = h e i g h t × 20 + δ 2 \begin{aligned} W &= width \times 10 + \delta_1\\ H &= height \times 20 + \delta_2 \end{aligned} WH=width×10+δ1=height×20+δ2

2. Entry组件

Entry组件为一个字符输入控制插件,他的作用是可以让用户在给定的输入框当中输入文本,然后通过get内置函数就能够获取用户的输入。

Entry组件使用的典型代码如下:

entry = tk.Entry(window)
entry.pack()

而后,用户就可以在界面上的窗口中进行输入,我们通过entry.pack()方法就可以获取用户的输入内容了。

需要注意的是,Entry组件在定义是可以通过设置show参数将所有的输入显示全部转换为指定的特殊字符,例如:

entry = tk.Entry(window, show="*")
entry.pack()

此时所有的输入字符在界面上的显示都会被替换为*字符,特别适合于密码框的设置。

3. Text组件

Text组件算是结合了Label组件与Entry组件,它既可以允许用户直接对其中的字符串变量进行编辑,也可以对结果进行显示。

同样给出Text组件的典型使用方法如下:

text = tk.Text(window, width=20, height=2)
text.pack()

Text类的典型方法如下:

  1. get(self, index1, index2=None)
    • 这一方法的返回值为其本身字符串的相应index窗口部分,即:s[index1:index2]
    • 需要注意的是,他的index定义比较奇葩,具体可以参考文档:https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text-index.html,常用的包括:
      1. line.column格式,具体而言,0.0表示第0行第0列;
      2. insert:当前光标所处的位置;
      3. end:文本末端;
  2. insert(self, index, chars)
    • insert方法用于在现有的窗口字符串中的index位置插入字符串chars

4. Message组件

Message组件和Label组件基本是完全一样的,不过Label组件的显示框的长宽是一开始就定义好的,而Message组件的长宽则会根据输入文本的长度进行自适应的调整。

给出Message组件的典型使用代码如下:

message = tk.Label(window, text="hello world", bg="red", font=("Arial", 12), width=200)
message.pack()

需要注意的是,Message组件的的width定义单位为像素点,而且它指定的是最大的宽度,当文本宽度超过最大宽度时,文本会自动换行。

3. 按键类型组件

1. Button组件

tkinter中的Button组件事实上等价于LabVIEW当中的bool按键,它的基本用法如下:

button = tk.Button(window, text="Ok", width=10, height=2, command=fn)
button.pack()

其中,前面一些参数都好理解,唯一的差一点在于最后一个command参数,他的输入为一个行为函数fn,他的含义为触发函数,当点击行为发生时,就会触发行为函数fn,需要注意的是,他的行为发生方式为下沿触发,即当点击被释放时,触发行为函数。

2. Radiobutton组件

tkinter中的Radiobutton组件同样是一个bool按键的组件,直接给出他的常用代码方法如下:

var = tk.StringVar()
radio_button = tk.Radiobutton(window, text='Option A', variable=var, value='A', command=fn)
radio_button.pack()

这个插件比较坑爹,他的功能事实上就是每一次点击这个bool按键,就将variable赋值为value,如果variable与value相同,那么显示就是勾选,反之就是没有勾选。

因此,如果只有一个选项,我们就无法完成撤销操作,然后就比较呵呵了。

这个插件的一个典型用法就是作为单选题的实现。

3. checkbutton组件

同样,我们给出checkbutton的组件使用基础代码如下:

var = tk.BooleanVar(value=False)
button = tk.Radiobutton(window, text='on', variable=var, onvalue=True, offvalue=False, command=fn)
button.pack()

这一插件的典型用法为bool按键,它可以控制bool按键的状态为开还是关,每一次点击都会进行一次状态转换(开→关/关→开),然后触发一次行为函数fn

4. 可选项类型组件

另一些常用的输入组件是enum类型的输入组件,他们的输入是受到限制的,往往都是enum类型,这里,我们就来看一下这一类的插件的使用方法。

1. Listbox组件

同样,我们给出Listbox组件的使用典型代码样例如下:

var = tk.StringVar()
var.set(["A", "B", "C", "D"])
listbox = tk.Listbox(window, listvariable=self.var, **kwargs)
listbox.pack()

我们可以通过listbox.curselection()方法获取当前光标所处的选项index,然后通过listbox.get(index)方法我们即可获得用户的选项内容。

需要注意的是,当用户没有选择时,listbox.curselection()返回为一个空tuple。

2. Optionmenu组件

Optionmenu组件和Listbox组件在功能上事实上差不多,但是前者是平铺式的将所有可选项全部显示出来,而Optionmenu则是通过一个折叠窗口将选项进行了隐藏。

同样的,我们给出Optionmenu的典型使用方法如下:

var = tk.StringVar()
optionList = ["A", "B", "C", "D"]
om = tk.OptionMenu(window, var, *optionList)

用户的每一次选择操作都会执行一次赋值操作,即将选择的选项赋值给var参量。

而我们可以通过get()方法来获取OptionMenu的参数值。

3. Spinbox组件

5. 图片类组件

tkinter库同样支持图片的显示,和matplotlib等库绘制图片的方法大同小异,tkinter进行图片显示的方法同样是先定义一个画布,而后向画布当中填充元素。

1. Canvas组件

Canvas组件就是tkinter库中的画布组件,我们要进行图片的绘制,首先就要创建一个画布,即实例化一个Canvas类,然后将所有的图像元素添加到画布上之后在进行pack展示。

给出典型的代码样例如下:

canvas = tk.Canvas(window, height=200, width=200)
line = canvas.create_line(50, 50, 80, 80)
canvas.pack()
canvas.place(x=200, y=0, anchor="n")

下面,我们给出Canvas组件中的一些常用几何元素的添加命令如下:

  1. create_line():绘制直线(折线);
  2. create_oval():绘制椭圆;
  3. create_arc():绘制扇形;
  4. create_rectangle():绘制矩形;
  5. create_polygon():绘制曲线;
  6. create_text():绘制文本框;
  7. create_image():绘制图片,图片输入为下述PhotoImage组件实例;
  8. create_bitmap():绘制图片,图片输入为下述BitmapImage组件实例;

2. PhotoImage组件与BitmapImage组件

PhotoImage组件与下述的BitmapImage组件为tkinter中的两种图片载入实例。他们都可以通过传入文件路径的方式读取图片,然后通过canvas组件中的相应方法载入到画布上。

PhotoImage组件与BitmapImage组件的唯一区别就在于支持的图片格式的差别,两种组件所支持的图片格式分别如下表所示:

图片插件 图片格式
PhotoImage .gif, .pgm, .ppm
BitmapImage .xbm

6. 容器类组件

1. Frame组件

tkinter同样提供了一些容器类的组件,他们的作用是在原先的窗口中另外开辟出一个独立的空间来部署其他插件,其功能和labview当中的container模块作用一模一样。

这里,我们给出Frame组件的使用代码样例如下:

frame = tk.Frame(window)
frame.pack()

label = tk.Label(frame, text="hello world")
label.pack()

本质来说和普通的用法没啥大的区别。labview当中container往往用于代码的模块化管理,这里原则上应该也是相似的用法,不过我是没看出再不能够折叠组件的情况下有啥作用。。。

7. 菜单类组件

1. Menu组件

现在,我们来看一下如何在可视化交互界面当中加入菜单栏。

tkinter当中用于实现这一功能的组件为Menu组件,其基础的使用代码如下:

menu = tk.Menu(window)
filemenu = tk.Menu(menu)
menu.add_cascade(label="File", menu=filemenu)
filemenu.add_command(label="quit", command=window.quit)
window.config(menu=menu)

可以看到:

  • 事实上menu组件像是一个容器,它可以不断地通过add_cascade命令在上层menu当中定义新的menu的方式构建多层级的菜单栏;
  • menu中可以通过add_command方法在菜单栏中加入功能选项,其中的command参数即为点击该菜单项时会执行的行为函数;
  • menu的添加方式和其他组件的添加方式略有不同,需要通过config命令作为window的一个属性进行传入。

8. 消息窗口组件

1. tk.messagebox

如果想要通过一个额外弹出框来进行消息提醒的话,tkinter提供的组件支持为tk.messagebox,他是一系列窗口函数的集合。

我们直接罗列出其中的具体弹出窗口函数如下:

  1. showinfo(title, message, options)
    • 弹出信息显示窗口;
  2. showwarning(title, message, options)
    • 弹出警告显示窗口;
  3. showerror(title, message, options)
    • 弹出错误显示窗口
  4. askyesno(title, message, options)
    • 弹出是否确认窗口
  5. askretrycancel(title, message, options)
    • 弹出重试确认窗口
  6. askquestion(title, message, options)
    • 弹出问题确认窗口
  7. askokcancel(title, message, options)
    • 弹出确定取消窗口

3. 总结

上述便是tkinter库的一些基本的使用方法说明。

可以看到,除了一些坑之外,他们的用法事实上都是非常简单的,无奈官网的文档实在比较坑爹,各类教程又不太提及这个库,基本也就是莫烦那一套东西被搬来搬去,也不知道是谁抄谁的,有用法样例,但是细节内容也没有太过展开,想要了解基础用法的话是足够了,但是要想深入了解各部分功能的话还是有一点问题的。

这里,我们基于他们的教程对每个部分的插件使用方法进行了更加详细的讨论,某种意义上算是对官方文档的一点补充吧,也希望可以对想要使用tkinter库的朋友有所帮助。

不过总体而言事实上感觉是有点累赘的,毕竟tkinter这个库真心就只适合做做简单的窗口写作,不适合用于真正大规模的复杂窗口界面写作当中(大约也就是因为这个原因,官网的文档才会如此坑爹吧)。

Whatever,这里还是作为一个相对比较详细的文档介绍放在这里,希望对想要用tkinter写一些简单界面的朋友有所帮助吧。

上述所有的代码近期都会上传到我的GitHub仓库当中,仓库地址如下:

与君共勉!

4. 参考链接

  1. Tkinter 做简单的窗口视窗 (GUI 莫烦 Python 教程)
  2. https://www.runoob.com/python/python-gui-tkinter.html
  3. https://github.com/python/cpython/tree/master/Lib/tkinter/
  4. https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/index.html

猜你喜欢

转载自blog.csdn.net/codename_cys/article/details/112071861
今日推荐