В настоящее время у меня проблема с инициализацией и именованием 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, которые не позволяли Tcl удалить tk.Variable.
Решение заключалось в следующем:
dict на корневой уровень dict.dict храните список кортежей (child_key, child_widget).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, что позволяет повторно использовать одни и те же имена переменных без неустойчивого поведения, наблюдавшегося раньше.