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()

执行结果

相关推荐
长和信泰光伏储能6 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂7 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study7 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境8 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境8 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1238 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102169 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb009 小时前
一个项目带你入门AI应用开发01
python
0566469 小时前
Python康复训练——常用标准库
开发语言·python·学习
昆曲之源_娄江河畔10 小时前
Python如何安装flask, pymssql
开发语言·python·flask·pymssql