Python 每当 Tkinter 小部件值更改时如何运行代码?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3876229/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to run a code whenever a Tkinter widget value changes?
提问by Denilson Sá Maia
I'm using Python and Tkinter, and I want the equivalent of onchangeevent from other toolkits/languages. I want to run code whenever the user updates the state of some widgets.
我正在使用 Python 和Tkinter,并且我想要onchange来自其他工具包/语言的等效事件。我想在用户更新某些小部件的状态时运行代码。
In my case, I have many Entry, Checkbutton, Spinboxand Radiobuttonwidgets. Whenever any one of these changes, I want to run my code (in this case, update a text box on the other panel).
就我而言,我有很多Entry,Checkbutton,Spinbox和Radiobutton小部件。每当这些更改中的任何一个时,我都想运行我的代码(在这种情况下,更新另一个面板上的文本框)。
(just remember that user may interact with those widgets using either mouse or keyboard, and even using Ctrl+V to paste text)
(请记住,用户可以使用鼠标或键盘与这些小部件进行交互,甚至可以使用 Ctrl+V 粘贴文本)
回答by pyfunc
So far, I have not encountered any thing equivalent of onChange in Tkinter. Widgets can be bound to the various events and I have done that explicitly.
到目前为止,我还没有在 Tkinter 中遇到任何相当于 onChange 的东西。小部件可以绑定到各种事件,我已经明确地做到了这一点。
回答by Bryan Oakley
How I would solve this in Tcl would be to make sure that the checkbutton, spinbox and radiobutton widgets are all associated with an array variable. I would then put a trace on the array which would cause a function to be called each time that variable is written. Tcl makes this trivial.
我在 Tcl 中解决这个问题的方法是确保 checkbutton、spinbox 和 radiobutton 小部件都与数组变量相关联。然后我会在数组上放置一个跟踪,这将导致每次写入该变量时调用一个函数。Tcl 使这变得微不足道。
Unfortunately Tkinter doesn't support working with Tcl arrays. Fortunately, it's fairly easy to hack in. If you're adventurous, try the following code.
不幸的是,Tkinter 不支持使用 Tcl 数组。幸运的是,入侵相当容易。如果您喜欢冒险,请尝试以下代码。
From the full disclosure department: I threw this together this morning in about half an hour. I haven't actually used this technique in any real code. I couldn't resist the challenge, though, to figure out how to use arrays with Tkinter.
来自全面披露部门:我今天早上在大约半小时内把这个放在一起。我还没有在任何实际代码中实际使用过这种技术。不过,我无法抗拒挑战,要弄清楚如何在 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()
回答by dakov
It's quite late, but yet, somebody found something that might be useful.
已经很晚了,但是,有人发现了一些可能有用的东西。
The whole idea comes from @bryan Oakley's post
整个想法来自 @bryan Oakley 的帖子
If I understand well, the main problem is to detech Entry widget's . To detect it in spinbox, Checkbuttonand Radiobuttonyou can use commandoptions when creating widget.
如果我理解得很好,主要问题是取消 Entry 小部件的 . 为了检测它spinbox,Checkbutton并且Radiobutton你可以使用command创建窗口小部件时选择。
To catch the <onChange>in Entrywidget you can use Bryan`s approach using Tcl, which generates this event. As I said, this is not my solution, I've only changed it slightly for this case.
要捕获<onChange>inEntry小部件,您可以使用 Bryan 的方法使用 Tcl,它会生成此事件。正如我所说,这不是我的解决方案,我只是在这种情况下稍微改变了它。
For example:
例如:
import tkinter as tk
from tkinter import ttk
def generateOnChange(obj):
obj.tk.eval('''
proc widget_proxy {widget widget_command args} {
# call the real tk widget command with the real args
set result [uplevel [linsert $args 0 $widget_command]]
# generate the event for certain types of commands
if {([lindex $args 0] in {insert replace delete}) ||
([lrange $args 0 2] == {mark set insert}) ||
([lrange $args 0 1] == {xview moveto}) ||
([lrange $args 0 1] == {xview scroll}) ||
([lrange $args 0 1] == {yview moveto}) ||
([lrange $args 0 1] == {yview scroll})} {
event generate $widget <<Change>> -when tail
}
# return the result from the real widget command
return $result
}
''')
obj.tk.eval('''
rename {widget} _{widget}
interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
'''.format(widget=str(obj)))
def onEntryChanged(event = None):
print("Entry changed")
def onCheckChanged(event = None):
print("Check button changed")
def onSpinboxChanged(event = None):
print("Spinbox changed")
def onRadioChanged(event = None):
print("Radio changed")
if __name__ == '__main__':
root = tk.Tk()
frame = tk.Frame(root, width=400, height=400)
entry = tk.Entry(frame, width=30)
entry.grid(row=0, column=0)
generateOnChange(entry)
entry.bind('<<Change>>', onEntryChanged)
checkbutton = tk.Checkbutton(frame, command=onCheckChanged)
checkbutton.grid(row=1, column=0)
spinbox = tk.Spinbox(frame, width=100, from_=1.0, to=100.0, command=onSpinboxChanged)
spinbox.grid(row=2, column=0)
phone = tk.StringVar()
home = ttk.Radiobutton(frame, text='Home', variable=phone, value='home', command=onRadioChanged)
home.grid(row=3, column=0, sticky=tk.W)
office = ttk.Radiobutton(frame, text='Office', variable=phone, value='office', command=onRadioChanged)
office.grid(row=3, column=0, sticky=tk.E)
frame.pack()
root.mainloop()
Of course modify it to create different callback for plenty of instances (as you mentioned in the question) is easy now.
当然,修改它来为大量实例创建不同的回调(正如你在问题中提到的)现在很容易。
I hope somebody will find it useful.
我希望有人会觉得它有用。
回答by I_do_python
I think the correct method is to use traceon a tkinter variable that has been assigned to a widget.
我认为正确的方法是trace在已分配给小部件的 tkinter 变量上使用。
For example...
例如...
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()
The win trace tells tkinter whenever somebody writes (updates) the variable, which would happen every time someone wrote something in the Entry widget, do oddblue. The trace always passes three values to whatever function you've listed, so you'll need to expect them in your function, hence a,b,c. I usually do nothing with them as everything I need is defined locally anyway. From what I can tell ais the variable object, bis blank (not sure why), and cis the trace mode (i.e.w).
该w微量讲述的Tkinter每当有人写(更新)的变量,它会发生,每次有人在条目写的东西部件,做oddblue。跟踪始终将三个值传递给您列出的任何函数,因此您需要在函数中期望它们,因此a,b,c. 我通常不使用它们,因为无论如何我需要的一切都是在本地定义的。据我所知a,变量对象b是空白的(不知道为什么),并且c是跟踪模式(即w)。
回答by Emilio M.
You have three different ways of doing the same:
您可以通过三种不同的方式来做同样的事情:
1) Use the built-in "command" configuration, like the one you use on buttons
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) Use the event bindings to capture specific events: http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
2) 使用事件绑定来捕获特定事件: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) "trace" changes on a tkinter variable classes, so if your widget uses a StringVar, BooleanVar, IntVar, or DoubleVar in the textvariable parameter, you will get a callback once it gets updated. https://effbot.org/tkinterbook/variable.htm
3) tkinter 变量类上的“跟踪”更改,因此如果您的小部件在 textvariable 参数中使用 StringVar、BooleanVar、IntVar 或 DoubleVar,则一旦更新,您将收到回调。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()

