如何在Tkinter小部件值更改时运行代码?

2024-05-12 22:12:46 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在使用Python和Tkinter,我想要其他工具包/语言中的onchange事件的等价物。我想在用户更新某些小部件的状态时运行代码。

在我的例子中,我有很多EntryCheckbuttonSpinboxRadiobutton小部件。每当其中任何一个更改时,我都要运行我的代码(在本例中,更新另一个面板上的文本框)。

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


Tags: 代码用户语言工具包部件tkinter状态事件
1条回答
网友
1楼 · 发布于 2024-05-12 22:12:46

已经很晚了,但是,有人发现了一些可能有用的东西。

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

如果我理解的好,主要的问题是检测入口小部件的。要在spinboxCheckbuttonRadiobutton中检测它,可以在创建小部件时使用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()

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

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

网友
2楼 · 发布于 2024-05-12 22:12:46

在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楼 · 发布于 2024-05-12 22:12:46

我认为正确的方法是对已分配给小部件的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小部件中写入某些内容时都会发生这种情况),执行oddblue。跟踪总是将三个值传递给您列出的任何函数,因此您需要在函数中使用它们,因此a,b,c。我通常什么都不做,因为我所需要的一切都是在本地定义的。据我所知,a是变量对象,b是空白的(不知道为什么),而c是跟踪模式(即w)。

For more info on tkinter variables check this out.

相关问题 更多 >