【问题标题】:tkinter function repeats itself twice when ttk widgets are engaged使用 ttk 小部件时,tkinter 函数会重复两次
【发布时间】:2018-11-06 11:53:39
【问题描述】:

当我简单地使用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 放在我的函数中工作。如果有人能解释一下,我将不胜感激

【问题讨论】:

  • 看我的回答,有什么问题就问吧!
  • @MosheSlavin 谢谢,我完全明白为什么我现在需要*args。我的主要问题仍然存在,我将进一步搜索 ttk 的文档。
  • 很高兴帮助...我没有看到您描述的问题:>'当我使用 ttk 的小部件时,程序会重复两次'你能详细说明吗?
  • 我无法在 linux 上复制该问题。当我选择一个选项时,_up_options 只会被调用一次。
  • @BryanOakley 既然我认为你是专家(并且你提出了我提到的错误报告),对我的回答有什么想法吗?

标签: python-3.x tkinter ttk


【解决方案1】:

我想我想通了。问题是该变量实际上被 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 添加到菜单中。所有单选按钮都通过将它们链接到同一个变量来“分组”。因为单选按钮有一个变量,所以当单击其中一个时,该变量被设置为按钮的值。在此旁边,添加了与 tkinter OptionMenu 中相同的命令。如前所述,这会设置变量,然后触发另一个提供的命令。如您所见,现在变量更新了两次,一次是因为它链接到单选按钮,另一次是因为它是在_setit 函数中设置的。因为您跟踪变量的变化并且变量设置了两次,所以您的代码也运行了两次。

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

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

附:这是在this bugreport 之后用this commit 介绍的。
我想在添加variable=self._variable 时,该命令应该已更改为command=self._callback

【讨论】:

  • 现在的问题是为什么这不会发生在我的电脑上我在 PycharmCE2018.2 中使用 Windows 10 和 Python 3.6 有什么想法吗?
  • 您能否检查您在 Python36\Lib\tkinter 中的 ttk.py 文件以包含 variable=self._variable 部分?对我来说,它在第 1643 行。 sys.version3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 16:07:46) [MSC v.1900 32 bit (Intel)]
  • 你是对的!我的文件不包含variable=self._variable 我添加了它,我可以看到它被调用了两次!我删除它并保存它现在它恢复正常了!感谢您的宝贵时间!
【解决方案2】:

你可以理解错误信息中的问题:

Tkinter 回调异常 回溯(最近一次通话最后): 调用中的文件“C:\Users\user\AppData\Local\Programs\Python\Python36\lib\tkinter__init__.py”,第 1699 行 返回 self.func(*args) TypeError: _up_options() 接受 0 个位置参数,但给出了 3 个

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

现在,当您执行此操作时,您将在字典中的所有对象上运行,因此,您需要 *args 以便 lambda 函数将在给定的所有参数上运行!

至于你的问题:

当我使用 ttk 的小部件时,程序会重复两次。

编辑

查看@fhdrsdg 的回答!

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

希望对您有所帮助!

【讨论】:

  • 我实际上建议不要删除variable,因为它还有其他用途,例如将复选标记放在正确的位置。相反,我会将command=tkinter._setit(self._variable, val, self._callback) 更改为command=self._callback
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    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 的解决方案:Passing OptionMenu into a callback (or retrieving a reference to the used widget) 我想我可能仍然会分享这个 Dropdown 小部件,因为它可以使更高级别的代码更简单,并且如果您有一些其他自定义方法要添加,它提供了一个很好的基础。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-07-08
      • 1970-01-01
      • 2011-04-30
      • 2018-03-11
      • 2017-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多