怎样在Tkinter小部件值变化时运行代码?

18 投票
5 回答
31641 浏览
提问于 2025-04-16 05:06

我正在使用Python和Tkinter,想要实现类似其他工具包或语言中的onchange事件的功能。也就是说,我希望每当用户更新某些控件的状态时,就能运行我的代码。

在我的例子中,我有很多Entry(输入框)、Checkbutton(复选框)、Spinbox(数字选择框)和Radiobutton(单选按钮)控件。每当这些控件中的任何一个发生变化时,我都想运行我的代码(在这个情况下,是更新另一个面板上的文本框)。

(记住,用户可以通过鼠标或键盘与这些控件互动,甚至可以使用Ctrl+V来粘贴文本)

5 个回答

5

你有三种不同的方法来实现同样的功能:

1- 使用内置的“命令”配置,就像你在按钮上使用的一样。

import tkinter as tk
from tkinter import messagebox as tk_messagebox

def spinbox1_callback():
    tk_messagebox.showinfo("Spinbox callback", "You changed the spinbox.")

if __name__ == "__main__":
    master = tk.Tk()
    spinbox1 = tk.Spinbox(master, from_=0, to=10, command=spinbox1_callback)
    spinbox1.pack()
    tk.mainloop()

2- 使用事件绑定来捕捉特定的事件:
http://web.archive.org/web/20190712094613/http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm

import tkinter as tk
from tkinter import messagebox as tk_messagebox

root = tk.Tk()

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

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

root.mainloop()

3- “追踪” tkinter 变量类的变化,如果你的控件使用了 StringVar、BooleanVar、IntVar 或 DoubleVar 作为 textvariable 参数,那么一旦它被更新,你就会收到一个回调。
http://web.archive.org/web/20190712094613/https://effbot.org/tkinterbook/variable.htm

import tkinter as tk
from tkinter import messagebox as tk_messagebox


if __name__ == "__main__":
    master = tk.Tk()
    widget_contents = tk.StringVar()
    widget_contents.set('')
    some_entry = tk.Entry(master,textvariable=widget_contents,width=10)
    some_entry.pack()

    def entry1_callback(*args):
        tk_messagebox.showinfo("entry callback", "You changed the entry %s" % str(args))
        some_entry.update_idletasks()

    widget_contents.trace('w',entry1_callback)

    tk.mainloop()
11

我在Tcl中解决这个问题的方法是确保复选框、旋转框和单选按钮这些控件都和一个数组变量关联起来。然后我会在这个数组上设置一个监控,这样每次这个变量被修改时,就会调用一个函数。Tcl让这个过程变得非常简单。

可惜的是,Tkinter不支持直接使用Tcl数组。不过,幸运的是,我们可以稍微动动手脚来实现这个功能。如果你有兴趣,可以试试下面的代码。

关于这个代码的小插曲: 我今天早上花了大约半个小时把这个代码写出来。我其实并没有在任何实际的代码中使用过这种技巧。不过,我还是忍不住想挑战一下,看看怎么能在Tkinter中使用数组。

import Tkinter as tk

class MyApp(tk.Tk):
    '''Example app that uses Tcl arrays'''

    def __init__(self):

        tk.Tk.__init__(self)

        self.arrayvar = ArrayVar()
        self.labelvar = tk.StringVar()

        rb1 = tk.Radiobutton(text="one", variable=self.arrayvar("radiobutton"), value=1)
        rb2 = tk.Radiobutton(text="two", variable=self.arrayvar("radiobutton"), value=2)
        cb = tk.Checkbutton(text="checked?", variable=self.arrayvar("checkbutton"), 
                             onvalue="on", offvalue="off")
        entry = tk.Entry(textvariable=self.arrayvar("entry"))
        label = tk.Label(textvariable=self.labelvar)
        spinbox = tk.Spinbox(from_=1, to=11, textvariable=self.arrayvar("spinbox"))
        button = tk.Button(text="click to print contents of array", command=self.OnDump)

        for widget in (cb, rb1, rb2, spinbox, entry, button, label):
            widget.pack(anchor="w", padx=10)

        self.labelvar.set("Click on a widget to see this message change")
        self.arrayvar["entry"] = "something witty"
        self.arrayvar["radiobutton"] = 2
        self.arrayvar["checkbutton"] = "on"
        self.arrayvar["spinbox"] = 11

        self.arrayvar.trace(mode="w", callback=self.OnTrace)

    def OnDump(self):
        '''Print the contents of the array'''
        print self.arrayvar.get()

    def OnTrace(self, varname, elementname, mode):
        '''Show the new value in a label'''
        self.labelvar.set("%s changed; new value='%s'" % (elementname, self.arrayvar[elementname]))

class ArrayVar(tk.Variable):
    '''A variable that works as a Tcl array variable'''

    _default = {}
    _elementvars = {}

    def __del__(self):
        self._tk.globalunsetvar(self._name)
        for elementvar in self._elementvars:
            del elementvar


    def __setitem__(self, elementname, value):
        if elementname not in self._elementvars:
            v = ArrayElementVar(varname=self._name, elementname=elementname, master=self._master)
            self._elementvars[elementname] = v
        self._elementvars[elementname].set(value)

    def __getitem__(self, name):
        if name in self._elementvars:
            return self._elementvars[name].get()
        return None

    def __call__(self, elementname):
        '''Create a new StringVar as an element in the array'''
        if elementname not in self._elementvars:
            v = ArrayElementVar(varname=self._name, elementname=elementname, master=self._master)
            self._elementvars[elementname] = v
        return self._elementvars[elementname]

    def set(self, dictvalue):
        # this establishes the variable as an array 
        # as far as the Tcl interpreter is concerned
        self._master.eval("array set {%s} {}" % self._name) 

        for (k, v) in dictvalue.iteritems():
            self._tk.call("array","set",self._name, k, v)

    def get(self):
        '''Return a dictionary that represents the Tcl array'''
        value = {}
        for (elementname, elementvar) in self._elementvars.iteritems():
            value[elementname] = elementvar.get()
        return value


class ArrayElementVar(tk.StringVar):
    '''A StringVar that represents an element of an array'''
    _default = ""

    def __init__(self, varname, elementname, master):
        self._master = master
        self._tk = master.tk
        self._name = "%s(%s)" % (varname, elementname)
        self.set(self._default)

    def __del__(self):
        """Unset the variable in Tcl."""
        self._tk.globalunsetvar(self._name)


if __name__ == "__main__":
    app=MyApp()
    app.wm_geometry("400x200")
    app.mainloop()
14

我觉得正确的方法是对一个已经分配给控件的tkinter变量使用trace

比如说...

import tkinter

root = tkinter.Tk()
myvar = tkinter.StringVar()
myvar.set('')
mywidget = tkinter.Entry(root,textvariable=myvar,width=10)
mywidget.pack()

def oddblue(a,b,c):
    if len(myvar.get())%2 == 0:
        mywidget.config(bg='red')
    else:
        mywidget.config(bg='blue')
    mywidget.update_idletasks()

myvar.trace('w',oddblue)

root.mainloop()

trace中提到的w会告诉tkinter,每当有人写入(更新)这个变量时,就会发生什么。这种情况会在每次有人在输入框(Entry widget)中输入内容时发生,然后执行oddbluetrace会把三个值传递给你指定的函数,所以你需要在函数中准备好接收这些值,因此用a,b,c来表示。我通常对这些值不做处理,因为我需要的东西都是在函数内部定义的。根据我的理解,a是变量对象,b是空的(不太清楚为什么),而c是追踪模式(也就是w)。

想了解更多关于tkinter变量的信息,可以看看这个链接。

撰写回答