Tkinter как скрыть окно
На этом шаге мы рассмотрим процесс создания таких окон .
Модальное вторичное окно , в отличие от обычного, при открытии блокирует все остальные окна приложения, в результате чего пользователь теряет доступ к ним. Остальные окна становятся доступными только после закрытия модального окна. Такие окна обычно применяются в качестве диалоговых окон для запроса каких-либо данных, необходимых для дальнейшей работы приложения.
К сожалению, библиотека Tkinter не предоставляет никаких инструментов для вывода модальных окон. Однако мы можем превратить обычное вторичное окно в модальное, вызвав у его контейнера или у него самого метод grab_set() (см. 11 шаг). Этот метод задает для контейнера или окна режим перехвата событий, в результате чего остальные окна перестают реагировать на действия пользователя. Как только окно, для которого был установлен перехват событий, закрывается и удаляется из памяти, перехват событий перестает работать, и остальные окна приложения становятся доступными для пользователя.
Есть еще один неприятный момент, связанный с реализацией модальных окон. Если после запуска приложения и вывода модального вторичного окна мы посмотрим на панель задач Windows , то увидим, что там присутствуют оба окна: и главное, и вторичное. Но присутствие модального окна на панели задач говорит о плохом стиле программирования. Чтобы скрыть вторичное окно, следует вызвать у него метод transient() (см. 21 шаг), передав ему ссылку на главное окно. Этот метод, в частности, отменит представление вторичного окна на панели задач.
Вот фрагмент кода контейнера, который превратит окно, в котором выведен, в модальное:
class Secondary(tkinter.ttk.Frame): def __init__(self, master=None): . . . . . self.master.transient(parent) self.grab_set()
В качестве примера давайте модифицируем приложение из предыдущего шага таким образом, чтобы выводимое им вторичное окно стало модальным. Текст ниже показывает исправленный код класса контейнера вторичного окна.
import tkinter import tkinter.ttk # Объявляем класс контейнера для вторичного окна class Secondary(tkinter.ttk.Frame): # Конструктор этого класса поддерживает дополнительный параметр # parent, с которым передается ссылка на главное окно. Она # понадобится нам, чтобы вывести занесенное значение в главном окне def __init__(self, master=None, parent=None): super().__init__(master) # Сохраним ссылку на главное окно в атрибуте self.parent = parent self.pack() self.create_widgets() self.master.title("Вторичное окно") self.master.transient(parent) self.grab_set() def create_widgets(self): self.varValue = tkinter.StringVar() self.varValue.set("Значение") entValue = tkinter.ttk.Entry(self, textvariable=self.varValue) entValue.pack() btnOK = tkinter.ttk.Button(self, text="OK", command=self.ok) btnOK.pack(side="left") btnCancel = tkinter.ttk.Button(self, text="Отмена", command=self.master.destroy) btnCancel.pack(side="right") def ok(self): self.parent.lblValue["text"] = self.varValue.get() self.master.destroy() def show_value(self): self.parent.lblValue["text"] = self.varValue.get() class Application(tkinter.ttk.Frame): def __init__(self, master=None): super().__init__(master) self.pack() self.create_widgets() self.master.title("Модальное вторичное окно") self.master.resizable(False, False) def create_widgets(self): btnShowWindow = tkinter.ttk.Button(self, text="Вывести окно", command=self.show_window) btnShowWindow.pack() # Опция width компонента Label задает ширину надписи # в символах текста self.lblValue = tkinter.ttk.Label(self, text="", width=50) self.lblValue.pack() def show_window(self): # Выводим вторичное окно, не забыв указать в параметре parent # конструктора ссыпку на главное окно Secondary(master=tkinter.Toplevel(), parent=self) root = tkinter.Tk() app = Application(master=root) root.mainloop()
Архив с файлом можно взять здесь.
Результат работы приложения приведен на рисунке 1.
Рис.1. Результат работы приложения
На следующем шаге мы рассмотрим управление жизненным циклом приложения .
Спрятать окно tkinter
Проблема следующая. Пользуюсь модулем messagebox, который, помимо окна диалога, создает пустое окно root. Чтобы спрятать это окно, на форумах советуют использовать root.withdraw(). Однако мой скрипт создает много разнообразных окон tkinter, и после того, как я что-нибудь нажал в диалоге messagebox, tkinter не может создать новое окно (терминал просто висит) — видимо, ждет, когда я закрою пустое окно root (оно, напомню, спрятано, т.е. мне остается только Ctrl+C). root_destroy() в конце таких процедур как ниже не помогает. Если же withdraw убрать, tkinter начинает плодить сущности: 1, 2 — некрасиво. Как можно решить этот вопрос?
from tkinter import * from tkinter import filedialog from tkinter import messagebox # Диалог "Вы уверены. " def ok_cancel(message): root=Tk() # Иначе будет показываться лишнее пустое окно #root.withdraw() if messagebox.askokcancel('Подтвердите:',message): return True; else: return False #root.destroy()
Deleted
01.04.13 06:05:21 MSK

Никак. В Tk обязательно есть root-окно. Цикл wm (основной цикл для Windows Manager’a Tk) обязательно должен содержать рутовое окно (иначе просто не к чему привязать цикл). Ну, и для Tk использование диалоговых окон вне цикла — просто не возможно. Дело в том, что исходя из архитектуры Tk, элемент message box — это просто дочернее окно (следовательно, оно должно быть привязано к root’овому), у которого заранее заданы определенные параметры.
Я такую проблему обходил просто — сделал класс, который описывал нужный мне диалог и создавал/убивал окно по мере надобности (каждый раз это было просто заново создаваемое root’овое окно). Для Tk это более правильный подход, нежели скрывать корневое окно, а потом плодить мессадж-боксы.
silver-bullet-bfg ★★
( 01.04.13 08:05:07 MSK )

Второй путь: забить на Tk и юзать Gtk 🙂
Novator ★★★★★
( 01.04.13 10:34:18 MSK )
Ответ на: комментарий от Novator 01.04.13 10:34:18 MSK

А смысл? Для маленьких скриптов — tk самое оно. Тем более, что Tkinter — гвидоугодный.
silver-bullet-bfg ★★
( 01.04.13 13:03:24 MSK )
Ответ на: комментарий от silver-bullet-bfg 01.04.13 08:05:07 MSK
Пример класса можете привести?
Deleted
( 01.04.13 13:28:31 MSK )
Ответ на: комментарий от silver-bullet-bfg 01.04.13 08:05:07 MSK
Для Tk это более правильный подход, нежели скрывать корневое окно, а потом плодить мессадж-боксы.
Т.е. предлагаете написать свой messagebox?
Deleted
( 01.04.13 13:30:22 MSK )

не знаю как оно на питоне, но в tcl так:
wm withdraw .
Bad_ptr ★★★★★
( 01.04.13 13:32:36 MSK )

Bad_ptr ★★★★★
( 01.04.13 13:34:29 MSK )
Ответ на: комментарий от silver-bullet-bfg 01.04.13 13:03:24 MSK

А смысл? Для маленьких скриптов — tk самое оно.
Gtk в любом линуксе есть всегда. А вот tk не всегда.
В виндовс Gtk (без туфты) занимает 14Мб в распакованном виде. В архиве того меньше. Сколько tk в винде занимает — не знаю.
А смысл в том, что Gtk выглядит поприятнее, более популярно и лучше документировано. Еще в том, что описанной проблемы можно избежать без выкрутасов.
Ни в коем случае не уговариваю. Решать топикстартеру.
Novator ★★★★★
( 01.04.13 13:59:36 MSK )
Ответ на: комментарий от Novator 01.04.13 13:59:36 MSK
Согласен, что GTK-диалоги приятнее, но очень обломно изучать Tkinter ради простого скрипта, и в итоге из-за маленькой проблемы разучивать что-то другое.
Deleted
( 01.04.13 14:38:04 MSK )
Ответ на: комментарий от Deleted 01.04.13 13:30:22 MSK

Ну да, что тут сложного? Сделаете за 3 минуты. Python очень плохо помню, но что-то вроде:
class WmDialog def __init__(self): self.root=Tk() self.bntOk=ttk.Button (root, text="Hello, world", ).grid() def cmdOk(self): return "ok" def liveWnd (self,isLive): self.root.mainloop() def deadWnd (self): self.root.destroy()
silver-bullet-bfg ★★
( 01.04.13 14:53:51 MSK )
Ответ на: комментарий от silver-bullet-bfg 01.04.13 14:53:51 MSK

* self.bntOk=ttk.Button (root, text=«Hello, world», command=self.cmdOk).grid()
silver-bullet-bfg ★★
( 01.04.13 14:55:04 MSK )
Ответ на: комментарий от Deleted 01.04.13 14:38:04 MSK

Tkinter гвидоугоден зато ^_^. На нем можно не только для скриптов гуй рисовать.
silver-bullet-bfg ★★
( 01.04.13 14:55:51 MSK )
Ответ на: комментарий от silver-bullet-bfg 01.04.13 14:55:04 MSK
Я Питон недавно начал изучать. Можете объяснить, как применить этот класс, чтобы не было пустых окон?
Deleted
( 02.04.13 00:40:42 MSK )
Ответ на: комментарий от Novator 01.04.13 10:34:18 MSK
А есть какой-нибудь простой конструктор типа zenity, вместо того, чтобы кучу непонятных функций писать?
Deleted
( 02.04.13 00:53:31 MSK )
Ответ на: комментарий от Deleted 02.04.13 00:40:42 MSK

Там все просто, вот смотри какая идея:
from Tkinter import * class WmDialog: def __init__(self): self.root=Tk() self.bntOk=Button (self.root, text="Kill Dialog",command=self.root.destroy).grid() self.bntCancel=Button (self.root, text="Exit from Script", command=exit).grid() def liveWnd (self): self.root.mainloop() dlg=WmDialog() dlg.liveWnd() print "I am destroy 1-st window" dlg=WmDialog() dlg.liveWnd() print "I am destroy 2-st window" print "Exit"
Что тут происходит: ты создаешь класс с именем WmDialog (как раз аналог диалог-бокса, я его настраивать не стал — сам сделаешь, тут не сложно), который позволяет либо продолжить выполнение скрипта дальше (если нажмешь bntOk), либо вырубит скрипт (если нажмешь btnCancel). Когда ты нажимаешь bntOk вызывается метод destroy для окна dlg.root, которое и является корневым для цикла Tk(). После вывода первой надписи («I am destroy 1-st window»), мы снова создаем объект-окно. Ну и так далее :). Дальше сам придумаешь, я думаю.
silver-bullet-bfg ★★
( 02.04.13 09:26:40 MSK )
Ответ на: комментарий от silver-bullet-bfg 01.04.13 14:53:51 MSK

Какими местом для опроса диалога Вы хотите присобачить класс О_О?
from Tkinter import * from tkMessageBox import * def dialog(f, *L, **D): root=Tk(); root.withdraw() try: return f(*L, **D) finally: root.destroy() print dialog( askokcancel, '111', '222') print dialog( askokcancel, '333', '444')
dialog подходит для вызова любого dialog-а. Для питона3 ТС думаю сможет адаптировать сам.
ЗЫ: ТС destroy вызывал ПОСЛЕ return, ясно что рут не закрывался;-) Ну и конструкции вида
if True: return True else: return False
доставляют;-)
AIv ★★★★★
( 02.04.13 09:27:11 MSK )
Ответ на: комментарий от Novator 01.04.13 13:59:36 MSK

В винде Tk будет ставится вместе в Python (а вот Gtk придется отдельно доставлять), да и лучше документации чем по Tk в принципе не видел. Просто надо смотреть не только Tkinter доки, но и Tk чистого доки.
silver-bullet-bfg ★★
( 02.04.13 09:30:47 MSK )
Ответ на: комментарий от AIv 02.04.13 09:27:11 MSK

Ваше решение красивее:) Эх. Привык я уже все делать через ООП-извращения 🙁
silver-bullet-bfg ★★
( 02.04.13 09:33:26 MSK )
Ответ на: комментарий от silver-bullet-bfg 02.04.13 09:26:40 MSK

И даже если делать диалог ручками — накой там класс то? По дизайну это ф-я, вызвал — она все нарисовала — висит ждет решения юзера — закрылась и вернула результат. Классы тут актуальны либо когда есть несколько вызовов для работы с одними и теми же данными, либо когда нужно сделать какой то полуфабрикат (сложный виджет) для повторного испольщования.
AIv ★★★★★
( 02.04.13 09:34:09 MSK )
Ответ на: комментарий от silver-bullet-bfg 02.04.13 09:33:26 MSK

Дык переходите на ФП! Даже для гуйни оно иногда бывает весьма полезно;-)
AIv ★★★★★
( 02.04.13 09:35:02 MSK )
Ответ на: комментарий от AIv 02.04.13 09:35:02 MSK

Вот и хочу на ФП перейти, только пока не получается. А вы случайно не подскажите, какой ФП язык наиболее хорошо работает с гуйней? Сейчас как раз пишу «домашний» проект на C#+Gtk, но честно — уже воротит от этой дряни (C#/ООП).
silver-bullet-bfg ★★
( 02.04.13 10:11:52 MSK )
Ответ на: комментарий от AIv 02.04.13 09:34:09 MSK

Ну по привычке я бы загнал вообще весь скрипт в объект, сделал бы не только диалоги как классы, но и саму программу как класс, который работает с объектами других классов..
silver-bullet-bfg ★★
( 02.04.13 10:13:17 MSK )
Ответ на: комментарий от silver-bullet-bfg 02.04.13 10:11:52 MSK

Ну я пишу на питоне, ФП там есть, и в гуйне оно вполне актуально
Как скрыть виджеты после их нажатия и выполнения действия?
На главном окне у меня будут: кнопка, поля ввода и несколько label. Как сделать так, чтобы после нажатия кнопки она исчезала(скрывалась), а затем появлялись остальные объекты.
То есть как авторизация: вводим логин, пароль, нажимаем «войти», всё скрывается и появляются другие компоненты, то есть будто вход выполнен.
Искал в интернете, но нашел только скрыть кнопку по нажатию или скрыть/показать label по нажатию на кнопку.
Можно ли, когда нажму на кнопку, сначала выполниться действие(показ всех остальных элементов), а потом все компоненты авторизации исчезнут?(или наоборот, сначала все исчезает, а потом появляются компоненты). То есть можно ли как-то скрыть все компоненты по 1 нажатию кнопки и как это реализовать или есть какой-то другой способ?
Функция hide_me скрывает кнопку через bind(), если кнопка не привязана к координатам, а так ничего не происходит.
Попробовал через lambda сделать, чтобы выполнить 2 функции сразу, но появляется ошибка.
btn=Button(text=»Авторизация», command=lambda:(AUTO(),hide_me()))
TypeError: hide_me() missing 1 required positional argument: ‘event’
from tkinter import * def hide_me(event): event.widget.pack_forget() root = Tk() root.title("Авторизация") root.geometry("500x250") root.resizable(False, False) l = "admin" p = "password" LOGIN = "Введите логин:" LOGIN = Label(text=LOGIN, justify=LEFT) LOGIN.place(x=0, y=0) lineLOGIN = Entry() lineLOGIN.pack() lineLOGIN.place(x=80, y=30, anchor="c") PASS = "Введите пароль:" PASS = Label(text=PASS, justify=LEFT) PASS.place(x=0, y=60) linePASS = Entry() linePASS.pack() linePASS.place(x=80, y=90, anchor="c") def AUTO(): if lineLOGIN.get() == l and linePASS.get() == p: linePA = Entry() linePA.pack() linePA.place(x=200, y=70, anchor="c") else: print("Неверный логин/пароль") btn=Button(text="Авторизация", command=lambda:(AUTO(),hide_me())) #btn.bind('', hide_me) btn.pack() btn.place(x=60, y=120, anchor="c") root.mainloop()
- Вопрос задан более двух лет назад
- 650 просмотров
Python-сообщество
![]()
- Начало
- » GUI
- » Скрыть окно Tkinter
#1 Апрель 1, 2013 13:39:03
vanvanov Зарегистрирован: 2013-03-31 Сообщения: 252 Репутация: 4 Профиль Отправить e-mail
Скрыть окно Tkinter
Использую messagebox, который, помимо диалога, создает пустое окно root
. Для скрытия этого окна на форумах предлагается использовать root.withdraw(), но после этого я не всегда могу снова использовать окна Tkinter, потому что он ждет, пока я закрою скрытое окно root. Если же не скрывать пустые окна, они плодятся
. Могу ли я как-нибудь по-другому избавиться от этого окна или полностью закрыть все окна Tkinter перед его повторным использованием?
#2 Апрель 1, 2013 20:38:55
Griffon От: Ukrain, Zaporozhie Зарегистрирован: 2009-03-04 Сообщения: 324 Репутация: 11 Профиль Отправить e-mail
Скрыть окно Tkinter
Возможно вы не хотите закрывать главное окно и потом заново его создавать. Возможно вы просто хотите скрывать и вновь отображать одно и то же главное окно, а что на нем отображается можно менять как вам угодно.
Если же вы действительно хотите то о чем просите, то вам просто необходимо использовать метод destroy экземпляра Tk (root в вашем случае). Но мне кажется что вы ставите задачу не совсем правильно.
#3 Апрель 2, 2013 00:11:09
4kpt От: Харьков Зарегистрирован: 2010-11-03 Сообщения: 998 Репутация: 63 Профиль Отправить e-mail
Скрыть окно Tkinter
Griffon Вы абсолютно правы. Вопрос поставлен неккоректно.
Вопрос должен быть следующим:
“Каким образом удалить корневое окно, вывод которого скрыт методом .withdraw()?”
В результате имеет то, что окна размножаются при каждом вызове и естественно, обращение к ним блокируется.
Ну так удаляйте их. Кто за Вас это будет делать? Пример кода:
import Tkinter import tkMessageBox root = Tkinter.Tk() root.withdraw() mes = tkMessageBox.askyesno(message = "first question") print mes root.destroy() root = Tkinter.Tk() root.withdraw() mes2 = tkMessageBox.askyesno(message = "second question") print mes2 root.destroy() root.mainloop()
P.S. Подход, построенный на удалении корневых окон изначально ущербен. Легче удалять не окна (а потом их заново создавать), а рамки в которые покуются все виджеты данного сеанса или страницы программы (как Вам будет удобно). Таким образом, корневое окно не будет мучаться, а виджеты будут скрываться. Кстати, есть еще альтернативный механизм — метод _forget() который скрывает рассположение рамки с упакованными виджетами. Если снова понадобиться, можно заново ее разместить 🙂
P.S.S. Надеюсь это оно…
Отредактировано 4kpt (Апрель 2, 2013 01:09:16)
#4 Апрель 2, 2013 00:59:50
vanvanov Зарегистрирован: 2013-03-31 Сообщения: 252 Репутация: 4 Профиль Отправить e-mail
Скрыть окно Tkinter
4kpt
Ну так удаляйте их. Кто за Вас это будет делать?
Дело в следующем. У меня есть следующий код.
def ok_cancel(message): root=Tk() # Иначе будет показываться лишнее пустое окно root.withdraw() if messagebox.askokcancel('Подтвердите:',message): return True else: return False root.mainloop() root.destroy() # Вернуть отредактированный текст def text_field_edit(title,array): root, res = Tk(), [None] def callback(): res[0] = txt.get(1.0,END); root.destroy() root.title(title) scrollbar = Scrollbar(root,jump=0) txt = Text(root,font="14",wrap=WORD,yscrollcommand=scrollbar.set) txt.insert(END,array) # Позволяет использовать мышь для управления скроллбаром scrollbar.config(command=txt.yview) scrollbar.pack(side=RIGHT,fill=Y) txt.pack() # Выход по клику кнопки ok = Button(root, text='Запомнить и выйти',command=callback) ok.pack() # Выход по нажатию Enter и Пробел на кнопке (навигация по Shift+Tab) ok.bind('', lambda e:callback()) txt.focus_set() root.mainloop() if str(res[0])=='None' or str(res[0])==default_line_break: my_error(empty_field_error_msg) return res[0]
Я попеременно выполняю то ok_cancel, то text_field_edit. ok_cancel завершается нормально, но после text_field_edit остается пустое окно (в случае root.iconify()), а в случае withdraw скрипт перестает что-то делать. Где ошибка, я же делаю root.destroy() в text_field_edit тоже?
Отредактировано vanvanov (Апрель 2, 2013 01:04:14)
#5 Апрель 2, 2013 01:10:28
4kpt От: Харьков Зарегистрирован: 2010-11-03 Сообщения: 998 Репутация: 63 Профиль Отправить e-mail
Скрыть окно Tkinter
Ответьте на один вопрос.
Зачем нужна эта строчка?
root.mainloop()
#6 Апрель 2, 2013 01:40:02
vanvanov Зарегистрирован: 2013-03-31 Сообщения: 252 Репутация: 4 Профиль Отправить e-mail
Скрыть окно Tkinter
4kpt
Ответьте на один вопрос.Зачем нужна эта строчка?
Показывает окно?
#7 Апрель 2, 2013 02:03:23
4kpt От: Харьков Зарегистрирован: 2010-11-03 Сообщения: 998 Репутация: 63 Профиль Отправить e-mail
Скрыть окно Tkinter
Неверно…
Разберитесь…
По Вашему коду. Поменяйте местами эти две строки:
root.mainloop() root.destroy()
P.S. Я настоятельно не рекомендую использовать инструкцию from tkinter import*
Отредактировано 4kpt (Апрель 2, 2013 02:07:31)
#8 Апрель 2, 2013 03:25:19
vanvanov Зарегистрирован: 2013-03-31 Сообщения: 252 Репутация: 4 Профиль Отправить e-mail
Скрыть окно Tkinter
4kpt
Неверно…Разберитесь…По Вашему коду. Поменяйте местами эти две строки:
Поменял. Осталось как раньше — окна messagebox нормально убиваются, а после text_field_edit остается пустое root-окно, хотя если использовать только text_field_edit, все завершается нормально. Куда в этой функции (text_field_edit) я могу поместить mainloop? Если до кнопок — кнопки не отображаются, если последней строкой — скрипт не сможет вернуть тот текст, который пользователь вводит в окно. Вырубаю в терминале
File "/usr/local/bin/main.py", line 785, in text_field_edit
root.mainloop()
File "/usr/lib/python3.1/tkinter/__init__.py", line 1012, in mainloop
self.tk.mainloop(n)
KeyboardInterrupt
Похоже да, какие-то проблемы именно с mainloop. Но где тогда он должен стоять? Разве где-то в другом месте?
Отвечая на ваш вопрос, mainloop — метод, запускающий цикл обработки событий.
Отредактировано vanvanov (Апрель 2, 2013 03:26:15)
#9 Апрель 2, 2013 09:39:30
Griffon От: Ukrain, Zaporozhie Зарегистрирован: 2009-03-04 Сообщения: 324 Репутация: 11 Профиль Отправить e-mail
Скрыть окно Tkinter
Подумайте об архитектуре вашего приложения.
Нет смысла десять раз создавать обьект Tk.
Разберитесь вот с этим:
# -*- coding: UTF-8 -*- from Tkinter import * import tkMessageBox as messagebox from tkFont import Font root = Tk() root.withdraw() yesno = messagebox.askokcancel("OMG", "Не нажмайте ОК!") my_frame = Frame(root) my_frame.pack() my_label = Label(my_frame, text="Мы используем все то же главное окно!") my_label.pack() root.update_idletasks() root.deiconify() yesno = messagebox.askokcancel("OMG", "Удалить окно?") root.withdraw() my_frame.forget() yesno = messagebox.askokcancel("OMG", "Новое окно?") my_label.configure(text="Все тоже главное окно, тот же фрейм, та же надпись.") my_frame.pack() root.update_idletasks() root.deiconify() yesno = messagebox.askokcancel("OMG", "Может всетаки новое окно?") my_frame.destroy() my_frame = Frame(root) my_frame.pack() font = Font(family="Helvetica", size=16) text = Text(my_frame, font=font) text.pack() text.insert(END, "Все новое - хорошо забытое старое.\n") text.insert(END, "В нашем случае, это не пустые слова.") Button(my_frame, text="Выход", command=root.destroy).pack() root.mainloop()