Tkinter库的学习记录11-事件和绑定

11、事件和绑定

其实GUI程序是一种事件导向的应用程序设计,事件的来源可能是用户单击鼠标、键盘输入或是Widget状态改变。tkinter提供一些机制让我们可以针对这些事件做做更进一步的处理,这些处理的方式称为事件处理程序。

11-1 Widget的command参数

在前面介绍了许多Widget控件,许多Widget的构造方法内可以看到command参数,例如,功能按钮(Button)、数值滚动条(Scale)等。其实这就是一个Widget的事件绑定的概念,当按钮事件发生、当数值滚动条值改变......就可以通过command=callback,设计callback函数,这个callback函数就是事件处理程序。

下列程序是对前面概念的复习。

当单击功能按钮或是选择复选框时,窗口下方会做出所执行的动作,所利用的就是Widget控件构造方法内的command参数。

python 复制代码
from tkinter import *

def pythonClicked():
    if varPython.get():
        lab.config(text="Select Python")
    else:
        lab.config(text="Unselect Python")

def javaClicked():
    if varJava.get():
        lab.config(text="Selected Java")
    else:
        lab.config(text="Unselected Java")

def buttonClicked():
    lab.config(text="Button Clicked")

root = Tk()
root.title("ch11_1")
root.geometry("300x180")

btn = Button(root, text="Click me", command=buttonClicked)
btn.pack(anchor=W)
varPython = BooleanVar()
cbnPython = Checkbutton(root, text="Python", variable=varPython, 
                        command=pythonClicked)
cbnPython.pack(anchor=W)
varJava = BooleanVar()
cbnJava = Checkbutton(root, text="Java", variable=varJava,
                      command=javaClicked)
cbnJava.pack(anchor=W)

lab = Label(root, bg="yellow", fg="blue", 
            height=2, width=12,
            font="Times 16 bold")
lab.pack()

root.mainloop()

执行结果

11-2 事件绑定

在tkinter应用程序中最后一个指令是mainloop(),这个方法是让程序进入事件等待循环,除了如11-1节的Widget控件状态可以调用相对应的事件处理程序外,tkinter也提供了为事件绑定处理程序的机制。它的语法格式如下。

widget.bind(event, handler)

上述绑定语法中Widget是事件的来源,可以是root窗口对象,或是任意的Widget控件,例如,功能按钮、选项按钮、复选框......handler是事件处理程序。

鼠标相关的事件如下表所示。

|---------------------|-----------------------------------------|
| 鼠标事件 | 说明 |
| <Button-1> | 单击鼠标左键,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <Button-2> | 单击鼠标中键(鼠标含三个键),鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <Button-3> | 单击鼠标右键,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <Button-4> | 鼠标滑轮向上滚动,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <Button-5> | 鼠标滑轮向下滚动,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <Motion> | 鼠标移动,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <B1-Motion> | 拖拽,按住鼠标左键再移动鼠标,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <B2-Motion> | 拖拽,按住鼠标中键再移动鼠标,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <B3-Motion> | 拖拽,按住鼠标右键再移动鼠标,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <ButtonRelease-1> | 放开鼠标左键,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <ButtonRelease-2> | 放开鼠标中键,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <ButtonRelease-3> | 放开鼠标右键,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <Double-Button-1> | 连按鼠标左键,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <Double-Button-2> | 连按鼠标中键,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <Double-Button-3> | 连按鼠标右键,鼠标光标相对控件位置会被存入事件对象的x和y变量 |
| <Enter> | 鼠标光标进入Widget控件 |
| <Leave> | 鼠标光标离开Widget控件 |
[ ]

键盘相关的事件如下表所示。

|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 键盘事件 | 说明 |
| <Focusln> | 键盘焦点进入Widget控件 |
| <FocusOut> | 键盘焦点离开Widget控件 |
| <Return> | 按下Enter键,键盘所有键都可以被绑定,例如,Cancel、BackSpace、Tab、Shift、Ctrl、Alt、End、Esc、Next(Page Down)、Prior(Page Up)、Home、End、Right、Left、Up、Down、F1 ~ F12、Scroll Lock、Num Lock |
| <Key> | 按下某键盘键,键值会被存储在event对象中传递 |
| <Shift-Up> | 按住Shift键时按下Up键 |
| <Alt-Up> | 按住Alt键时按下Up键 |
| <Ctrl-Up> | 按住Ctrl键时按下Up键 |

控件相关事件如下表所示。

|---------------|-------------------------------------------------|
| 控件事件 | 说明 |
| <Configure> | 更改widget控件的大小和位置,新控件大小的width与height会储存在event对象内 |

了解了以上事件绑定后,其实我们已经可以试着学习自我设计事件绑定处理程序,同时将事件处理程序与一般事件绑在一起。我们从先前的学习中可以知道,单击功能按钮时可以执行某个动作,所使用的是在Button()内增加command参数,然后单击功能按钮时让程序执行command所指定的方法。

其实设计功能按钮程序时,若是在Button()内省略command参数,所产生的影响是单击功能按钮是没有动作。然后我们可以使用本节的知识重新让单击功能按钮有动作产生,假设功能按钮对象是btn,可以使用下列方式建立单击与事件的绑定。

复制代码
btn.bind("<Button-1>", event handler)

下面程序实例:重新设计程序ch11_1.py,使用事件绑定方式让单击Click me按钮后可以列出"Button clicked"字符串。对这个程序而言,功能按钮就是bind()方法的事件来源。所以第24行用btn.bind()建立绑定工作。

python 复制代码
from tkinter import *

def pythonClicked():
    if varPython.get():
        lab.config(text="Select Python")
    else:
        lab.config(text="Unselect Python")

def javaClicked():
    if varJava.get():
        lab.config(text="Selected Java")
    else:
        lab.config(text="Unselected Java")

def buttonClicked(event):
    lab.config(text="Button Clicked")

root = Tk()
root.title("ch11_1")
root.geometry("300x180")

btn = Button(root, text="Click me")
btn.pack(anchor=W)
btn.bind("<Button-1>", buttonClicked)  # 单击Click me绑定buttonClicked
varPython = BooleanVar()
cbnPython = Checkbutton(root, text="Python", variable=varPython, 
                        command=pythonClicked)
cbnPython.pack(anchor=W)
varJava = BooleanVar()
cbnJava = Checkbutton(root, text="Java", variable=varJava,
                      command=javaClicked)
cbnJava.pack(anchor=W)

lab = Label(root, bg="yellow", fg="blue", 
            height=2, width=12,
            font="Times 16 bold")
lab.pack()

root.mainloop()

11-2-1 鼠标绑定的基本应用

下面程序实例:鼠标事件的基本应用,这个程序在执行时会建立300 x 180大小的窗口,当单击鼠标左键时,在Python Shell窗口中会列出单击事件时的坐标。

python 复制代码
from tkinter import *

def callback(event):
    print("clicked at", event.x, event.y)

root = Tk()
root.title("ch11_2")
frame = Frame(root, width=300, height=180)
frame.bind("<Button-1>", callback)
frame.pack()

root.mainloop()

执行结果

下面是Python Shell示范输出界面。

bash 复制代码
PS E:\code\python\GuiTkinter\ch11> python .\ch11.py
clicked at 151 64
clicked at 191 108

在程序第3行绑定的事件处理程序中必须留意,callback(event)需有参数event,event名称可以自定义,这时因为事件会传递事件对象给此事件处理程序。

下面程序实例:移动鼠标时可以在窗口右下方看到鼠标目前坐标。

python 复制代码
from tkinter import *

def mouseMotion(event):
    x = event.x
    y = event.y
    text = "Mouse location - x:{}, y:{}".format(x, y)
    var.set(text)

root = Tk()
root.title("ch11_2_1")
root.geometry("300x180")

x, y = 0, 0
var = StringVar()
text = "Mouse location - x:{}, y:{}".format(x, y)
var.set(text)

lab = Label(root, textvariable=var)  # 建立标签
lab.pack(anchor=S, side=RIGHT, padx=10, pady=10)

root.bind("<Motion>", mouseMotion)

root.mainloop()

执行结果

下面这个程序在执行时,如果鼠标光标进入Exit功能按钮,会在黄色底的标签区域显示"鼠标进入Exit功能按钮",如果鼠标光标在离开Exit功能按钮,会在黄色底的标签区域显示"鼠标离开Exit功能按钮",如果单击Exit按钮,程序结束。

python 复制代码
from tkinter import *

def enter(event):
    x.set("Enter the button area.")

def leave(event):
    x.set("Leave the button area.")

root = Tk()
root.title("ch11_3")
root.geometry("300x180")

btn = Button(root, text="Exit", command=root.destroy)
btn.pack(pady=30)
btn.bind("<Enter>", enter)          # 进入绑定enter
btn.bind("<Leave>", leave)          # 离开绑定leave

x = StringVar()
lab = Label(root, textvariable=x,
            bg="yellow", fg="blue",
            height=4, width=45,
            font="Times 12 bold")
lab.pack(pady=30)

root.mainloop()

执行结果

11-2-2 键盘绑定的基本应用

下面程序实例:这是一个测试键盘绑定的程序,在执行时会出现窗口,若是按Esc键,将出现对话框询问是否离开,单击"是"按钮可以离开程序,单击"否"按钮程序继续。

python 复制代码
from tkinter import *
from tkinter import messagebox

def leave(event):
    ret = messagebox.askyesno("ch11_4", "是否离开?")
    if ret == True:
        root.destroy()
    else:
        return

root = Tk()
root.title("ch11_4")

root.bind("<Escape>", leave)
lab = Label(root, text="测试Esc键",
            bg="yellow", fg="blue",
            height=4, width=15,
            font=('Times', 20, 'bold'))
lab.pack(padx=30, pady=30)

root.mainloop()

执行结果

下面程序实例:这个程序在执行时用作绑定事件key,整个程序执行时会将所按a...z键打印出来。这个程序第4行使用了比较少使用的repr()函数,这个函数会将参数处理成字符串。

python 复制代码
from tkinter import *

def key(event):                 # 处理键盘按a...z事件
    print("按了 " + repr(event.char) + " 键")      # 输出按键字符

root = Tk()
root.title("ch11_5")

root.bind("<Key>", key)         # <Key>键绑定Key函数

root.mainloop()

执行结果

下面是按键的结果

python 复制代码
PS E:\code\python\GuiTkinter\ch11> python .\ch11.py
按了 'a' 键
按了 'w' 键

11-2-3 键盘与鼠标事件绑定的陷阱

我们在第8章学习了框架Frame的观念,框架本身是一个Widget控件,在使用框架时需特别小心获得焦点的概念,当事件绑定与Frame有关时,必须在Frame获得焦点时,键盘绑定才可生效。

下面程序实例:键盘与鼠标绑定Frame对象的应用。

python 复制代码
from tkinter import *

def key(event):                 # 处理键盘按a...z事件
    print("按了 " + repr(event.char) + " 键")      # 输出按键字符

def coordXY(event):             # 列出鼠标坐标
    frame.focus_set()
    print("鼠标坐标: ", event.x, event.y)

root = Tk()
root.title("ch11_6")

frame = Frame(root, width=100, height=100)
frame.bind("<Key>", key)            # frame对象的<Key>绑定key
frame.bind("Button-1", coordXY)     # frame对象单击绑定corrdXY
frame.pack()
frame.focus_set()

root.mainloop()

执行结果 这个程序在执行时必须将鼠标光标放在窗口内,同时吸纳有鼠标单击,这是第7行同时使用frame.focus_set()让Widget控件frame获得焦点,然后按键才可以动作。

不知道为啥,只有按键有打印,鼠标单击没反应。

至于ch11_5程序在一开始时即可执行,原因是此程序是在root窗口执行绑定,在程序被启动时窗口已经获得焦点。

11-3 取消绑定

取消绑定obj的方法如下。

obj.unbind("<xxx>") # <xxx>是绑定方式

下面程序实例:这是一个tkinter按钮程序,在tkinter按钮下方有复选框bind/unbind。如果勾选这个复选框,相当于有绑定,在单击tkinter按钮时Python Shell会列出字符串"I like tkinter"。如果没有选择这个复选框,相当于没有绑定,在单击tkinter按钮时Python Shell没有任何动作产生。

python 复制代码
from tkinter import *

def buttonClicked(event):
    print("I like tkinter")

def toggle(onoff):
    if var.get() == True:
        onoff.bind("<Button-1>", buttonClicked)
    else:
        onoff.unbind("<Button-1>")

root = Tk()
root.title("ch11_7")
root.geometry("300x180")

btn = Button(root, text="tkinter")
btn.pack(anchor=W, padx=10, pady=10)

var = BooleanVar()
cbtn = Checkbutton(root, text="bind/unbind", variable=var,
                   command=lambda:toggle(btn))
cbtn.pack(anchor=W, padx=10)


root.mainloop()

执行结果

当按钮与复选框绑定时,单击tkinter按钮会在Python Shell窗口中打印"I like tkinter"字符串。

11-4 一个事件绑定多个事件处理程序

之前程序中使用bind()方法时可以绑定一个事件处理程序,tkinter也允许我们执行一个事件绑定多个事件处理程序,同样时使用bind()方法,但是新增加的事件处理程序需要在bind()方法内增加参数add="+"。

下面程序实例:一个单击功能按钮动作,会有两个事件处理程序做出响应。

python 复制代码
from tkinter import *

def btnClicked1():
    print("Command event handler. I like tkinter.")

def btnClicked2(event):
    print("Binding event handler. I like tkinter.")

root = Tk()
root.title("ch11_8")
root.geometry("300x180")

btn = Button(root, text="tkinter",
             command=btnClicked1)
btn.pack(anchor=W, padx=10, pady=10)
btn.bind("<Button-1>", btnClicked2, add="+") # 添加事件处理程序


root.mainloop()

执行结果

若单击tkinter功能按钮,可以在python shell窗口中看到执行两个事件处理程序的结果。

从上述我们也发现了当单击按钮事件发生时,程序会先执行bind()绑定的程序,然后再执行Button()内command指定的程序。

11-5 Protocols

Protocols可以翻译为通信协议,在tkinter内可以解释为窗口管理程序(windows Manager)与应用程序(Application)之间的通信协议。tkinter也支持使用绑定概念更改此通信协议

下面程序实例:单击通信协议(Protocols)内容窗口右上角的 × 按钮可以关闭窗口,它的名称是WM_DELETE_WINDOW。这个程序会修改此协议,改为单击此按钮后增加Messagebox,询问"结束或取消",若是单击"确定"按钮才会结束此程序。

python 复制代码
from tkinter import *
from tkinter import messagebox

def callback():
    ret = messagebox.askokcancel("OKCANCEL", "结束或取消?")
    if ret == True:
        root.destroy()
    else:
        return

root = Tk()
root.title("ch11_9")
root.geometry("300x180")
root.protocol("WM_DELETE_WINDOW", callback)

root.mainloop()

执行结果

相关推荐
IvanCodes11 分钟前
Python 基础语法(一):变量、数据类型与运算符
python
程序员三藏1 小时前
浅谈性能测试
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·性能测试
l1258651 小时前
# RAG向量数据库优化实战:HNSW索引调参与生产级性能设计
数据库·python·mysql·langchain
meilindehuzi_a2 小时前
Python 基础语法(1):常量、变量、类型、输入输出与运算符
开发语言·python
Zane19942 小时前
调用了 async 函数却没执行?一文讲透协程、event loop 与 await
后端·python
OPEN-F2 小时前
Python进阶教程:装饰器与生成器深入
开发语言·python
zhipujiaoyu2 小时前
2026年大数据专科何去何从?就业前景、发展趋势全揭秘!
大数据·python
l1258653 小时前
# RAG噪声知识库治理:一致性与可信度的四层防线设计
数据库·人工智能·python·自然语言处理·langchain
zoujiahui_20183 小时前
别再只写 np.random.seed() 了:default_rng 与 np.random、random 到底差在哪?
python
Patrick在香港3 小时前
Claude Agent 进阶:4 种工具调用编排模式,让 0820 那 13 个 endpoint 并行起来
python·ai·ai编程