代码之家  ›  专栏  ›  技术社区  ›  Denilson Sá Maia

当tkinter小部件值更改时如何运行代码?

  •  12
  • Denilson Sá Maia  · 技术社区  · 15 年前

    我用的是python和 Tkinter 我想要相当于 onchange 来自其他工具包/语言的事件。我想在用户更新某些小部件的状态时运行代码。

    就我而言,我有很多 Entry , Checkbutton , Spinbox Radiobutton 小部件。每当这些更改中的任何一个时,我都希望运行我的代码(在本例中,更新另一个面板上的文本框)。

    (请记住,用户可以使用鼠标或键盘与这些小部件进行交互,甚至可以使用ctrl+v粘贴文本)

    4 回复  |  直到 7 年前
        1
  •  9
  •   PM 2Ring    7 年前

    我认为正确的方法是 trace 在分配给小部件的tkinter变量上。

    例如。。。

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

    这个 w 在trace中,每当有人写(更新)变量时,就会告诉tkinter,每当有人在entry小部件中写东西时,就会发生这种情况。 oddblue . 跟踪总是将三个值传递给您列出的任何函数,因此您需要在函数中预期它们,因此 a,b,c . 我通常对它们什么都不做,因为我需要的一切都是本地定义的。据我所知 a 是变量对象, b 空白(不确定原因),以及 c 是跟踪模式(即 W )

    For more info on tkinter variables check this out.

        2
  •  8
  •   Bryan Oakley    14 年前

    我在TCL中解决这个问题的方法是确保checkbutton、spinbox和radiobutton小部件都与数组变量关联。然后我将在数组上放置一个跟踪,这将导致每次写入该变量时都调用一个函数。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()
    
        3
  •  1
  •   Community Mohan Dere    8 年前

    吉特很晚了,但可能有人会发现它很有用。

    整个想法来自 @bryan Oakley's post

    如果我理解的很好,主要的问题是检测入口小部件。探测到它 spinbox , Checkbutton Radiobutton 你可以用 command 创建小部件时的选项。

    抓住 <onChange> 在里面 Entry 小部件,你可以使用Bryan的方法使用tcl,它会生成这个事件。正如我所说,这不是我的解决方案,我只是稍微改变了一下。

    最后你可以这样使用它:

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

    当然,现在修改它来为许多实例(如您在问题中提到的)创建不同的回调很容易。

    我希望有人会发现它有用。

        4
  •  0
  •   pyfunc    15 年前

    到目前为止,我还没有在特金特遇到任何类似于onchange的事情。 小部件可以绑定到各种事件,我已经明确地做到了这一点。