Python GUI库 Tkinter入门资料 -- 基础篇

Tkinter 8.5官方开发文档:http://arcticfox1919.gitee.io/tkinter-doc/

Tcl Developer Xchange/Tcl 8.7/Tk8.7 组件开发文档:http://www.tcl-lang.org/man/tcl8.7/UserCmd/contents.htm

其他大神的博客资料:https://blog.csdn.net/qq_41556318/category_9283243.html

 

Tkinter是Python对Tcl组件的.py模块封装,有些说明Tkinter本身的开发文档是没有的,还需要去查询Tcl/Tk组件的开发文档,Tcl/Tk组件是一个泛用型GUI库,可以给C++,C#,JAVA调用用来做窗口程序。

 

1. 概述

1.1 简单程序示例

1.2 背景介绍

Tkinter(音为tea-kay-inter)是Tk的Python接口,Tk是Tcl/Tk的GUI工具包。

Tcl(工具命令语言,音为tickle)是嵌入式应用程序,测试,原型设计和GUI开发领域的流行脚本语言。另一方面,Tk是一个开源的多平台小部件工具包,许多不同语言都使用它来构建GUI程序。而Tkinter只是一个使用Tcl/Tk库的C扩展的包装器

Tkinter的优势

  • 简单易学(比Python的其他GUI库简单)
  • 跨平台
  • 标准库,无需安装

tkinter的文档

1.3 概念学习

GUI编程的三个核心问题

  • 屏幕上应显示哪些组件? 认识组件的形态

  • 组件应该放在哪里? 学习组件布局

  • 组件如何交互? 组件中的事件

1.3.1 “画板”—— 根窗口

GUI编程是一门艺术,就如同绘画,我们首先需要一个画板来展现。

import tkinter as tk 

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

1.3.2 组件(小控件)

有了画板,我们需要思考哪些组件应该出现在窗口中?

import tkinter as tk

root = tk.Tk()

label = tk.Label(root,text ="我是一个标签")
button = tk.Button(root,text ="我是一个按钮")

label.pack()
button.pack()

root.mainloop()

1.3.3 布局管理(几何管理器)

所谓布局,就是指控制窗体容器中各个控件(组件)的位置关系。tkinter 共有三种几何布局管理器,分别是:pack布局(类似html Div布局),grid布局(类似html Lable),place布局。

我们小时候都玩过积木,只要发挥创意,相同的积木可以堆出各种造型。tkinter的控件也可以看做一个个积木,形状或许不同,其本质都是一样的,都是一个积木。这些小控件都是有许多共性的,掌握重要的几个,其他的用法也都大同小异,因此在学习界面编程时,最重要的不是一开始急于学习每个积木的样子,不是学习每个控件怎么用,而是要学习这些控件该怎么放。初始学习中,怎么放远远比怎么用重要的多。而在网上大量的资料中,全都急迫的介绍每个组件怎么用,没人去讲清楚tkinter编程的一些概念,包括布局都是一笔带过,我认为这是本末倒置。

1.3.4 事件和回调

所谓事件,就是在用户按下一个键或点击鼠标时,应用程序需要做出的反应。简单说就是对外部刺激做出的反应。

命令绑定的示例

import tkinter as tk

def callback():
    print("被点击了")

root = tk.Tk()
tk.Button(root, text='单击', command=callback).pack()

root.mainloop()

 

2. 基础入门

本章从整体上学习GUI编程的相关概念,不仅仅是简单的hello world式浅尝即止的学习,而是做到知其然还要知其所以然,弄明白GUI编程是怎么回事。

 

2.1 基本控件

tkinter中,每个组件都是一个类,创建某个组件其实就是将这个类实例化。在实例化的过程中,可以通过构造函数给组件设置一些属性,同时还必须给该组件指定一个父容器,意即该组件放置何处。最后,还需要给组件设置一个几何管理器(布局管理器),解决了放哪里的问题,还需要解决怎么放的问题,而布局管理器就是解决怎么放问题的,即设置子组件在父容器中的放置位置。

基本控件学习

 

from tkinter import *

root = Tk()

Label(root, text="这是一个标签").pack()
Button(root, text="按钮").pack()
Checkbutton(root, text="多选框").pack()
Entry(root).pack()
Radiobutton(root, text="男", value=1).pack()
Radiobutton(root, text="女", value=2).pack()
Scale(root, orient='horizonta').pack()

root.mainloop()
  • Label 一个标签组件。主要用来实现显示功能,可以显示文字和图片。

    l = Label(parent, option=value ... ) 参数说明:

    • parent: 代表承载该按钮的父容器.
    • options: 可选项,即该按钮的可设置的属性。这些选项可以用键 =值的形式设置,并以逗号分隔

如下,分别使用了两个标签,一个显示文字,另一个显示了一张位图

from tkinter import *

root = Tk()
Label(root, text="我是标签").pack()
Label(root, bitmap="error").pack()
root.mainloop()

 

 

 

关于图片显示 

注意:Python内置了10种位图,可以直接使用,设置bitmap属性即可。 "error" "gray75" "gray50" "gray25" "gray12" "hourglass" "info" "questhead" "question" "warning"

 

效果 :

 

Python 中image属性仅支持gif、pgm、ppm格式,bitmap支持xbm格式。设置方法:

photo = PhotoImage(file="image.gif")
bmp = BitmapImage(file="logo.xbm")
label = Label(root,image = photo)

注意:image和bitmap参数两者只需设置一个,如果同时设置两个属性,则image 将优先

如需显示其他格式图片,则需要做一些特别处理,要用到Python的图像处理库——PIL库,但是PIL不支持Python3,且该库过于陈旧,不建议使用,这里可以选择其替代方案,Pillow库,函数使用方法与PIL相同。首先去网上下载并安装Pillow库

使用如下:

from PIL import Image, ImageTk
image = Image.open("F:\\001.jpg")
photo = ImageTk.PhotoImage(image)

#在Label中显示图片
label = Label(root,image = photo)
label.pack()
  • Button 一个简单的按钮,用来响应用户的一个点击操作。能够与一个函数关联,当按钮被按下时,自动调用该函数。它的属性可以直接参考标签,事实上按钮就是一个特殊的标签,只不过按钮多出了点击响应的功能

    b = Button(parent,option=value,... )

from tkinter import *

def onclick():
    print("点你妹啊 ")

root = Tk()
Button(root, text="按钮", command=onclick).pack()
root.mainloop()
  • Checkbutton 一个多选框组件。主要给用户提供多个选项的选中功能。 w= Checkbutton(parent,option,...)
from tkinter import *

def sel():
    val = "Checkbutton value is " + str(v.get())
    print(val)

root = Tk()
v = IntVar()
Checkbutton(root, text="点我啊", variable=v, command=sel).pack()

root.mainloop()
方法描述
deselect()清除单选按钮的状态
flash()在激活状态颜色和正常颜色之间闪烁几次单选按钮,但保持它开始时的状态。
invoke()可以调用此方法来获得与用户单击单选按钮以更改其状态时发生的操作相同的操作
select()设置单选按钮为选中。
toggle()反选。当前被选中,则调用之后变为未选中,反之则为选中。
  • Entry 一个单行文本输入框。可以用来接受用户的输入,但是只能输入一行。如果你只是想显示而不是编辑,那么应该使用标签。

    w = Entry(parent, option=value ... )

需要注意一点,Entry与 Lable 和 Button 不同,其text 属性是无效的。那么需要让Entry显示文字改如果操作呢?

from tkinter import *
root = Tk()
e = StringVar()
# 使用textvariable属性,绑定字符串型跟踪变量e
entry = Entry(root,textvariable = e)
e.set('请输入……')
entry.pack()
root.mainloop()

 

当用户在Entry输入密码时,希望输入的密码是不可见的,而不是明文,则可以使用show属性

from tkinter import *
root = Tk()
entry = Entry(root,show="*") entry.pack()
root.mainloop()

 

方法列表

方法描述
delete ( first, last=None )删除字符的部件,在指标之一,但不包括在最后位置的字符开始。如果第二个参数被忽略,只有在单个字符的位置被删除.
get()返回当前组件的字符串
icursor ( index )在给定索引处的字符之前插入光标
index ( index )移动entry的内容,使得给定索引处的字符是最左边的可见字符。 如果文本在entry中刚好完全显示,则不起作用。
insert ( index, s )将字符串s插入给定索引处的字符之前。
select_adjust ( index )此方法用于确保选中的部分包含指定索引处的字符。
select_clear()清除选中的。 如果当前没有选中的,则不起作用。
select_from ( index )将ANCHOR索引位置设置为由索引选择的字符位置,并选择该字符。
select_present()如果有选择,则返回true,否则返回false。
select_range ( start, end )在程序控制下设置选择。 选择从开始索引处开始的文本,但不包括结束索引处的字符。 起始位置必须在结束位置之前。
select_to ( index )选择从ANCHOR位置开始的所有文本,但不包括给定索引处的字符。
xview ( index )此方法在将Entry链接到水平滚动条时非常有用。
xview_scroll ( number, what )用于水平滚动Entry。 参数必须是UNITS,按字符宽度滚动,或者按页面大小来滚动。 数字是从左到右滚动的正数,负数从右到左滚动。
  • Radiobutton 单选按钮,即在同一组内只能有一个按钮被选中,每当选中组内的一个按钮时,其它的按钮自动改为非选中态,与其他控件不同的是,它有组的概念。

    w = Radiobutton(parent, option, ... )

from tkinter import *

def sel():
   selection = "You selected the option " + str(var.get())
   print(selection)

root = Tk()

#创建整型变量,用于绑定,相同的整型变量是为同一组
var = IntVar()

Radiobutton(root, text="Option 1", variable=var, value=1,command=sel).pack( anchor = W )
Radiobutton(root, text="Option 2",variable=var,value=2,command=sel).pack( anchor = W )
Radiobutton(root, text="Option 3", variable=var, value=3,command=sel).pack( anchor = W)

root.mainloop()
方法描述
deselect()清除单选按钮的状态
flash()在激活状态颜色和正常颜色之间闪烁几次单选按钮,但保持它开始时的状态。
invoke()可以调用此方法来获得与用户单击单选按钮以更改其状态时发生的操作相同的操作
select()设置单选按钮为选中。
  • Scale 一个滑块控件。用于在一个范围内,拖动它改变值的大小,例如音量条。
from tkinter import *

root = Tk()
v = IntVar()
# from_ : 设置最小值  to:设置最大值
# tickinterval:设置刻度   length:设置滑块的长度,单位为像素
Scale(root, orient='horizonta', variable=v, from_=0, to=200, tickinterval=50, length=200).pack()

root.mainloop()

跟踪控件的值

为了响应特定小控件的值的变化,Tkinter提供了自己的变量类,它们可以用来跟踪这些小控件的值。

# 字符串型
my_string = tk.StringVar()

# 布尔型
ticked_yes = tk.BooleanVar()

# 整型
group_choice = tk.IntVar()

# 浮点型
volume = tk.DoubleVar()

# 将变量类与控件关联
tk.Entry(root, textvariable=my_string)
tk.Checkbutton(root, text="选我", variable=ticked_yes)
tk.Radiobutton(root, text="点我", variable=group_choice) 
tk.Scale(root, label="音量", variable=volume)

变量类的操作,使用setget方法

# 向Entry输入框中设置文本
my_string.set("请输入内容")

# 获取Entry中用户输入的内容
my_string.get()

2.2 布局详解

pack 布局

使用 pack布局,将向容器中添加组件,第一个添加的组件在最上方,然后是依次向下添加。当pack布局不设置属性时,它只会占用能容纳下当前组件的最小空间

pack布局区分以下三种空间

  • 所属不明的,无人认领的空间

  • 要求但未使用的空间

  • 要求并已使用的空间

展开属性expand

“拉手效果布局”

import tkinter as tk

root = tk.Tk()

tk.Button(root, text="A").pack(side=tk.LEFT, expand=1)
tk.Button(root, text="B").pack(side=tk.LEFT, expand=1)
tk.Button(root, text="C").pack(side=tk.LEFT, expand=1)

root.mainloop()

综合示例

import tkinter as tk

root = tk.Tk()
frame = tk.Frame(root)

tk.Label(frame, text="Pack 布局的 side 和 fill").pack()
tk.Button(frame, text="A").pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text="B").pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text="C").pack(side=tk.RIGHT, fill=tk.NONE)
tk.Button(frame, text="D").pack(side=tk.TOP, fill=tk.BOTH)

# 需注意,顶部框架不会展开,也不会填充X或Y方向
frame.pack()

tk.Label(root, text="Pack 布局的 expand").pack()
tk.Button(root, text="我不扩展").pack()
tk.Button(root, text="我不向x方向填充,但我扩展").pack(expand=1)
tk.Button(root, text="我向x方向填充,并且扩展").pack(fill=tk.X, expand=1)

root.mainloop()

expandfill属性有什么区别? 相信绝大多数初次学习tkinter的人都会对这两个属性困惑,其实这两个属性是明显不同的,expand对应的正是要求但未使用的空间,而fill对应的是要求并已使用的空间。fill是填满的意思,将所有要求的空间占满。

pack常用属性

属性名属性简析取值取值说明
fill设置组件是否向水平或垂直方向填充X、Y、BOTH 和NONEfill = X(水平方向填充)fill = Y(垂直方向填充)fill = BOTH(水平和垂直)NONE 不填充
expand设置组件是否展开,当值为YES时,side选项无效。组件显示在父容器中心位置;若fill选项为BOTH,则填充父组件的剩余空间。默认为不展开YES 、NO(1、0)expand=YES expand=NO
side设置组件的对齐方式LEFT、TOP、RIGHT、BOTTOM值为左、上、右、下
ipadx、ipady设置x方向(或者y方向)内部间隙(子组件之间的间隔)可设置数值,默认是0非负整数,单位为像素
padx、pady设置x方向(或者y方向)外部间隙(与之并列的组件之间的间隔)可设置数值,默认是0非负整数,单位为像素
anchor锚选项,当可用空间大于所需求的尺寸时,决定组件被放置于容器的何处N、E、S、W、NW、NE、SW、SE、CENTER(默认值为CENTER)表示八个方向以及中心

注意:上表中取值都是常量,YES等价于"yes",亦可以直接传入字符串值。另外当界面复杂度增加时,要实现某种布局效果,需要分层来实现。

from tkinter import *

root = Tk()

# 使用Frame增加一层容器
fm1 = Frame(root)

Button(fm1, text='Top').pack(side=TOP, anchor=W, fill=X, expand=YES)
Button(fm1, text='Center').pack(side=TOP, anchor=W, fill=X, expand=YES)
Button(fm1, text='Bottom').pack(side=TOP, anchor=W, fill=X, expand=YES)
fm1.pack(side=LEFT, fill=BOTH, expand=YES)

fm2 = Frame(root)
Button(fm2, text='Left').pack(side=LEFT)
Button(fm2, text='This is the Center button').pack(side=LEFT)
Button(fm2, text='Right').pack(side=LEFT)
fm2.pack(side=LEFT, padx=10)


root.mainloop()

 

 

 

pack函数

函数名描述
pack_slaves()以列表方式返回本组件的所有子组件对象。
pack_configure(option=value)给pack布局管理器设置属性,使用属性(option)= 取值(value)方式设置
propagate(boolean)设置为True表示父组件的几何大小由子组件决定(默认值),反之则无关。
pack_info()返回pack提供的选项所对应得值。
pack_forget()Unpack组件,将组件隐藏并且忽略原有设置,对象依旧存在,可以用pack(option, …),将其显示。
location(x, y)x, y为以像素为单位的点,函数返回此点是否在单元格中,在哪个单元格中。返回单元格行列坐标,(-1, -1)表示不在其中
size()返回组件所包含的单元格,揭示组件大小。

grid 布局

又被称作网格布局,是最被推荐使用的布局。程序大多数都是矩形的界面,我们可以很容易把它划分为一个几行几列的网格,然后根据行号和列号,将组件放置于网格之中。使用grid 布局时,需要在里面指定两个参数,分别用row 表示行,column 表示列。需要注意的是 row 和 column 的序号都从0 开始,且每个网格只能容纳一个窗口小控件。

网格布局直观感受

from tkinter import *

root = Tk()

Label(root, width=15, height=3, bg="red").grid(row=0, column=0)
Label(root, width=15, height=3, bg="green").grid(row=0, column=1)
Label(root, width=15, height=3, bg="blue").grid(row=0, column=2)
Label(root, width=15, height=3, bg="white").grid(row=1, column=0)
Label(root, width=15, height=3, bg="black").grid(row=1, column=1)
Label(root, width=15, height=3, bg="grey").grid(row=1, column=2)

root.mainloop()

使用grid布局前,要打好腹稿,将界面划分成网格状

 

实例

import tkinter as tk

root = tk.Tk()
tk.Label(root, text="账号").grid(row=0, sticky=tk.W)
tk.Label(root, text="密码").grid(row=1, sticky=tk.W)
tk.Entry(root).grid(row=0, column=1, sticky=tk.E)
tk.Entry(root).grid(row=1, column=1, sticky=tk.E)
tk.Button(root, text="Login").grid(row=2, column=1, sticky=tk.E)
root.mainloop()

要注意,每列的宽度(或每行的高度)由网格中小部件的高度或宽度决定。如果需要额外的设置,只能通过设置网格中的小部件的宽度实现。 另外,可以使用sticky = tk.NSEW参数使小部件可扩展并填充网格的整个单元格。

在tkinter的官方文档中,并没有给出让grid布局适配根窗口拉伸的方法,但是通过查阅tcl/Tk的文档,可知设置权重能满足该需求。

# 第一个参数是想要设置的列的序号
root.grid_columnconfigure(1, weight=1)

# 用于设置行
# root.grid_rowconfigure(1, weight=1)

grid属性设置

属性名属性简析取值取值说明
row、columnrow为行号,column为列号,设置将组件放置于第几行第几列取值为行、列的序号,不是行数与列数row 和 column 的序号从0开始,但是,column的默认值是0,row的默认值是下一个编号较大的未占用行号
sticky设置组件在网格中的对齐方式(前提是有额外的空间)N、E、S、W、NW、NE、SW、SE类似于pack布局中的锚选项
rowspan组件所跨越的行数默认值为1取值为跨越占用的行数,而不是序号
columnspan组件所跨越的列数默认值为1取值为跨越占用的列数,而不是序号
ipadx、ipady、padx、pady组件的内部、外部间隔距离,与pack的该属性用法相同同pack同pack

关于sticky属性,还有一些组合用法

将小控件放置在单元格的一角

  • sticky=tk.NE 右上角

  • sticky=tk.SE 右下角

  • sticky=tk.SW 左下角

  • sticky=tk.NW 左上角

拉伸小控件

  • sticky=tk.N+tk.S 垂直拉伸小控件,并保持水平居中,其等价于字符串值"ns",以下同

  • sticky=tk.E+tk.W 水平拉伸且持垂直居中

  • sticky=tk.N+tk.E+tk.S+tk.W 水平和垂直拉伸,等价于常量tk.NSEW和字符串值nsew

  • sticky=tk.N+tk.S+tk.W 将垂直拉伸并向西(左)对齐

grid函数

函数名描述
grid_slaves()以列表方式返回本组件的所有子组件对象。
grid_configure(option=value)给pack布局管理器设置属性,使用属性(option)= 取值(value)方式设置
grid_propagate(boolean)设置为True表示父组件的几何大小由子组件决定(默认值),反之则无关。
grid_info()返回pack提供的选项所对应得值。
grid_forget()Unpack组件,将组件隐藏并且忽略原有设置,对象依旧存在,可以用pack(option, …),将其显示。
grid_location(x, y)x, y为以像素为单位的点,函数返回此点是否在单元格中,在哪个单元格中。返回单元格行列坐标,(-1, -1)表示不在其中
size()返回组件所包含的单元格,揭示组件大小。

grid布局配合使用单元格的合并,可以设计出复杂的界面

复杂示例

import tkinter as tk

parent = tk.Tk()
parent.title('Find & Replace')

tk.Label(parent, text="Find:").grid(row=0, column=0, sticky='e')
tk.Entry(parent, width=60).grid(row=0, column=1, padx=2, pady=2, sticky='we', columnspan=9)

tk.Label(parent, text="Replace:").grid(row=1, column=0, sticky='e')
tk.Entry(parent).grid(row=1, column=1, padx=2, pady=2, sticky='we', columnspan=9)

tk.Button(parent, text="Find").grid(row=0, column=10, sticky='e' + 'w', padx=2, pady=2)
tk.Button(parent, text="Find All").grid(row=1, column=10, sticky='e' + 'w', padx=2)
tk.Button(parent, text="Replace").grid(row=2, column=10, sticky='e' + 'w', padx=2)
tk.Button(parent, text="Replace All").grid(row=3, column=10, sticky='e' + 'w', padx=2)

tk.Checkbutton(parent, text='Match whole word only ').grid(row=2, column=1, columnspan=4, sticky='w')
tk.Checkbutton(parent, text='Match Case').grid(row=3, column=1, columnspan=4, sticky='w')
tk.Checkbutton(parent, text='Wrap around').grid(row=4, column=1, columnspan=4, sticky='w')

tk.Label(parent, text="Direction:").grid(row=2, column=6, sticky='w')
tk.Radiobutton(parent, text='Up', value=1).grid(row=3, column=6, columnspan=6, sticky='w')
tk.Radiobutton(parent, text='Down', value=2).grid(row=3, column=7, columnspan=2, sticky='e')

parent.mainloop()

place 布局

place布局 最简单最灵活的一种布局,使用组件坐标来放置组件的位置。但是不太推荐使用,在不同分辨率下,界面往往有较大差异。

place属性设置

属性名属性简析取值取值说明
anchor锚选项,同pack布局默认值为 NW同pack布局
x、y组件左上角的x、y坐标整数,默认值0绝对位置坐标,单位像素
relx、rely组件相对于父容器的x、y坐标0~1之间浮点数相对位置,0.0表示左边缘(或上边缘),1.0表示右边缘(或下边缘)
width、height组件的宽度、高度非负整数单位像素
relwidth、relheight组件相对于父容器的宽度、高度0~1之间浮点数与relx(rely)取值相似
bordermode如果设置为INSIDE,组件内部的大小和位置是相对的,不包括边框;如果是OUTSIDE,组件的外部大小是相对的,包括边框INSIDE、OUTSIDE(默认值INSIDE)可以使用常量INSIDE、OUTSIDE,也可以使用字符串形式"inside"、"outside"

place函数

函数名描述
place_slaves()以列表方式返回本组件的所有子组件对象。
place_configure(option=value)给pack布局管理器设置属性,使用属性(option)= 取值(value)方式设置
propagate(boolean)设置为True表示父组件的几何大小由子组件决定(默认值),反之则无关。
place_info()返回pack提供的选项所对应得值。
grid_forget()Unpack组件,将组件隐藏并且忽略原有设置,对象依旧存在,可以用pack(option, …),将其显示。
location(x, y)x, y为以像素为单位的点,函数返回此点是否在单元格中,在哪个单元格中。返回单元格行列坐标,(-1, -1)表示不在其中
size()返回组件所包含的单元格,揭示组件大小。

 

2.3 事件详解

添加命令

  • 无参数
import tkinter as tk

def callback():
    print("被点击了")

root = tk.Tk()
tk.Button(root, text='单击', command=callback).pack()

root.mainloop()
  • 有参数
import tkinter as tk

def callback(args):
    print("被点击了", args)

root = tk.Tk()
tk.Button(root, text='单击', command=lambda: callback("按钮")).pack()

root.mainloop()

如不想使用lambda表达式

import tkinter as tk

class Command:
    def __init__(self, func, *args):
        self.func = func
        self.args = args

    def __call__(self):
        self.func(*self.args)


def callback(args):
    print("被点击了", args)


root = tk.Tk()
tk.Button(root, text='单击', command=Command(callback, "按钮")).pack()

root.mainloop()

命令绑定的缺陷:

  • 只有Button和一小部分控件支持
  • 功能单一。命令只能绑定到鼠标左键单击和空格键点击事件,无法实现焦点、鼠标滚轮、快捷键响应等等丰富的交互情况

事件与绑定

所谓事件,就是在用户按下一个键或点击鼠标时,应用程序需要做出的反应。简单说就是对外部刺激做出的反应。

当窗口小控件中注册的事件发生时,会回调关联的处理函数,并将事件对象的实例作为参数传递到这个处理函数中。

事件参数文档

事件类型文档

鼠标事件

import tkinter as tk

root = tk.Tk()
tk.Label(root, text='单击').pack()


def callback(event):
    print("EventType=", event.type)
    print("Num=", event.num)


frame = tk.Frame(root, bg='khaki', width=100, height=80)
frame.bind("<Button-1>", callback)
frame.pack()

root.mainloop()

常用的几个鼠标事件

  • <Button-1>:鼠标左击事件
  • <Button-2>:鼠标滚轮点击
  • <Button-3>:鼠标右击事件
  • <Double-Button-1>:鼠标左双击事件
  • <Triple-Button-1>:鼠标左三击事件

关于事件回调传参 之前的命令回调我们都是通过lambda表达式传参,这里的事件回调也一样,只是稍微有一点区别

# 修改回调函数,添加a、b两个参数
def callback(event, a, b):
    print(a, b)
    print("EventType=", event.type)

# 注册回调时,在lambda表示中需要定义event参数
frame.bind("<Button-1>", lambda event: callback(event, 1, 2))

键盘事件

键名文档

import tkinter as tk

root = tk.Tk()
tk.Label(root, text='按键').pack()


def callback(event):
    print("EventType=", event.type)
    print("keysym=", event.keysym)


frame = tk.Frame(root, bg='khaki', width=100, height=80)
frame.pack()
root.bind("<KeyPress-a>", callback)  # a 键
root.bind("<KeyPress-F1>", callback)  # F1 键
root.bind("<Control-Alt-a>", callback)  # Ctrl + Alt + a 键

root.mainloop()

事件属性(Event类)

  • char 键盘事件,按键的字符
  • delta鼠标滚动事件,鼠标滚动的距离
  • heightwidth 仅用于Configure事件,即当控件形状发生变化之后的宽度和高度.相当于SizeChanged事件
  • keycode 键盘事件,按键码
  • keysym、 keysym_num 按键事件
  • num 鼠标事件,鼠标按键码,1为左键,2为中建,3为右键
  • serial 相当于Event的ID
  • state 用来表示修饰键的状态,即ctrl,shift,alt等修饰键的状态.
  • time 事件发生的时间
  • type 事件的类型
  • widget 事件源控件
  • xyx_rooty_root 鼠标事件中鼠标的位置。x_root、·y_root`为绝对坐标,x,y为相对坐标。

绑定级别

事件有三种绑定级别,实际开发中选择合适的一种即可

  1. 实例绑定。将事件绑定到一个特定的控件实例上。如上述代码中的鼠标事件绑定到Frame实例上

    frame.bind("<Button-1>", callback)
    
  2. 类级绑定。可以将事件绑定到类的所有窗口小控件上。典型使用如下,所有Entry控件都将绑定到事件,该事件将调用名为paste的函数,类级绑定须慎用,以免造成不可预知的问题!

    my_entry.bind_class('Entry','<Control-V>',paste)
    
  3. 应用程序级绑定。只要应用程序的任何一个窗口处于焦点,事件发生时回调函数都会被调用。

    root.bind_all('<F1>',show_help)
    

    只要应用程序处于焦点,按F1键将始终触发 show_help函数回调

取消绑定

事件被注册后,是可以取消的,三种级别绑定的取消方法如下

entry.unbind('<Alt-Shift-5>')
root.unbind_all('<F1>')
root.unbind_class('Entry', '<KeyPress-Del>')

手动模拟事件

有时候我们需要用代码模拟生成一个事件,发送给监听者,而不是通过操作事件源产生事件

tkinter中所有的widget都包含一个公共方法event_generate,这个方法就是用来产生相应的事件的

event_generate("<<Copy>>")

要想查看这些内置的事件,需要查看Tk/tcl的文档,tkinter只是对Tk/tcl的封装,因此tkinter的文档并不是非常全面

Tk 事件文档

 

2.4 运用面向对象范式

几乎所有的GUI程序中,都天生的适合运用面向对象思维开发。在GUI程序中使用面向过程的范式,则存在一些缺陷,这也是为什么C语言不适合开发大型GUI程序的原因

  • 需要定义大量全局变量
  • 需要在调用代码之前定义相关的函数
  • 最严重的是代码不可重用

OOP(Object Oriented Programming 面向对象程序设计)实际上是一种代码组织方式,它将焦点转移到我们想要操作的对象上,而不是操纵它们所需的逻辑。这与面向过程编程形成对比,面向过程编程将程序视为一个逻辑过程,它接受输入,处理并产生一些输出。

OOP提供了一些好处,例如数据抽象,封装,继承和多态。此外,OOP为程序提供了清晰的模块化结构。代码修改和维护很容易,因为可以在不修改现有对象的情况下创建新对象。

聚合式

通常在Python中写Tkinter界面程序时,很多人会使用这种聚合式的写法,实际上这并不是真正纯粹的面向对象的GUI写法,但tkinter本身也仅仅是对过程式的tcl库的封装,继承小控件并不能真正去重写控件的表现行为,因此是否继承也显得无关紧要,但是在类似Qt这种库中则不然。

下面提供一个编码示例

import tkinter as tk


class App:
    def __init__(self, root):
        self.root = root
        self.set_window()
        self.create_top()
        self.create_body()
        self.create_bottom()

    # 设置窗口
    def set_window(self):
        self.root.title("测试")
        self.root.resizable(False, False)

    # 创建顶部
    def create_top(self):
        tk.Label(self.root, text="Top").pack()

    # 创建主体
    def create_body(self):
        self.input = tk.StringVar()
        tk.Entry(self.root, textvariable=self.input).pack()

    # 创建底部
    def create_bottom(self):
        tk.Button(self.root, text="Bottom", command=self.onclick).pack()

    # 按钮回调函数
    def onclick(self):
        print(self.input.get())


if __name__ == "__main__":
    root = tk.Tk()
    App(root)
    root.mainloop()

继承式

上面已经说过了,通过继承不能改变控件的表现行为,但是继承式的写法,仍然会让代码更简洁,逻辑更清晰,在其他的面向对象的GUI库中,这是一种常用的写法。

下面是示例

import tkinter as tk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.set_window()
        self.create_top()
        self.create_body()
        self.create_bottom()

    # 设置窗口
    def set_window(self):
        self.title("测试")
        self.resizable(False, False)

    # 创建顶部
    def create_top(self):
        tk.Label(self, text="Top").pack()

    # 创建主体
    def create_body(self):
        self.input = tk.StringVar()
        tk.Entry(self, textvariable=self.input).pack()

    # 创建底部
    def create_bottom(self):
        tk.Button(self, text="Bottom", command=self.onclick).pack()

    # 按钮回调函数
    def onclick(self):
        print(self.input.get())


if __name__ == "__main__":
    app = App()
    app.mainloop()

 

2.5 附录:属性

大部分控件共享属性

选项(别名)说明单位典型值没有此属性的控件
background(bg)当控件显示时,给出的正常颜色colorgray25'、'#ff4400' 
borderwidth(bd)设置一个非负值,该值显示画控件外围3D边界的宽度;(特别的由relief选项决定这项决定).控件内部的3D效果也可以使用该值pixel3 
cursor指定控件使用的鼠标光标,该值可以是Tkinter接受的任何格式cursorgumby 
font指定控件内部文本的字体font'Helvetica'、('Verdana',8)Canvas
Frame
Scrollbar
Toplevel
foreground(fg)指定控件的前景色colorblack'、'#ff2244'Canvas
Frame
Scrollbar
Toplevel
highlightbackground指出经过没有输入焦点的控件加亮区域颜色color'gray30'Menu
highlightcolor指出经过没有输入焦点的控件周围长方区域加亮颜色color'royalblue'Menu
highlightthickness设置一个非负值,该值指出一个有输入焦点的控件周围加亮方形区域的宽度,该值可以是 Tk_GetCursor接受的任何格式.如果为0,则不画加亮区域pixel2.1mMenu
relief指出控件3D效果.可选值为RAISED,SUNKEN,FLAT,RIDGE,SOLID,GROOVE.该值指出控件内部相对于外部的外观样式,比如RAISED意味着控件内部相对于外部突出constantRAISED、GROOVE 
takefocus决定窗口在键盘遍历时是否接收焦点(比如Tab,shift-Tab).在设定焦点到一个窗口之前,遍历脚本检查takefocus选项的值,值0意味着键盘遍历时完全跳过,值1意味着只要有输入焦点(它及所有父代都映射过)就接收.空值由脚本自己觉定是否接收,当前的算法是如果窗口被禁止,或者没有键盘捆绑或窗口不可见时,跳过boolean1 YES 
width指定一个整数,设置控件宽度,控件字体的平局字符数.如果值小于等于0,控件选择一个能够容纳目前字符的宽度integer32Menu

多控件共享属性

选项(别名)说明单位典型值仅此类控件
activebackground指定画活动元素的背景颜色.元素(控件或控件的一部分)在鼠标放在其上并按动鼠标按钮引起某些行为的发生时,是活动的.如果严格的Modf一致性请求通过设置tk_strictModf变量完成,该选项将被忽略,正常背景色将被使用.对Windows和Macintosh系统,活动颜色将只有在鼠标按钮1被按过元素时使用colorred'、'#fa07a3'Button
Checkbutton
Menu
Menubutton
Radiobutton
Scale
Scrollbar
activeforeground指定画活动元素时的前景颜色.参见上面关于活动元素的定义color'cadeblue'Button
Menu
Checkbutton
Menubutton
Radiobutton
anchor指出控件信息(比如文本或者位图)如何在控件中显示.必须为下面值之一:N,NE,E,SE,S,SW,W,NW或者CENTER.比如NW(NorthWest)指显示信息时使左上角在控件的左上端constant Button
Checkbutton
Label
Message
Menubutton
Radiobutton
bitmap指定一个位图在控件中显示,以Tkinter(Tk_GetBitmap)接受的任何形式.位图显示的精确方式受其他选项如锚或对齐的影响.典型的,如果该选项被指定,它覆盖指定显示控件中文本的其他选;bitmap选项可以重设为空串以使文本能够被显示在控件上.在同时支持位图和图像的控件中,图像通常覆盖位图bitmap Button
Checkbutton
Label
Menubutton
Radiobutton
command指定一个与控件关联的命令.该命令通常在鼠标离开控件之时被调用,对于单选按钮和多选按钮,tkinter变量(通过变量选项设置)将在命令调用时更新commandfunction类型Button
Checkbutton
Radiobutton
Scale
Scrollbar
disabledforeground指定绘画元素时的前景色.如果选项为空串(单色显示器通常这样设置),禁止的元素用通常的前景色画,但是采用点刻法填充模糊化color'gray50'Button
Checkbutton
Radiobutton
Menu
Menubutton
height指定窗口的高度,采用字体选项中给定字体的字符高度为单位,至少为1integer1 4Button
Canvas
Frame
Label
Listbox
Checkbutton
Radiobutton
Menubutton
Text
Toplevel
image指定所在控件中显示的图像,必须是用图像create方法产生的.如果图像选项设定,它覆盖已经设置的位图或文本显示;更新恢复位图或文本的显示需要设置图像选项为空串image Button
Checkbutton
Label
Menubutton
Radiobutton
justify当控件中显示多行文本的时候,该选项设置不同行之间是如何排列的,其值为如下之一:LEFT,CENTER或RIGHT.LEFT指每行向左对齐,CENTER指每行居中对齐,RIGHT指向右对齐constantRIGHTButton
Checkbutton
Entry
Label
Menubutton
Message
Radiobutton
padx指定一个非负值设置控件X方向需要的边距.该值为Tkinter(Tk_GetPixels)接受的格式.当计算需要多大的窗口时,控件会把此值加到正常大小之上(由控件中显示内容决定);如果几何管理器能够满足此请求,控件将在左端或右端得到一个给定的多余空边.大部分控件只用此项于文本,如果它们显示位图或图像,通常忽略空边选项pixels2m 10Button
Checkbutton
Label
Menubutton
Message
Radiobutton
Text
pady指定一个非负值设置控件Y方向需要的边距.该值为Tkinter(Tk_GetPixels)接受的格式.当计算需要多大的窗口时,控件会把此值加到正常大小之上(由控件中显示内容决定);如果几何管理器能够满足此请求,控件将在上端或下端得到一个给定的多余空边.大部分控件只用此项于文本,如果它们显示位 图或图像,通常忽略空边选项pixels12 3mButton
Checkbutton
Label
Menubutton
Message
Radiobutton
Text
selectbackground指定显示选中项时的背景颜色color'blue'Canvas
Listbox
Entry
Text
selectborderwidth指定一个非负值,给出选中项的三维边界宽度,值可以是任何Tkinter(Tk_GetPixels)接受的格式pixel3Canvas
Entry
Listbox
Text
selectforeground指定显示选中项的前景颜色coloryellowCanvas
Entry
Listbox
Text
state指定控件下列两三个状态之一(典型是复选按钮): NORMAL和DISABLED或NORMAL,ACTIVE和NORMAL.在NORMAL状态,控件有前景色和背景显示;在ACTIVE状态,控件按activeforeground和activebackground选项显示;在DISABLED状态下,控件不敏感,缺省捆绑将拒绝激活控件,并忽略鼠标行为,此时,由disabled foreground和background选项决定如何显示constantACTIVEButton
Checkbutton
Entry
Menubutton
Scale
Radiobutton
Text
text指定控件中显示的文本,文本显示格式由特定控件和其他诸如锚和对齐选项决定string'Display'Button
Checkbutton
Label
Menubutton
Message
Radiobutton
textvariable指定一个变量名字.变量值被转变为字符串在控件上显示.如果变量值改变,控件将自动更新以反映新值,字符串显示格式由特定控件和其他诸如锚和对齐选项决定variablewidgetConstantButton
Checkbutton
Entry
Label
Menubutton
Message
Radiobutton
underline指定控件中加入下划线字符的整数索引.此选项完成菜单按钮与菜单输入的键盘遍历缺省捆绑.0对应控件中显示的第一个字符,1对应第二个,以此类推integer2Button
CheckButton
Label
Menubutton
Radiobutton
wraplength对于能够支持字符换行的控件,该选项指定行的最大字符数,超过最大字符数的行将转到下行显示,这样一行不会超过最大字符数.该值可以是窗口距离的任何标准格式.如果该值小于或等于0,不换行,换行只有在文本中的换行符的地方才出现pixel41,65Button
Checkbutton
Label
Menubutton
Radiobutton
xscrollcommand指定一个用来与水平滚动框进行信息交流的命令前缀,当控件窗口视图改变(或者别的任何滚动条显示的改变,如控件的总尺寸改变等等),控件将通过把滚动命令和两个数连接起来产生一个命令.两个数分别为0到1之间的分数,代表文档中的一个位置,0表示文档的开头,1.0表示文档的结尾处,0.333表示整个文档的三分之一处,如此等等.第一个分数代表窗口中第一个可见文档信息,第二个分数代表紧跟上一个可见部分之后的信息.然后命令把它们传到Tcl解释器执行.典型的,xscrollcommand选项由滚动条标识跟着set组成,如set.x.scrollbar set将引起滚动条在窗口中视图变化时被更新.如果此项没有指定,不执行命令function Canvas
Entry
Listbox
Text
yscrollcommand指定一个用来与垂直滚动框进行信息交流的命令前缀,当控件窗口视图改变(或者别的任何滚动条显示的改变,如控件的总尺寸改变等等),控件将通过把滚动命令和两个数连接起来产生一个命令.两个数分别为0到1之间的分数,代表文档中的一个位置,0表示文档的开头,1.0表示文档的结尾处,0.333表示整个文档的三分之一处,如此等等.第一个分数代表窗口中第一个可见文档信息,第二个分数代表紧跟上一个可见部分之后的信息.然后命令把它们传到Tcl解释器执行.典型的,yscrollcommand选项由滚动条标识跟着set组成,如set.y.scrollbar set将引起滚动条在窗口中视图变化时被更新.如果此项没有指定,不执行命令function Canvas
Entry
Listbox
Text

Button 控件属性示例

以下属性中,许多具有通用性,熟悉之后,其他组件也就掌握了,这里以Button为例,因Button具有代表性

Button常用属性

属性取值说明
text字符串按钮的文本内容
activebackground 当鼠标放上去时,按钮的背景色
activeforeground 当鼠标放上去时,按钮的前景色
bd(bordwidth)单位为像素,默认值为2个像素按钮边框的大小
bg(background) 按钮的背景色
command函数名的字符串形式按钮关联的函数,当按钮被点击时,执行该函数
fg(foreground) 按钮的前景色(按钮文本的颜色)
font 设置字体,还包含样式和大小
image 给按钮设置一张图像,必须是用图像create方法产生的
bitmap 指定按钮显示一张位图
justifyLEFT、CENTER、RIGHT显示多行文本的时候,设置不同行之间的对齐方式
padx单位像素按钮在x轴方向上的内边距,是指按钮的内容与按钮边缘的距离
pady单位像素按钮在y轴方向上的内边距
reliefRAISED,SUNKEN,FLAT,RIDGE,SOLID,GROOVE设置控件3D效果
stateDISABLED、ACTIVE、NORMAL设置组件状态。正常(normal)、激活(active)、禁用(disabled)
underline取值为整数,默认值为-1默认按钮上的文本都不带下划线。取值就是带下划线的字符串索引,为0时,第一个字符带下划线,为1时,前两个字符带下划线,以此类推
width单位像素按钮的宽度,如未设置此项,其大小以适应按钮的内容(文本或图片的大小)
height单位像素按钮的高度,同width属性
wraplength取值为整数,默认值0限制按钮每行显示的字符的数量
textvariable 指定一个变量名,变量值被转变为字符串在控件上显示。当变量值改变,控件也将自动更新
anchor取值可参考布局中的锚选项锚选项,控制文本的位置,默认为中心