Найти - Пользователи
Полная версия: tkinter + wait_variable = приехали
Начало » Python для экспертов » tkinter + wait_variable = приехали
1 2
vanvanov
Я сам не эксперт, но вопрос, который задам, вероятно, требует отличных знаний tkinter, поэтому публикую в форуме экспертов. Есть GUI, написанный на tkinter (проверял нижеприведенный код на Python 3.4 и tk 8.5 и 8.6). Задача - многократное использование Toplevel и сидящих на нем виджетов. Образец:
 #!/usr/bin/python3
import tkinter as tk
class Top:
	
	def __init__(self):
		self.widget = tk.Toplevel(h_widgets.root())
		self.widget.title('Today is the day')
		self.widget.protocol("WM_DELETE_WINDOW",self.close)
		self.tk_trigger = tk.BooleanVar()
		
	def close(self,*args):
		self.widget.withdraw()
		self.tk_trigger.set(True)
	
	def show(self):
		self.widget.deiconify()
		self.tk_trigger = tk.BooleanVar()
		self.widget.wait_variable(self.tk_trigger)
class Widgets:
	
	def __init__(self):
		self._root = self._top = self._textbox = None
		
	def root(self):
		if not self._root:
			self._root = tk.Tk()
			self._root.withdraw()
		return self._root
		
	def start(self):
		self.root()
		self._root.withdraw()
		
	def end(self):
		self.root().destroy()
		self._root.mainloop()
		
	def top(self):
		if not self._top:
			self._top = Top()
		return self._top
h_widgets = Widgets()
if __name__ == '__main__':
	
	h_widgets.start()
	h_widgets.top().show()
	h_widgets.end()

Этот код срабатывает так, как задумывалось, но до тех пор, пока я не буду использовать его через другой модуль:
 #!/usr/bin/python3
from sample1 import Widgets
h_widgets = Widgets()
h_widgets.start()
h_widgets.top().show()
h_widgets.end()

В этом случае код зависает на этапе wait_variable. Я знаю, что проблему можно решить, заменив wait_variable;withdraw на wait_window;destroy, но я хочу сделать многократно используемый код, без повторной паковки. Я нашел описание бага, но не нашел решения или вариантов обхода проблемы. Пытался подсунуть wait_variable в текущий модуль:
 class TopTrigger:
	
	def add(self,obj):
		self.obj = obj
		self.obj.tk_trigger = tk.BooleanVar()
	
	def on_close(self,*args):
		self.obj.tk_trigger.set(True)
	
	def on_show(self):
		self.obj.tk_trigger = tk.BooleanVar()
		self.obj.widget.wait_variable(self.obj.tk_trigger)
но это не помогло. Кто-нибудь сталкивался? Как можно обойти?
4kpt_IV
Адище…
1. Зачем Вам wait_variable?
2. Вообще не понятно зачем все это. Вы хотите использовать top в другом модуле?
vanvanov
4kpt_IV
Адище…
Что именно не так?
4kpt_IV
Зачем Вам wait_variable
Я делаю диалоговое окно, в котором пользователь должен нажать на кнопку. Чтобы дождаться этого, есть методы wait_variable или wait_window (по крайней мере, я знаю только эти). Второй требует destroy. Но я не хочу уничтожать виджет и потом пересоздавать его, а хочу повторно его использовать (например, с помощью withdraw и deiconify). Таким образом, остается первый.
4kpt_IV
Вы хотите использовать top в другом модуле
А что тут такого? Я написал модуль-обертку для некоторых стандартных виджетов tkinter. Я могу, например, по умолчанию центрировать их по экрану, делать на них фокус и т.п. Большинство из классов требуют Toplevel.
4kpt_IV
vanvanov
Я делаю диалоговое окно, в котором пользователь должен нажать на кнопку.
Я не сказал, что это плохо, я просто задал вопрос. Т.е. Вы хотите чтобы появлялось диалоговое окно в котором пользователь должен нажать на кнопку? А зачем Вы это событие ждете?
vanvanov
4kpt_IV
А как иначе? Посмотрите на этот код:
 #!/usr/bin/python3
import tkinter as tk
root = tk.Tk()
root.withdraw()
top = tk.Toplevel(root)
txt = tk.Text(top)
txt.insert('1.0','Готово!')
txt.pack()
txt.focus_set()
top.wait_window()
root.destroy()
root.mainloop()

Если закомментировать wait_window, пользователь ничего не увидит, и программа завершится.
4kpt_IV
А почему после нажатия кнопки на toplevel нельзя сделать root.destroy() раз Вы ждете от пользователя этого?
vanvanov
4kpt_IV
Я не жду от пользователя root.destroy(). Он делается предпоследним перед mainloop, чтобы пользователю не пришлось вручную уничтожать рутовое окно (оно все время скрыто, а показывается Toplevel).

Вот, посмотрите, например:
 #!/usr/bin/python3
import tkinter as tk
class Root:
	
	def __init__(self):
		self.widget = tk.Tk()
	
	def kill(self):
		self.widget.destroy()
	
	def run(self):
		self.widget.mainloop()
		
	def close(self):
		self.widget.withdraw()
		
	def show(self):
		self.widget.deiconify()
class Top:
	
	def __init__(self,parent_obj):
		self.parent_obj = parent_obj
		self.widget = tk.Toplevel(self.parent_obj.widget)
		self.widget.protocol("WM_DELETE_WINDOW",self.close)
		self.tk_trigger = tk.BooleanVar()
		# Здесь мы по умолчанию делаем центровку, ну и другие вкусные вещи
		self.center()
		
	def title(self,arg=None):
		if arg:
			self.widget.title(arg)
			
	def center(self,*args):
		pass
		
	def show(self,*args):
		self.widget.deiconify()
		self.widget.wait_variable(self.tk_trigger)
		
	def close(self,*args):
		self.widget.withdraw()
		self.tk_trigger.set(True)
class TextBox:
	
	def __init__(self,parent_obj):
		self.parent_obj = parent_obj
		self.widget = tk.Text(self.parent_obj.widget)
		self.widget.pack()
		self.focus()
		
	def clear_text(self,*args):
		self.widget.delete('1.0',tk.END)
		
	def insert(self,arg):
		if arg:
			self.widget.insert('1.0',arg)
			
	def update(self,arg):
		self.clear_text()
		self.insert(arg)
	
	def title(self,arg=None,*args):
		self.parent_obj.title(arg)
	
	def focus(self,*args):
		self.widget.focus_set()
		
	def show(self,*args):
		self.parent_obj.show()
		
	def close(self,*args):
		self.parent_obj.close()
		
		
		
class Widgets:
	
	def __init__(self):
		self._root = self._txt = None
		
	def root(self):
		if not self._root:
			self._root = Root()
		return self._root
		
	def start(self):
		self.root()
		self._root.close()
		
	def end(self):
		self.root().kill()
		self._root.run()
		
	def txt(self):
		if not self._txt:
			top = Top(parent_obj=self.root())
			self._txt = TextBox(parent_obj=top)
		return self._txt
		
		
widgets = Widgets()
widgets.start()
txt = widgets.txt()
txt.title('1')
txt.update('Здесь был Вася.')
txt.show()
txt.title('2')
txt.update('А потом сюда пришел Петя.')
txt.show()
widgets.end()
Такой подход позволяет не убивать текстовый виджет, а использовать его повторно. Кстати, конкретно в данном случае wait_variable почему-то не виснет при импорте из другого модуля. Правда, виджеты случайным образом по непонятной причине теряют фокус.
4kpt_IV
А почему нельзя просто скрыть текстовый виджет?

vanvanov
Я не жду от пользователя root.destroy(). Он делается предпоследним перед mainloop, чтобы пользователю не пришлось вручную уничтожать рутовое окно (оно все время скрыто, а показывается Toplevel).

Этого вообще не понял. Пользователю не придется делать это вручную. Вы в toplevel передаете root. Вот и сделайте ему внутри какого-нибудь обработчика toplevel метод destroy().

Виджеты не теряют. Фокус переходит на виджеты, которых Вы не видите, потому что они где-то или ушли из видимой области (перекрыты другими) или просто не отображены. Попробуйте проанализировать какие виджеты получают фокус. Для этого можно создать свой базовый класс от которого будут наследоваться все виджеты и который будет автоматом к наследникам лепить нужный перехватчик событий и печататься путь виджета при получении фокуса. Потому как уход фокуса “в никуда” очень настораживает. Где-то что-то Вы не видите…

P.S. Я Ваш код уже видел. В первой фразе записал, что это Адище
vanvanov
4kpt_IV
А почему нельзя просто скрыть текстовый виджет?
Потому что TextBox - это не примитив. Там могут быть кнопки, фреймы, скроллбары, еще много всякой всячины. Если я сделаю pack_forget на фрейме, то Toplevel будет висеть впереди. Поскольку я делаю такие виджеты-конструкторы на основе Toplevel, то и скрывать надо Toplevel, а, как я уже говорил, я знаю только 2 метода для этого, причем один из них не подходит.
4kpt_IV
Вот и сделайте ему внутри какого-нибудь обработчика toplevel метод destroy().
Зачем? Я же говорю, мне надо не разрушать виджет, а скрывать его (то бишь вместе с привязанным к нему Toplevel).
4kpt_IV
Потому как уход фокуса “в никуда” очень настораживает.
У меня иногда бывает так, что фокус теряется (скорее всего, на скрытый root переходит) даже при перезапуске программы. Т.е. при 1-м запуске фокус есть, а при 2-м его может и не быть. Я грешу на DM.
4kpt_IV
В первой фразе записал, что это Адище
Вы написали, но никак не обосновали. Я бы с удовольствием научился бы писать *правильный* код, но для этого нужны комментарии по существу.
4kpt_V
По существу…
Ну ок. Зачем переименовывать существующие методы? Поясните смысловую нагрузку этого?

 #
class Root:
	def __init__(self):
		self.type = 'Root'
		self.widget = tk.Tk()
	def run(self):
		self.widget.mainloop()
	def show(self):
		self.widget.deiconify()
	def close(self):
		self.widget.withdraw()
	def destroy(self):
		self.kill()
	
	def kill(self):
		self.widget.destroy()
		
	def update(self):
		self.widget.update()

Вас эта часть кода не смущает?

 	if obj_type_str == 'str':
		obj_type_str = globs['mes'].type_str
	elif obj_type_str == 'list':
		obj_type_str = globs['mes'].type_lst
	elif obj_type_str == 'dict':
		obj_type_str = globs['mes'].type_dic
	elif obj_type_str == 'tuple':
		obj_type_str = globs['mes'].type_tuple
	elif obj_type_str == 'set' or obj_type_str == 'frozenset':
		obj_type_str = globs['mes'].type_set
	elif obj_type_str == 'int':
		obj_type_str = globs['mes'].type_int
	elif obj_type_str == 'long':
		obj_type = globs['mes'].type_long_int
	elif obj_type_str == 'float':
		obj_type_str = globs['mes'].type_float
	elif obj_type_str == 'complex':
		obj_type_str = globs['mes'].type_complex
	elif obj_type_str == 'bool':
		obj_type_str = globs['mes'].type_bool

И это только вершина айсберга.

Вообще не пойму что Вы хотите построить, если честно. Какая-то дивная архитектура. Вы хотите построить приложение на toplevel'ах. При этом Вы не хотите их destro'ить, а хотите… Короче. Реально не понятно. Давайте, может, в картинках. Потому как если я не пойму, с моей любовью докапываться до истины, то даю 100 из 100, что тут не поймет никто
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB