Функция tkinter повторяется дважды, когда задействованы виджеты ttk

Программа работает как задумано, когда я просто использую 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 в моей функции работать. Если кто-нибудь может объяснить это, я был бы очень признателен

4 ответа

Решение

Я думаю, что я понял это. Проблема в том, что переменная фактически устанавливается дважды с помощью 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)

PS это было введено с этим коммитом после этого багрепорта.
Я думаю, при добавлении variable=self._variable команда должна была быть изменена на просто command=self._callback,

Вы можете понять проблему в сообщении об ошибке:

Исключение в обратном вызове Tkinter (последний последний вызов): файл "C:\Users\user\AppData\Local\Programs\Python\Python36\lib\tkinter__init__. Py", строка 1699, при вызове return 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,

Надеюсь, вы найдете это полезным!

Вместо трассировки StringVar добавьте обратный вызов в качестве аргумента команды для конструктора OptionMenu.

Я создал подкласс 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 или значение по умолчанию, если они вам не нужны (элемент по умолчанию будет первым элементом в списке опций, если нет при условии) и возможность получить виджет, который вызвал функцию обратного вызова, когда он сработает. Последнее очень полезно, если у вас много выпадающих виджетов, использующих один и тот же обратный вызов, и вам нужно знать, какой из них используется в вызове.

Вскоре после написания этого я также нашел другое решение с использованием лямбда: передача OptionMenu в обратный вызов (или получение ссылки на используемый виджет), я подумал, что все равно могу поделиться этим виджетом Dropdown, поскольку он может упростить код более высокого уровня, и он обеспечивает хорошую основу, если у вас есть другие настраиваемые методы, которые нужно добавить.

Другие вопросы по тегам