Tkinter сделать окно активным

Обновлено: 05.05.2024

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

Если у вас есть что-то с «меню Пуск», для вашего начального экрана, и после выбора пользователя вы переходите в другой раздел программы и перерисовываете экран соответствующим образом, каков элегантный способ сделать это?

Нужно ли просто .destroy() фрейм 'меню Пуск', а затем создать новый, заполненный виджетами для другой части? И отменить этот процесс, когда они нажимают кнопку назад?

3 ответа

Один из способов - сложить кадры друг над другом, тогда вы можете просто поднять один над другим в порядке наложения. Тот, что сверху, будет тем, который виден. Это работает лучше всего, если все кадры имеют одинаковый размер, но с небольшой работой вы можете заставить его работать с кадрами любого размера.

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

Вот немного надуманного примера, чтобы показать вам общую концепцию:

start page
page 1
page 2

Если вы находите концепцию создания экземпляра в классе неясной или если разные страницы требуют разных аргументов во время конструирования, вы можете явно вызывать каждый класс отдельно. Цикл служит главным образом для иллюстрации того, что каждый класс идентичен.

Например, чтобы создать классы индивидуально, вы можете удалить цикл ( for F in (StartPage, . ) с помощью этого:

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

Предупреждение . Ответ, приведенный ниже, может вызвать память утечка путем многократного уничтожения и воссоздания кадров.

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

Я изменил ответ Брайана Оукли, чтобы уничтожить старый фрейм перед его заменой. В качестве дополнительного бонуса это устраняет необходимость в объекте container и позволяет использовать любой общий класс Frame .

Start page
Page one
Page two

Объяснение

switch_frame() работает путем принятия любого объекта Class, который реализует Frame . Затем функция создает новый кадр для замены старого.

Есть программа, написанная на Python 3 и имеющая GUI-обмотку на Tkinter. Эта программа последовательно создает окна типа Toplevel. Главное окно (root) скрывается методом withdraw или остается не скрытым там, где это нужно. При работе с ней одни и те же окна Tkinter могут после вызова становиться активными, а могут выходить на передний план, но, тем не менее, оставаться неактивными, и приходится либо использовать переключение по Alt+Tab, либо клик мышью, чтобы навести фокус. Сейчас я работаю в openbox, и чтобы окна моей программы попадали в фокус, можно постепенно сворачивать окна других программ, пока на передний план не выйдет окно моей программы. Ввиду этого создалось впечатление, что такое поведение зависит от оконного менеджера/среды. Решил проверить эту гипотезу и установил gnome, kde, xfce4, lxde, fluxbox, icewm. В итоге openbox, lxde, icewm не могли обеспечить нужного мне поведения окон, fluxbox вроде смог, но паузы между разрушением/скрытием окон достигали секунд 2, так что это малоюзабельно. В Gnome и KDE программа работает как нужно, но мне не охота разгребать ту кучу бажного навоза, которая осталась после интеграции моего $HOME с их настройками. В одном из менеджеров посмотрел свойства своего окна, там было что-то типа WM_TAKEFOCUS False и Focus policy Passive. Так что вопрос. Встречались ли вы с таким? Каким образом задается эта Focus policy?

Novator ★★★★★ ( 17.12.13 11:50:21 )
Последнее исправление: Novator 17.12.13 11:52:56 (всего исправлений: 1)

Можно поподробнее? window.present - это же вроде из gtk (я его не знаю)?

Аа, точно, у тебя же Tk. Тогда не знаю.

это пример из книги Лутц М. Программирование на Python, том I,4-еиздание. – Пер. с англ. 582 страница.

win.focus_set() Передает окну фокус ввода приложения, как если бы оно было акти- визировано щелчком мыши. У этого метода есть также синоним, fo- cus, и часто фокус ввода устанавливается не на все окно, а на виджет в нем, позволяющий вводить данные (например, Entry). (c)

.focus_set() уже имеется на элементах типа Entry или Text всех окон. Проблемы возникают, когда я переключаюсь на другие программы через Alt+Tab, а затем назад и работаю дальше.

может надо принудительно еще и на окно повесить .focus_set()? возможно на виджетах оно работает только если окно принимает фокус

Есть программа (уже писал в соседних тредах, но выведу в новый тред, так как новый вопрос ближе к tkinter всё-таки). Чат клиент-серверный. Хочу написать GUI к клиенту. (желательно использовать именно tkinter, но если так и не разберусь, плюну и буду пытаться освоить pyqt4). Ну да не в тему.

В общем, хочу, чтобы самое первое окно при запуске программы спрашивало адрес сервера + порт сервера - с единственной кнопочкой connect. После нажатия на которую (в случае успешного соединения, ошибку разберусь как лучше обработать) - старое окно как бы удалялось, и замещалось новым - где уже требовалось бы ввести login/password. После авторизации надо чтобы второе окно тоже пропадало - и уже появлялось окно собственно чата (список комнат), и уже оно становилось бы главным.

Как такое реализовать (и можно ли)? Мне нужен TopLevel() или что-то другое?

Главное окно создаю примерно так:

Собственно метод нажатия на кнопку (частично)

Собственно, создаётся новое окно, а старое не убирается. и старое остаётся главным (если закрыть первое, закроется и второе). Сделать второе главным? (а после успешного логина сделать третьё).

Сделай одно специальное окно главным и невидимым.

Собственно, если кто подскажет какую схему с pyqt4 провернуть (аналогичную - тоже благодарен)

ок. попробую (то есть в случае pyqt4 просто метод show() не вызывать). Как вариант.

или как вариант сразу создавать окно со списком комнат (но показывать его не сразу)

У окна, если не ошибаюсь, есть метод root.withdraw(), который его прячет.

Тебе не TopLevel(), он по сути просто создает еще одно активное окно (именно окно, а не диалог), которое не блокирует родительское. Но оно все равно остается дочерним.

А почему не сделать проще - сделать два состояния у родительского окна. При первом несколько entry + кнопки. Коннет успешен - старые виджеты просто удаляются с фрейма, а добавляются новые, котороые собственно и необходимы. При этом весь список виджетов с параметрами можно держать либо в словарях, либо в списках. Выстраивание интерфейса соответственно через обработчик списка/словаря. Ну собственно и получаем профит :).

З.Ы.: Может коряво описал, но примерно такой принцип у меня использует клиент, который сейчас к серваку на python как раз пишу. Только я брал сразу Tcl/Tk, на нем такой подход ИМХО логичнее. Для python. можно по идее состояния попробовать через объект реализовать. Будут «те же яйца только в профиль».

приходила такая мысль в голову, почему-то подумал, что так неправильно.

ладно, сейчас с pyqt4 шаманю, будет время - попробую такую схему :)

Такая схема как раз является Ъ-Tk (на сколько я знаю). Я сейчас даже процедуры для кнопкокоманд собираю в зависимости от состояния. Получается, ИМХО, удобно. Хотя определенная порция веществ в данном подходе все же есть. Но тут. как говорится на вкус и цвет все фломастеры разные.

З.Ы.: сорри за вопрос, но все же - а почему не родной tcl/tk? тогда работать будет на любом утюге, где есть виртуальная машина тикля. Для задачи - подходит на первый взгляд почти идеально.

цель задачи вообще говоря изучение питона.

впрочем tkinter сюда даже больше подходит (так как входит в стандартные библиотеки). просто pyqt4 просто приятнее. да и не такая проблема его установить.

а на утюге мне эта штука точно не нужна :)

Тогда точно лучше Tkinter. Судя по словам Гвидо для питона он Ъ и наиболее удобный/готовый фреймворк. Ну и позволяет писать на «правильном» питоновом стиле (pythonic по моему это обзывается, хотя могу ошибаться).

Это конечно классно, но тут получается «создание» одинаковых сущностей (получается что начало создается один цикл Tk, потом он после манипуляций уничтожается вместе с окном, потом опять создается цикл Tk. Для Tk (и tkinter) правильнее сделать все в одном цикле через пересборку виджетов на рутовом окне.

я вообще сильно разочаровываюсь в этом питоне :) слишком легко на нём написать код, который если потом через полгода перечитывать - сам не поймёшь. а это плохо.

Тогда советую приглядеться к Tcl (мощный язык, мне больше чем lisp понравился - простой, гибкий, с понятным читаемым синтаксисом) или Ruby (раньше на нем писал, остались только лучшие воспоминания и чувства.

А Python самого разочаровал. Не хватает мне в нем гибкости tcl (чем больше общаюсь с CommonLisp и Tcl тем больше мне нравится «тикль» [раньше было наоборот]), читаемого синтаксиса (недавно доставал исходник написанный на tcl года два назад - вспомнил очень быстро). Ну и поддержка парадигм в Tcl гораздо шире чем в Python. «Тикль» будет таким как хочешь ты, а python заставит тебя перестроиться под него. В этом их разница.

Ладно. Тогда ответьте на такой вопрос. как правильно лучше сделать.

Вот есть чат-клиент. Сетевое приложение. Суть заключается в том, что обращается к удалённому серверу (через сокет), в главном потоке посылает команды, в другом потоке принимает ответы и уведомления. (так как sock.recv() - операция блокирующая, выделил в отдельный поток).

в ответ должен получить

Как мне такое реализовать? на tkinter. Чтобы GUI не блокировался.

А зачем делать блокировку гуя в tkinter? Если ты выстраиваешь его динамически, то достаточно держать все элементы интерфейса в списках/словарях (удобнее ИМХО словари). Т.е. ты делаешь рутовое окно, на нем фрейм. А во фрейм все выстраивается динамически через обработку словаря. Соответственно клиент принимает любые запросы, которые меняют списки/словари по которым выстраивается интерфейс, а интерфейс в свою очередь просто работает - список изменился - выстроился «новый интерфейс».

В tcl я так сделал на основе как раз словарей - т.е. есть в клиенте функция которая постоянно принимает ответы и обрабатывает их (разносит по спискам) и меняет запросы она выполняется первой в списке команд цикла Tk (до того как на фрейм выстраиваются все виджеты). В свою очередь по словарям выстраивается вообще все в программе - начиная от интерфейса программы и заканчивая функциями для виджетов (command, которые). Сам цикл Тк выполняется до тех пор пока существует рутовое окно, к которому оно привязано (т.е. root=Tkinter Tk()). Соответственно все что между Tk() и mainloop() будет выполнятся постоянно, пока окно не будет уничтожено (в том числе и функция, которая тебе нужна).

(как подсказывает мне мой коллега, который как раз пишет сервак на питоне - он бы сделал просто два потока, в одном бы крутил сам GUI, а во второй бы запихал всю работу с сетью, а потоки бы обменивались между собой данными при необходимости [но у него есть нехорошая привычка все распаралеливать, так что решение, ИМХО, спорное])

первый способ так и не понял. можно на простом примере?

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

Для Tk (и tkinter) правильнее сделать все в одном цикле

Как заставить приложение Tkinter перейти на передний план? В настоящее время окно появляется за всеми моими другими окнами и не фокусируется.

Есть какой-то метод, который я должен вызывать?

Предполагая, что вы имеете в виду окна своего приложения, когда говорите «другие мои окна», вы можете использовать метод lift() на Toplevel или Tk:

Если вы хотите, чтобы окно оставалось выше всех других окон, используйте:

Где root - ваш Toplevel или Tk. Не забудьте - инфронт "topmost" !

Чтобы сделать его временным , отключите самый верхний сразу после:

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

Что касается Mac, я заметил, что может быть проблема в том, что если запущено несколько графических интерфейсов Python, каждый процесс будет называться «Python», а AppleScript будет стремиться выдвинуть неправильный текст вперед. Вот мое решение. Идея состоит в том, чтобы получить список ID запущенных процессов до и после загрузки Tkinter. (Обратите внимание, что это идентификаторы процессов AppleScript, которые, по-видимому, не имеют отношения к их аналогам posix. Поймите.) Тогда странный человек будет вашим, и вы переместите его вперед. (Я не думал, что цикл в конце будет необходим, но если вы просто получите каждый процесс, чей ID является procID, AppleScript, очевидно, возвращает один объект, идентифицируемый по имени, что, конечно, является неуникальным «Python», поэтому мы вернемся к исходной точке, если я что-то упустил.)

Если окно, над которым вы хотите поднять целевое окно, известно, вы можете просто использовать метод tkraise с параметром aboveThis , установленным для окна, которое вы хотите рисовать.

Есть подсказка о том, как заставить окно Tkinter сфокусироваться при вызове mainloop () в функции Tkinter._test ().

Это самый чистый и правильный способ, который я нашел для этого, но он нужен только для систем Windows.

В macOS High Sierra, py3.6.4, вот мое решение:

Идея состоит в том, чтобы вынести это на передний план, пока пользователь не будет взаимодействовать с ним, то есть сфокусироваться.

Я попробовал принятый ответ, .after_idle() и .after() . Все они терпят неудачу в одном случае: когда я запускаю свой скрипт непосредственно из IDE, такой как PyCharm, окно приложения остается позади.

Мое решение работает во всех случаях, с которыми я столкнулся.

В некоторой степени это комбинация различных других методов, это работает на OS X 10.11 и Python 3.5.1, работающих в venv, и должно работать на других платформах. Он также нацелен на приложение по идентификатору процесса, а не по имени приложения.

Вы вызываете это прямо перед вызовом mainloop() , вот так:

В Mac OS X PyObjC предоставляет более чистый и менее подверженный ошибкам метод, чем выделение в osascript:

Добавьте следующие строки перед mainloop ():

Это прекрасно работает для меня. Когда окно генерируется, оно выходит вперед, и оно не будет всегда оставаться впереди.

Если вы делаете это на Mac, используйте AppleEvents, чтобы сфокусироваться на Python. Например:

Возможность реагировать на события — одна из базовых, но важных тем в приложениях с графическим интерфейсом. Именно она определяет, как пользователи смогут взаимодействовать с программой.

Нажимание клавиш на клавиатуре и клики по элементам мышью — базовые примеры событий, все из которых автоматически обрабатываются в некоторых классах Tkinter. Например, это поведение уже реализовано в параметре command класса виджета Button , который вызывает определенную функцию.

Некоторые события можно вызвать и без участия пользователя. Например, фокус ввода можно сместить с одного виджета на другой.

Выполнить привязку события к виджету можно с помощью метода bind . Следующий пример привязывает некоторые события мыши к экземпляру Frame :

Обработка событий с мыши

Следующий пример содержит виджет поля ввода и несколько привязок. Одна из них срабатывает в тот момент, когда фокус оказывается в виджете, а вторая — при нажатии кнопки:

Как работает отслеживание событий

Метод bind определен в классе widget и принимает три аргумента: событие sequence , функцию callback и опциональную строку add :

Строка sequence использует синтаксис .

Модификаторы являются опциональными и позволяют задать дополнительные комбинации для общего типа события:

  • Shift – когда пользователь нажимает клавишу Shift.
  • Alt – когда пользователь нажимает клавишу Alt.
  • Control – когда пользователь нажимает клавишу Control.
  • Lock – когда пользователь нажимает клавишу Lock.
  • Shift – когда пользователь нажимает клавишу Shift.
  • Shift – когда пользователь нажимает клавишу Shift lock.
  • Double – когда событие происходит дважды подряд.
  • Triple – когда событие происходит трижды подряд.

Типы события определяют общий тип события:

  • ButtonPress или Button – события, которые генерируются при нажатии кнопки мыши.
  • ButtonRelease – событие, когда кнопка мыши отпускается.
  • Enter – событие при перемещении мыши на виджет.
  • Leave – событие, когда мышь покидает область виджета.
  • FocusIn – событие, когда фокус ввода попадает в виджет.
  • FocusOut – событие, когда виджет теряет фокус ввода.
  • KeyPress или Key – событие для нажатия кнопки.
  • KeyRelease – событие для отпущенной кнопки.
  • Motion – событие при перемещении мыши.

detail – также опциональный параметр, который отвечает за определение конкретной клавиши или кнопки:

  • Для событий мыши 1 — это левая кнопка, 2 — средняя, а 3 — правая
  • Для событий клавиатуры используются сами клавиши. Если это специальные клавиши, то используется специальный символ: enter, Tab, Esc, up, down, right, left, Backspace и функциональные клавиши (от F1 до F12).

Функция callback принимает параметр события. Для событий мыши это следующие атрибуты:

  • x и y текущее положение мыши в пикселях
  • x_root и y_root то же, что и x или y, но относительно верхнего левого угла экрана
  • num – номер кнопки мыши

Для клавиш клавиатуры это следующие атрибуты:

  • char – нажатая клавиша в виде строки
  • keysym – символ нажатой клавиши
  • keycode – код нажатой клавиши

В обоих случаях у события есть атрибут widget , ссылающийся на экземпляр, который сгенерировал событие и type , определяющий тип события.

Рекомендуется определять метод для функции callback , поскольку всегда будет иметься ссылка на экземпляр класса, и таким образом можно будет легко получить доступ к атрибутам widget .

Наконец, параметр add может быть пустым ( "" ) для замены функции callback , если до этого была привязка или + для добавления функции обратного вызова и сохранения старых.

Помимо описанных типов событий есть и другие, которые оказываются полезными в определенных сценариях: например, генерируется при уничтожении виджета, а — при изменении размера или положения.

Полный список событий доступен в документации Tcl/Tk.

Настройка иконки, названия и размера основного окна

Экземпляр Tk отличается от обычных виджетов тем, как он настраивается. Рассмотрим основные методы, которые позволяют настраивать внешний вид.

Этот кусок кода создает основное окно с заданными названием и иконкой. Его ширина — 400 пикселей, а высота — 200. Плюс, есть разделение в 10px по каждой оси к левому верхнему углу экрана.

Читайте также: