Неустойчивое поведение виджета при использовании tk.Variable в качестве атрибута виджета

В настоящее время у меня проблема с инициализацией и именованием Tkinter.Variable.

Настраивать:

У меня есть окно с рамкой (window_frame) и строкой меню вверху (tk.Menu). Каждый command в меню открывает новое приложение, которое очищает window_frame и заново наполняет его новыми виджетами. К некоторым виджетам прикреплен tk.Variable (например, tk.Variable, tk.StringVar и т. д.).

Проблема:

Я использую настраиваемые классы виджетов (для глобальной конфигурации), и изначально у меня были инициализированы переменные внутри виджета с именем, основанным на имени виджета, например:

<-SNIP->

# In the widgets module:
class CustomWidget(tk.Entry):
    def __init__(self, master=None, cnf = {}, **kw):
        value = kw.pop('value', None)
        tk.Entry.__init__(self, master, cnf, **kw)
        self.variable = tk.Variable(self, name=self._name + '_var', value=value)

def destroy_widgets(master):
    for child in master.winfo_children():
        destroy_children(child)
        child.destroy()

# In each app module:
def main(window_frame):
    # Destroy the widgets in the window_frame:
    destroy_widgets(window_frame)
    # Add new widgets to the window_frame:
    widgets = dict(
        child1 = dict(widget = CustomWidget(name='child1', master=master)),
        child2 = dict(widget = CustomWidget(name='child2', master=master)),
        # etc
        )
    # Keep track of the variable in the dict as well:
    for key in widgets:
        widgets[key]['variable'] = widgets[key]['widget'].variable

    # Notes:
    # window_frame is populated with widgets & a button to re-run main().
    # At root level, window_frame also has a menu to run main() 
    # from various apps.

<-SNIP->

Ожидаемое поведение заключалось в том, что после уничтожения виджета (и его переменной) каждый tk.Variable будет повторно инициализирован и получит новое значение.

Похоже, это работало для всех виджетов, кроме тех, которые основаны на ttk.Checkbutton, у которых кнопка проверки переходила в «альтернативное» состояние при изменении/перезагрузке приложения.

Однако при дальнейшей проверке все виджеты были затронуты.

Насколько я понимаю, после уничтожения виджетов в window_frame именованные tk.Variable все равно будут «установлены» в Tcl, когда виджеты (и их встроенные tk.Variable) будут инициализированы. Поэтому tkinter будет повторно использовать переменные Tcl с этими именами вместо инициализации новых экземпляров tk.Variable. Однако к тому времени, когда код попытался get() получить значения из tk.Variable (например, для отображения кнопки-галочки в «selected» или «!selected» state), tk.Variable уже не был «установлен» в Tcl и виджет вел себя хаотично, поскольку tk.Variable не был доступен через tk методы setvar() или getvar().

Предложенное решение:

Кто-то рекомендовал мне отслеживать имена tk.Variable на корневом уровне, например, в dict, которому будут передаваться имена переменных. Затем повторно используйте переменные по ходу дела вместо того, чтобы создавать новые переменные для каждого виджета. (Примечание: я быстро обнаружил, что в этом tk.Variable нужно будет отслеживать экземпляры dict, а не только name переменной!)

Однако, прежде чем двигаться дальше, есть ли что-то еще, что мне не хватает, что приводит к беспорядочному поведению экземпляров tk.Variable?

Например, поскольку я храню всю информацию о виджете, включая указатель на виджет, в dict, который инициализируется при каждом вызове main() в каждом модуле приложения. Возможно, эти dict не удаляются должным образом сборщиком мусора, когда я запускаю main() нового приложения? Должен ли я распространять словари вверх и обновлять dict на корневом уровне, а не инициализировать новый dict внутри main() каждого приложения?

Первоначальное исправление:

Моим первоначальным решением было удалить все name, которые я назначил всем моим экземплярам tk.Variable. Это позволило tkinter инициализировать новую переменную при каждой инициализации виджета («PYVAR_1», «PYVAR_2» и т. д.).

Однако когда я добавил в приложение оператор print для отслеживания количества переменных Tcl, их число быстро увеличивалось с каждым изменением/обновлением приложения. Количество переменных не уменьшается ни в какой момент, жду ли я несколько секунд, а затем меняю/обновляю приложение или принудительно window_frame.update() после уничтожения дочерних виджетов.

Пример модуля приложения:

<-SNIP->

# App window definition:
def main(window_frame, **kw):
    print(root.tk.call("info", "vars")) # This number always increases on main() call
    destroy_children(window_frame)
    # Add new widgets here

<-SNIP->

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

После длительного расследования я определил, что проблема заключалась в устаревших указателях в Python, которые не позволяли Tcl удалить tk.Variable.

Решение заключалось в следующем:

  1. Распространите все дочерние определения с отдельных уровней приложения dict на корневой уровень dict.
  2. В каждом мастер-ключе корня dict храните список кортежей (child_key, child_widget).
  3. Рефакторинг destroy_children для использования дочернего виджета главных ключей tuples, чтобы pop() переменные из виджета __dict__ и del явные переменные.

Рефакторинг фрагментов кода (не включая неизмененные методы):

<-SNIP->

# In the widgets module:
def destroy_widgets(master_dict, master):
    # 'master' is now a key in 'master_dict' rather than a widget!
    # Use (child, child_widget) tuples instead of master.winfo_children()
    # to cycle through the children of master:
    for child, widget in master_dict[master].children:
        # Pop the child key from the master_dict:
        master_dict.pop(child)

        # Remove the Variable pointer from the Widget:
        variable = widget.__dict__.pop('variable')

        # Destroy the widget:
        widget.destroy()

        # Delete the Variable:
        del variable

    # Clear the master's children:
    master_dict[master].children = [] 

    # At this point, no pointers to widgets or Variables exist in Python,
    # so Tcl can "unset" the named Variables without issue

# In each app module:
def main(window_dict): # Note change to window_dict from window_frame
    # The number of Tcl variables number no longer increases on main() call:
    print(root.tk.call("info", "vars"))

    # Destroy the widgets in the window_frame:
    destroy_widgets(window_dict, 'window_frame')

    # Add new widgets to the window_frame:
    master = window_dict['window_frame']['widget']
    widgets = dict(
        child1 = dict(widget = CustomWidget(name='child1', master=master)),
        child2 = dict(widget = CustomWidget(name='child2', master=master)),
        # etc
        )
    # Propagate upwards:
    window_dict.update(**widgets)

    # Add the children to the window_frame key:
    if window_dict['window_frame'].get('children') is None:
        window_dict['window_frame']['children'] = []    
    for key in widgets.keys():
        # Change: Don't need to keep track of the variable in the dict:
        # window_dict[key]['variable'] = window_dict[key]['widget'].variable

        # Add the (child, child_widget) tuples to the master key:
        window_dict['window_frame']['children'].append(
            (key, window_dict[key]['widget']))

<-SNIP->

На этом считаю вопрос закрытым. Переменные корректно «сбрасываются» в Tcl, что позволяет повторно использовать одни и те же имена переменных без неустойчивого поведения, наблюдавшегося раньше.

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