Как в Tkinter отцентровать окно внутри другого окна?
У меня есть некое окно с кнопками. Одна из кнопок открывает еще одно окно. Как можно сделать так, чтобы второе окно, которое открывается по кнопке, открывалось по центру первого окна? Или хотя бы не выходило за его пределы.
В гугле не нашел ничего похожего кроме того как в целом можно отцентровать окно, но как связать окна между собой не ясно. Буквально вчера начал изучать python по этому пока не могу даже наводящими поисковыми запросами вырулить в нужное направление.
- Вопрос задан 01 июн. 2023
- 126 просмотров
1 комментарий
Простой 1 комментарий

Модератор @TosterModerator
Не надо ставить как можно больше тэгов. Лучше оставить один, но конкретный, с которым проблема.
См.п.3.1 Регламента.
Если нет кода python, не ставьте тэг python . Если есть тэг более конкретный, чем python (т.е. Tkinter ) не ставьте тэг python .
Решения вопроса 0
Ответы на вопрос 1

Nicelogin @Nicelogin Автор вопроса
Решил вот таким способом, отцентровав окна по отдельности. В целом удовлетворительно, так как окна открываются не абы где а примерно по центру. Так как первое окно больше, а второе меньше создается ощущение что они связаны и второе открывается внутри первого. Но это не совсем решение так как если изменить настройки открытия первого второе не потянется за ним и не будет открываться внутри.
w = root.winfo_screenwidth() h = root.winfo_screenheight() w = w//2 # середина экрана h = h//2 w = w - 200 # смещение от середины h = h - 200 root.geometry('400x400+<>+<>'.format(w, h))
Окна в tkinter
В этом уроке рассмотрим основные настройки окон, в которых располагаются виджеты. Обычные окна в Tkinter порождаются не только от класса Tk , но и Toplevel . От Tk принято создавать главное окно. Если создается многооконное приложение, то остальные окна создаются от Toplevel. Методы обоих классов схожи.
Размер и положение окна
По умолчанию окно приложения появляется в верхнем левом углу экрана. Его размер (ширина и высота) определяется совокупностью размеров расположенных в нем виджетов. В случае если окно пустое, то tkinter устанавливает его размер в 200 на 200 пикселей.
С помощью метода geometry можно изменить как размер окна, так и его положение. Метод принимает строку определенного формата.
from tkinter import * root = Tk() root.geometry('600x400+200+100') root.mainloop()
Первые два числа в строке-аргументе geometry задают ширину и высоту окна. Вторая пара чисел обозначает смещение на экране по осям x и y . В примере окно размерностью 600 на 400 будет смещено от верхней левой точки экрана на 200 пикселей вправо и на 100 пикселей вниз.
Если перед обоими смещениями вместо плюса указывается минус, то расчет происходит от нижних правых углов экрана и окна. Так выражение root.geometry(‘600×400-0-0’) заставит окно появиться в нижнем правом углу.
В аргументе метода geometry можно не указывать либо размер, либо смещение. Например, чтобы сместить окно, но не менять его размер, следует написать root.geometry(‘+200+100’) .
Бывает удобно, чтобы окно появлялось в центре экрана. Методы winfo_screenwidth и winfo_screenheight возвращают количество пикселей экрана, на котором появляется окно. Рассмотрим, как поместить окно в центр, если размер окна известен:
from tkinter import * root = Tk() w = root.winfo_screenwidth() h = root.winfo_screenheight() w = w // 2 # середина экрана h = h // 2 w = w - 200 # смещение от середины h = h - 200 root.geometry(f'400x400++') root.mainloop()
Здесь мы вычитаем половину ширины и высоты окна (по 200 пикселей). Иначе в центре экрана окажется верхний левый угол окна, а не его середина.
Если размер окна неизвестен, то его можно получить с помощью того же метода geometry , но без аргументов. В этом случае метод возвращает строку, содержащую сведения о размерах и смещении, из которой можно извлечь ширину и высоту окна.
from tkinter import * root = Tk() Button(text="Button", width=20).pack() Label(text="Label", width=20, height=3).pack() Button(text="Button", width=20).pack() root.update_idletasks() s = root.geometry() s = s.split('+') s = s[0].split('x') width_root = int(s[0]) height_root = int(s[1]) w = root.winfo_screenwidth() h = root.winfo_screenheight() w = w // 2 h = h // 2 w = w - width_root // 2 h = h - height_root // 2 root.geometry('+<>+<>'.format(w, h)) root.mainloop()
Метод update_idletasks позволяет перезагрузить данные об окне после размещения на нем виджетов. Иначе geometry вернет строку, где ширина и высота равняются по одному пикселю. Видимо таковы параметры на момент запуска приложения.
По умолчанию пользователь может разворачивать окно на весь экран, а также изменять его размер, раздвигая границы. Эти возможности можно отключить с помощью метода resizable . Так root.resizable(False, False) запретит изменение размеров главного окна как по горизонтали, так и вертикали. Развернуть на весь экран его также будет невозможно, при этом соответствующая кнопка разворота исчезает.

Заголовок окна
По умолчанию в заголовке окна находится надпись «tk». Для установки собственного названия используется метод title .
… root.title("Главное окно") …

Если необходимо, заголовок окна можно вообще убрать. В программе ниже второе окно ( Toplevel ) открывается при клике на кнопку, оно не имеет заголовка, так как к нему был применен метод overrideredirect с аргументом True . Через пять секунд данное окно закрывается методом destroy .
from tkinter import * def about(): a = Toplevel() a.geometry('200x150') a['bg'] = 'grey' a.overrideredirect(True) Label(a, text="About this").pack(expand=1) a.after(5000, lambda: a.destroy()) root = Tk() root.title("Главное окно") Button(text="Button", width=20).pack() Label(text="Label", width=20, height=3).pack() Button(text="About", width=20, command=about).pack() root.mainloop()

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

Курс с примерами решений практических работ: pdf-версия
X Скрыть Наверх
Tkinter. Программирование GUI на Python
Tkinter, раскрытие потенциала. + Игра на рабочем столе
Tkinter — это та библиотека, с которой на начальных этапах изучения языка python знакомились все, но обходили стороной по разным причинам. Сейчас я предлагаю вернуться назад, немного поностальгировать и открыть для себя в разы больше фич библиотеки.
ВАЖНО! Tkinter — не лучшее решение для создания больших приложений. И по большей части эта статья нацелена на начинающих программистов, которые уже имеют представление о библиотеке и хотят рыть дальше.
Улучшаем кнопки tkinter.Button
Пройдёмся по параметры кнопок, которые нам пригодятся:
- bg — фон кнопки (background color)
- fg — цвет текста кнопки (foreground color)
- bd — ширина обводи
- text — сам текст
- command — функция исполняющаяся при нажатии
- font — шрифт
- relief — стиль обводки (tk.GROOVE , tk.SUNKEN , tk.RAISED , tk.RIDGE , tk.FLAT)
- state — состояние кнопки (tk.ACTIVE , tk.DISABLED)
- underline — подчёркнутый символ текста (>-1)
- padx , pady — отступы по горизонтали , вертикали
- width , height — ширина , высота (!В СТРОЧКАХ)
- activebackground — фон кнопки при активации
- activeforeground — цвет текста кнопки при активации
- cursor — курсор (https://docs.huihoo.com/tkinter/tkinter-reference-a-gui-for-python/cursors.html)
Предлагаю сделать «кнопку ссылку», которая при нажатии будет перекидывать нас на какой-нибудь сайт. Библиотека webbrowser — встроенная, её не нужно устанавливать.
import tkinter as tk import webbrowser FORM = tk.Tk() # Создаём окно FORM .geometry('500x500') # Задаём размер def link(e = None): # !ОБРАТИТЕ ВНИМАНИЕ на e = None webbrowser.open('https://www.youtube.com/watch?v=dQw4w9WgXcQ') # Открываем браузер button = tk.Button(FORM,command = link,padx = 5,pady = 5,text = 'Link',bd = 0, fg = '#fff', bg = '#08f',underline = 0 , activebackground = '#fff', activeforeground = '#fff',cursor = 'hand2') # Инициализация кнопки button.pack(expand = 1) # Размещение кнопки по центру окна # Сюда мы ещё добавим код FORM.mainloop()

Выглядит уже неплохо. Предлагаю добавить смену цвета кнопки при наведении курсором мыши.
def focus_in(e = None): button.configure(fg = '#08f') # Задаём кнопке нужные цвета button.configure(bg = '#fff') def focus_out(e = None): button.configure(bg = '#08f') button.configure(fg = '#fff') button.bind('', focus_in) # При входе курсора в область кнопки выполняем focus_in button.bind('', focus_out) # При выходе курсора из области кнопки выполняем focus_out_out

Улучшаем окно программы
Начнём с добавления переключения на полноэкранного режима (верхней панели окна не видно) по клавише F11:
def fullscreen(e = None): if FORM.attributes('-fullscreen'): # Проверяем режим окна FORM.attributes('-fullscreen',False) # Меняем режим окна else: FORM.attributes('-fullscreen',True) # Меняем режим окна FORM.bind('',fullscreen) # Биндим окно
Теперь нужно сделать так, что бы пользователь не мог не нажать на ту самую ссылку. Давайте сделаем выход из приложения невозможным (возможным только через диспетчер задач (ctrl+shift+esc)).
def on_close(e = None): pass # Мы просто ничего не делаем # Может быть любой код # Можно так же спрашивать пользователя ВЫ ТОЧНО ХОТИТЕ ЗАКРЫТЬ ОКНО ? FORM.protocol("WM_DELETE_WINDOW", on_close) # Перехватываем событие выхода из приложения
Реализуем ка мы также и «приоритетный» режим, окно будет отображаться поверх остальных. Здесь я рекомендую использовать библиотеку keyboard (pip install keyboard), так как bind tkintera работает только если окно находиться в фокусе. Keyboard же никак не зависит от tkintera и расположения окон.
def topmost(e = None): if FORM.attributes('-topmost'): # Проверяем режим окна FORM.attributes('-topmost',False)# Меняем режим окна else: FORM.attributes('-topmost',True)# Меняем режим окна keyboard.add_hotkey('ctrl+1',topmost) # Привязываем событие к функции
Проблема окон, которые находятся поверх всех — что бы что-то под ними увидеть, нужно их передвигать, а это иногда бывает не удобно. Да бы избежать неудобств пользователя, мы можем добавить окну 50% прозрачности. Когда пользователь наводит курсор на окно, оно становится непрозрачным.
FORM.attributes('-alpha',0.5) # Задаём изначальное значение прозрачности def form_focus_in(e = None): FORM.attributes('-alpha',1) def form_focus_out(e = None): FORM.attributes('-alpha',0.5) FORM.bind('', form_focus_in) # При входе курсора в область окна выполняем form_focus_in FORM.bind('', form_focus_out)# При входе курсора в область окна выполняем form_focus_out
Поэкспериментировав с выше показанным, я думаю, вы в скором времени зададитесь вопросом «Можно ли убрать верхнюю панель у окна ?». Да, можно, но вам придётся самостоятельно реализовывать передвижение и если хотите, изменение размеров окна. Плюсы своего окна начинаются и заканчиваются том, что вы полностью контролируете внешний вид и логику окна. Также есть огромный минус — пока окно открыто, оно не отображается в панели задач, по этому ему желательно давать приоритетный режим, что бы пользователь не потерял окно под другими.
from tkinter import * # Объявляем основные цвета BGCL = '#000000' CANCELCL = '#800000' CANCELHOVCL = '#400000' INFOCL = '#000080' INFOHOVCL = '#000040' BARCL = '#004000' # Если число больше min, возвращает minrep, если число больше max, возвращает maxrep def barrier(val,min = 0, max = None,minrep = None,maxrep = None): if minrep is None:minrep = min if maxrep is None: maxrep = max if not min is None and val < min:return minrep elif not max is None and val >max: return maxrep else:return val # Просто пустая функция def empty(*args,**kwargs):pass # Класс усовершенстованого окна class Form(Tk): def __init__(self,resizeable = True,exitfunc = empty,onresizefunc = empty): Tk.__init__(self) self['bg'] = BGCL self.resizeable = resizeable self.exitfunc = exitfunc self.onresizefunc = onresizefunc self.overrideredirect(True) # убираем у окна панельку self.wm_attributes('-topmost',True) # приоритетный режим self.bar = Frame(self,bg = BARCL) # Создаём свою панельку, как отдельный виджет self.bar.place(x = 0 ,y = 0,relwidth = 1,height = 24) self.closebtn = Button(self,bg = CANCELCL,fg = BGCL,relief = FLAT,command = self.Exit,bd=0,activebackground = BGCL) self.closebtn.place(width =24,height = 24,x = self.winfo_reqwidth()-24) self.wrapbtn = Button(self,bg = INFOCL,fg = BGCL,relief = FLAT,command = self.Wrap,bd=0,activebackground = BGCL) self.wrapbtn.place(width =24,height = 24,x = self.winfo_reqwidth()-48) # Здесь биндятся функции, передвигающие окно self.bar.bind("", self.StartMove) self.bar.bind("", self.StopMove) self.bar.bind("", self.OnMotion) # Обеспечиваем hover эффект кнопкам на нашей панельке self.closebtn.bind("",self.__closebtne) self.closebtn.bind("",self.__closebtnl) self.wrapbtn.bind("",self.__wrapbtne) self.wrapbtn.bind("",self.__wrapbtnl) # Запоминаем ширину и высоту окна self.width = self.winfo_reqwidth() self.height = self.winfo_reqheight() # В этом framе создавайте новые виджеты self.content = Frame(self,bg = BGCL,highlightthickness = 0) self.content.place(x=0,y = 24, width = self.width,height = self.height-24) if resizeable: # создаём кнопку изменения размера self.resizebtn = Button(self,bg = BARCL,fg = BGCL,relief = FLAT,bd=0,activebackground = BGCL,text = '=',font = ('Fixedsys',11),cursor = 'tcross') self.resizebtn.place(width =12,height = 12,x = self.winfo_reqwidth()-12,y = self.winfo_reqheight()-12) # Её hover эффект self.resizebtn.bind("",self.__resizebtne) self.resizebtn.bind("",self.__resizebtnl) # Здесь биндятся функции, меняющие размер окна self.resizebtn.bind("", self.StartResize) self.resizebtn.bind("", self.StopResize) self.resizebtn.bind("", self.OnResize) # Событие развёртывания окна (Редкое) self.bind('',self.Show) # функции hover эффектов def __closebtne(self,event = None): self.closebtn['bg'] = CANCELHOVCL def __closebtnl(self,event = None): self.closebtn['bg'] = CANCELCL def __resizebtne(self,event = None): self.resizebtn['bg'] = BGCL self.resizebtn['fg'] = BARCL def __resizebtnl(self,event = None): self.resizebtn['bg'] = BARCL self.resizebtn['fg'] = BGCL def __wrapbtne(self,event = None): self.wrapbtn['bg'] = INFOHOVCL def __wrapbtnl(self,event = None): self.wrapbtn['bg'] = INFOCL # Передвижение окна def StartMove(self, event = None): self.dragx = event.x self.dragy = event.y def StopMove(self, event = None): self.dragx = None self.dragy = None def OnMotion(self, event = None): deltax = event.x - self.dragx deltay = event.y - self.dragy x = self.winfo_x() + deltax y = self.winfo_y() + deltay self.geometry("+%s+%s" % (x, y)) # Изменение размера окна def StartResize(self, event = None): self.resizex = event.x self.resizey = event.y def StopResize(self, event = None): self.resizex = None self.resizey = None def OnResize(self, event = None): deltax = event.x - self.resizex deltay = event.y - self.resizey x = self.width + deltax y = self.height + deltay self.width =barrier(x,min=self.minsize()[0]) self.height = barrier(y,min=self.minsize()[1]) self.geometry("%sx%s" % (self.width,self.height)) self.Resize() # Функция вызывается после изменения размера, что бы заного разместить все кнопки. def Resize(self,event = None): # onresizefunc - вы можете передать функцию при инициализации, она будет выполнятся здесь self.onresizefunc(self.width,self.height) if self.resizeable: self.resizebtn.place_configure(x = self.width-12,y = self.height-12) self.closebtn.place_configure(y=0,x = self.width-24) self.wrapbtn.place_configure(y=0,x = self.width-48) self.content.place_configure(width = self.width,height = self.height-24) # Выполняется после нажатия красной кнопки def Exit(self,event = None): # exitfunc - вы можете передать функцию при инициализации, она будет выполнятся здесь self.exitfunc() self.destroy() # Сворачивает окно def Wrap(self,event = None): self.withdraw() # скрытие окна self.overrideredirect(False) # возвращаем ему панельку self.wm_state('iconic') # сворачиваем def Show(self,event = None): # Окно уже развёрнуто self.overrideredirect(True) #Просто обратно забираем панельку FORM = Form() # Создаём объект Form (изменённый Tk) FORM.minsize(200,200) #Задаём минимальный размер FORM.mainloop()
Вкратце опишу произошедшее выше. Мы создаём класс Form на основе класса Tk, то есть Form это модифицированный Tk. При инициализации объекта мы добавляем ему кнопки закрытия, сворачивания, изменения размера и два фрейма, панелька и контент. Делаем ховер эффект для каждой кнопки. Дальше реализуем перемещение окна. По нажатию на клавишу мыши координаты сохраняются, затем по мере движения мыши в соответствии с сохранёнными координатами меняется положение окна. С изменениями размера система та же, но при изменениях размера меняется также положение кнопок и. т. п.

Необычная игра
Скептик скажет «Питон не для игр, а tkinter так уж тем более». И я с этим скептиком от части согласен, tkinter НЕ для игр, для игр лучше pygame, а вот для «десктопных» игр это самое простое и единственное мне известное решение. Под «десктопной» игрой я подразумеваю игру, которая отрисовывается поверх всех окон, прямо на рабочем столе. В этом примере пиксельный человечек прыгает по окнам, это просто пример.
Как работает отрисовка поверх экрана в tkinter? Создаётся полноэкранное белое окно, на нём создаётся белый canvas, на canvase отрисовывается графика и всё белое заменяется прозрачным. Цвет не обязательно должен быть белым. В подобных играх также следует использовать keyboard, а именно функцию is_pressed() для проверки нажатия клавиш.
import tkinter as tk import keyboard FORM = tk.Tk() def Update(e = None): # Ваш игровой цикл FORM.after(int(1000/FPS),Update) FPS = 60 CANVAS = tk.Canvas(FORM,bg = 'white',bd = 0,highlightthickness = 0) CANVAS.place(x=0,y=0,width = FORM.winfo_screenwidth(),height = FORM.winfo_screenheight()) FORM.overrideredirect(True) FORM.state('zoomed') FORM.wm_attributes("-topmost", True) FORM.wm_attributes("-transparentcolor", "white") FORM.after(int(1000/FPS),Update) FORM.mainloop()
Я также напишу статью про создание «десктопной» игры в скором времени.
Метод pack
Прежде чем продолжить знакомство с виджетами GUI остановимся на вопросе их расположения в окне. Это важный вопрос, так как от продуманности интерфейса во многом зависит удобство использования программы. Организуя виджеты в пространстве, программист отчасти становится дизайнером, разработчиком интерфейсов.
В Tkinter существует три так называемых менеджера геометрии – упаковщик, сетка и размещение по координатам. В этом уроке будет рассмотрен первый как наиболее простой и часто используемый, остальные два изучим позже.
Упаковщик (packer) вызывается методом pack , который имеется у всех виджетов-объектов. Мы уже использовали его. Если к элементу интерфейса не применить какой-либо из менеджеров геометрии, то он не отобразится в окне. При этом в одном окне (или любом другом родительском виджете) нельзя комбинировать разные менеджеры. Если вы начали размещать элементы интерфейса методом pack , не пытайтесь тут же использовать методы grid (сетка) и place (место).
Если в упаковщики не передавать аргументы, то виджеты будут располагаться вертикально, друг над другом. Тот объект, который первым вызовет pack , будет вверху. Который вторым – под первым, и так далее.
У метода pack есть параметр side (сторона), который принимает одно из четырех значений-констант tkinter – TOP , BOTTOM , LEFT , RIGHT (верх, низ, лево, право). По умолчанию, когда в pack не указывается side , его значение равняется TOP . Из-за этого виджеты располагаются вертикально.
Создадим четыре раскрашенные метки
… l1 = Label(width=7, height=4, bg='yellow', text="1") l2 = Label(width=7, height=4, bg='orange', text="2") l3 = Label(width=7, height=4, bg='lightgreen', text="3") l4 = Label(width=7, height=4, bg='lightblue', text="4") …
и рассмотрим разные комбинации значений сайда:

Проблема двух последних вариантов в том, что если надо разместить виджеты квадратом, т. е. два сверху, два снизу ровно под двумя верхними, то сделать это проблематично, если вообще возможно. Поэтому прибегают к вспомогательному виджету – фрейму (рамке), который порождается от класса Frame .
Фреймы размещают на главном окне, а уже в фреймах – виджеты:
from tkinter import * root = Tk() f_top = Frame(root) f_bot = Frame(root) l1 = Label(f_top, width=7, height=4, bg='yellow', text="1") l2 = Label(f_top, width=7, height=4, bg='orange', text="2") l3 = Label(f_bot, width=7, height=4, bg='lightgreen', text="3") l4 = Label(f_bot, width=7, height=4, bg='lightblue', text="4") f_top.pack() f_bot.pack() l1.pack(side=LEFT) l2.pack(side=LEFT) l3.pack(side=LEFT) l4.pack(side=LEFT) root.mainloop()

Кроме Frame существует похожий класс LabelFrame – фрейм с подписью. В отличие от простого фрейма у него есть свойство text .
… f_top = LabelFrame(text="Верх") f_bot = LabelFrame(text="Низ") …

Кроме side у pack есть другие параметры-свойства. Можно задавать внутренние ( ipadx и ipady ) и внешние ( padx и pady ) отступы:

Когда устанавливаются внутренние отступы, то из-за того, что side прибивает виджет к левой границе, справа получаем отступ в 20 пикселей, а слева – ничего. Можно частично решить проблему, заменив внутренние отступы рамки на внешние отступы у меток.

Но тут появляется промежуток между самими метками. Чтобы его убрать, пришлось бы каждый виджет укладывать в свой собственный фрейм. Отсюда делаем вывод, что упаковщик Tkinter удобен только для относительно простых интерфейсов.
Следующие два свойства – fill (заполнение) и expand (расширение). По-умолчанию expand равен нулю (другое значение – единица), а fill – NONE (другие значения BOTH , X , Y ). Создадим окно с одной меткой:
from tkinter import * root = Tk() l1 = Label(text="This is a label", width=30, height=10, bg="lightgreen") l1.pack() root.mainloop()
Если начать расширять окно или сразу раскрыть его на весь экран, то метка окажется вверху по вертикали и в середине по горизонтали. Причина, по которой метка не в середине по вертикали заключается в том, что side по-умолчанию равен TOP , и метку прибивает к верху.

Если установить свойство expand в 1, то при расширении окна метка будет всегда в середине:
… l1.pack(expand=1) …

Свойство fill заставляет виджет заполнять все доступное пространство. Заполнить его можно во всех направлениях или только по одной из осей:
… l1.pack(expand=1, fill=Y) …

Последняя опция метода pack – anchor (якорь) – может принимать значения N (north – север), S (south – юг), W (west – запад), E (east – восток) и их комбинации:
… l1.pack(expand=1, anchor=SE) …

Практическая работа
Перепишите программу из практической работы предыдущего урока так, чтобы интерфейс выглядел примерно следующим образом:

Курс с примерами решений практических работ: pdf-версия
X Скрыть Наверх
Tkinter. Программирование GUI на Python