当 ttk 小部件被使用时,tkinter 函数会重复两次

tkinter function repeats itself twice when ttk widgets are engaged

当我只使用 tkinter 的小部件时,该程序按预期运行。当我使用 ttk 的小部件时,程序会自行重复两次。我几乎尽我所能来解决这个问题,我相信 *args 与它有关。有什么方法可以防止我的 function 两次 运行 吗?

from tkinter import *
from tkinter import ttk
root = Tk()

first = StringVar(root)
second = StringVar(root)
Ore = {'Options': [''], 'Yes': ['One'], 'No': ['Two']}
entry1 = ttk.OptionMenu(root, first, *Ore.keys())
entry2 = ttk.OptionMenu(root, second, '')
entry1.pack()
entry2.pack()


def _up_options(*args):
    print('update_options')
    ores = Ore[first.get()]
    second.set(ores[0])
    menu = entry2['menu']
    menu.delete(0, 'end')

    for line in ores:
        print('for')
        menu.add_command(label=line, command=lambda choice=line: second.set(choice))


first.trace('w', _up_options)

root.mainloop()

PS,我把 *args 放在我的函数中才能工作。如果有人能解释一下,我将不胜感激

您可以在报错信息中理解问题所在:

Exception in Tkinter callback Traceback (most recent call last): File "C:\Users\user\AppData\Local\Programs\Python\Python36\lib\tkinter__init__.py", line 1699, in call return self.func(*args) TypeError: _up_options() takes 0 positional arguments but 3 were given

最初,您不使用 _up_options 当您更改选项时,您调用 _up_options 来跟踪第一个 StringVar 并将其更改为字典中下一个对象的值。

现在,当您这样做时,您将 运行 连接到字典中的所有对象,因此,您需要 *args,因此 lambda 函数将 运行在给定的所有参数上!

关于您的问题:

When I use ttk's widgets the program repeats itself twice.

编辑

查看@fhdrsdg 的回答!

解决办法就是把command=tkinter._setit(self._variable, val, self._callback)改成command=self._callback

希望对您有所帮助!

我想我明白了。问题是该变量实际上由 ttk OptionMenu 设置了两次。

看看 tkinter OptionMenu 中的这段代码:

for v in values:
    menu.add_command(label=v, command=_setit(variable, v, callback))

这会使用 _setit 命令为每个值在菜单中添加一个按钮。当调用 _setit 时,它会设置变量和另一个回调(如果提供):

def __call__(self, *args):
    self.__var.set(self.__value)
    if self.__callback:
        self.__callback(self.__value, *args)

现在看看来自 ttk OptionMenu 的这段代码:

for val in values:
    menu.add_radiobutton(label=val,
        command=tkinter._setit(self._variable, val, self._callback),
        variable=self._variable)

而不是 command 这会向菜单添加 radiobutton。通过将所有单选按钮链接到同一个变量,它们都是 "grouped"。因为单选按钮有一个变量,当其中一个被点击时,变量被设置为按钮的值。在此旁边,添加了与 tkinter OptionMenu 中相同的命令。如前所述,这会设置变量,然后触发提供的另一个命令。如您所见,现在变量更新了两次,一次是因为它链接到单选按钮,另一次是因为它是在 _setit 函数中设置的。因为你跟踪变量的变化,变量设置了两次,你的代码也运行了两次。

因为变量在 ttk 代码中设置了两次,我想你对此无能为力。如果您不从代码的任何其他部分更改变量而不是从 OptionMenu,您可以选择不跟踪变量,而是将您的函数作为 command 添加到 OptionMenu:

entry1 = ttk.OptionMenu(root, first, *Ore.keys(), command=_up_options)

P.S。这是用 this commit after this bugreport.
引入的 我想在添加 variable=self._variable 时,命令应该更改为 command=self._callback.

不跟踪 StringVar,而是添加回调作为 OptionMenu 构造函数的命令参数。

我创建了 ttk.OptionMenu 的子class 来解决这个问题(以及提供稍微更简单的小部件用法和更有用的回调)。我认为这是一种比直接修改原始 class 或仅覆盖原始方法更稳定的方法,因为它保证了与未来 Tkinter 版本中 built-in/original 小部件的潜在更改的兼容性。

class Dropdown( ttk.OptionMenu ):

    def __init__( self, parent, options, default='', variable=None, command=None, **kwargs ):

        self.command = command
        if not default:
            default = options[0]
        if not variable:
            variable = Tk.StringVar()

        if command:
            assert callable( command ), 'The given command is not callable! {}'.format( command )
            ttk.OptionMenu.__init__( self, parent, variable, default, *options, command=self.callBack, **kwargs )
        else:
            ttk.OptionMenu.__init__( self, parent, variable, default, *options, **kwargs )

    def callBack( self, newValue ):
        self.command( self, newValue )

然后您可以像这样使用它:

def callback( widget, newValue ):
    print 'callback called with', newValue
    print 'called by', widget

options = [ 'One', 'Two', 'Three' ]

dropdown = Dropdown( parent, options, command=callback )
dropdown.pack()

除了避免双重跟踪问题外,与原始 ttk.OptionMenu 的其他显着差异包括不需要提供 Tkinter 变量或默认值(如果您不需要它们(默认项将是第一个)如果未提供,则选项列表中的项目),并能够在触发时获取调用回调函数的小部件。如果您有许多下拉小部件共享相同的回调并且您需要知道在调用中使用了哪一个,则后者非常有用。

在写完这篇文章后不久,我还找到了另一个使用 lambda 的解决方案: 我想我可能仍然会分享这个 Dropdown 小部件,因为它可以使更高级别的代码更简单,并且如果您要添加一些其他自定义方法,它会提供一个很好的基础。