Окна

В этом уроке рассмотрим основные настройки окон, в которых располагаются виджеты. Обычные окна в 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('600x400-0-0') заставит окно появиться в нижнем правом углу.

В аргументе метода geometry() можно не указывать либо размер, либо смещение. Например, чтобы сместить окно, но не менять его размер, следует написать root.geometry('+200+100').

Бывает удобно, чтобы окно появлялось в центре экрана. Методы winfo_screenwidth() и winfo_screenheight() возвращают количество пикселей экрана, на котором появляется окно. Рассмотрим, как поместить окно в центр, если размер окна известен:

…
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))

Здесь мы вычитаем половину ширины и высоты окна (по 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) запретит изменение размеров главного окна как по горизонтали, так и вертикали. Развернуть на весь экран его также будет невозможно, при этом соответствующая кнопка разворота исчезает.

resizable.png

Заголовок окна

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

… 
root.title("Главное окно")

root_title.png

Если необходимо, заголовок окна можно вообще убрать. В программе ниже второе окно (Toplevel) открывается при клике на кнопку, оно не имеет заголовка, так как к нему был применен метод overrideredirect() с аргументом True. Через пять секунд данное окно закрывается методом destroy().

from tkinter import *
root = Tk()
root.title("Главное окно")
 
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())
 
Button(text="Button", width=20).pack()
Label(text="Label", width=20, height=3).pack()
Button(text="About", width=20, command=about).pack()
 
root.mainloop()

overrideredirect.png

Практическая работа

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

prac11.png

Курс с примерами решений практических работ: android-приложение, pdf-версия.

Комментарии

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

https://pastebin.com/KfTY41wG

YOU'RE BEEN GNOMED

Практическая работа. Пыталась изобразить из мухи слона и 2 дня провозилась с классами, а потом поняла, что муха летает не хуже.
from tkinter import *
root=Tk()
root.title("Прямовал")
def add_window():
    def paint():        
        xx1=x1.get()
        xx2=x2.get()
        yy1=y1.get()
        yy2=y2.get()        
        if var.get()==2:
            c.create_oval(xx1,yy1,xx2,yy2)
        elif var.get()==1:
            c.create_rectangle(xx1,yy1,xx2,yy2)
        a.destroy()
 
    a=Toplevel()
    a.title("Фигура")
    a.resizable(False, False)  
    Label(a, text="x1").grid(row=0, column=0)
    Label(a, text="y1").grid(row=0, column=2)
    Label(a, text="x2").grid(row=1, column=0)
    Label(a, text="y2").grid(row=1, column=2)
    x1=Entry(a, width=5)
    x1.grid(row=0, column=1, sticky=W)
    y1=Entry(a, width=5)
    y1.grid(row=0, column=3,  sticky=W)
    x2=Entry(a, width=5)
    x2.grid(row=1, column=1,  sticky=W)
    y2=Entry(a, width=5)
    y2.grid(row=1, column=3,  sticky=W)
    var=IntVar()
    Radiobutton(a, text="Прямоугольник", variable=var, value=1).grid(row=2, columnspan=4)
    Radiobutton(a, text="Овал", variable=var, value=2).grid(row=3, columnspan=4, sticky=W)
    Button(a, text="Нарисовать", command=paint).grid(row=4, columnspan=4)
    a.geometry('+{}+{}'.format(w+width_root, h)) # размещение доп.окна справа от основного
 
c=Canvas(root, width=500, height=500, bg="white")
c.grid()
Button(text="Добавить фигуру", width=20, command=add_window).grid()
# размещение окна по центру
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()

 

from tkinter import *

root = Tk()
root.title('Главное окно')
newWnd=None #Глобальная. Для проверки окна на 'ужеоткрытость'

def  showNewWnd():
    def drawFigure():
        if     radioButton_var.get()==1:
            holst.create_rectangle(entryX1.get(),entryY1.get(), entryX2.get(),entryY2.get())
        elif radioButton_var.get()==2:
            holst.create_oval(entryX1.get(),entryY1.get(), entryX2.get(),entryY2.get())
        else:
            print('Выбери, что рисовать, да!')
            return
        newWnd.destroy()

    global newWnd
    if type(newWnd) is Toplevel: #Если окно уже открыто, убиваем старый экземпляр
        newWnd.destroy()

    newWnd=Toplevel()
    place=root.geometry() #Берём текущие параметры главного окна
    place=place.split('+')
    rootX=int(place[1])
    rootY=int(place[2])
    place=place[0].split('x')
    rootWidth=int(place[0])
    rootHeight=int(place[0])
    newWndX=rootX+rootWidth+14 #Эмпирический коэффициент 14 для красоты
    newWndY=rootY                                #Другое имя для читабельности
    newWnd.geometry('160x150+{}+{}'.format(newWndX, newWndY))
    newWnd.title('Фигура')

    frame1=Frame(newWnd); frame1.pack(side=TOP, pady=10)   #Некоторые объекты окна
    labelX1=Label(frame1, text='x1').pack(side=LEFT)                   #неправильно отображаются
    entryX1=Entry(frame1, width=5); entryX1.pack(side=LEFT)     #и ведут себя в функциях,
    labelY1=Label(frame1, text='y1').pack(side=LEFT)                   #если запакованы в одной строке
    entryY1=Entry(frame1, width=5); entryY1.pack(side=LEFT)     #var=Entry().pack()

    frame2=Frame(newWnd); frame2.pack(side=TOP)                #Пришлось разбивать на
    labelX2=Label(frame2, text='x2').pack(side=LEFT)                #две логических и записывать
    entryX2=Entry(frame2, width=5); entryX2.pack(side=LEFT)  #в одну для читабельности.
    labelY2=Label(frame2, text='y2').pack(side=LEFT)
    entryY2=Entry(frame2, width=5); entryY2.pack(side=LEFT)

    radioButton_var=IntVar()
    radioButton_var.set(0)
    radioButton1=Radiobutton(newWnd, text='Прямоугольник', value=1, variable=radioButton_var, padx=10); radioButton1.pack(anchor=W)
    radioButton2=Radiobutton(newWnd, text='Овал', value=2, variable=radioButton_var, padx=10); radioButton2.pack(anchor=W)

    buttonNewWnd=Button(newWnd, text='Нарисовать', command=drawFigure).pack()

holst = Canvas(root, width=800, height=600, bg="#eeeeee"); holst.pack()

buttonRoot=Button(text='Добавить фигуру', width=30, command=showNewWnd).pack()

root.mainloop()

 

from tkinter import *
 
 
 
root = Tk()
w = root.winfo_screenwidth()
h = root.winfo_screenheight()
 
w1 = w // 2
h1 = h // 2
 
w2 = w1 - 200
h2 = h1 - 200
 
window = root.geometry("500x500+{}+{}".format(w2, h2))
root.title("Овал/Круг")
c = Canvas(root, width = 500, height = 480)
c.pack()
 
def add():
    a = Toplevel()
    a.title("Фигура")
    a.geometry("200x200+{}+{}".format(w1 + 300, h2))
 
    f_top = Frame(a)
    f_bot = Frame(a)
    l1 = Label(f_top, text = "x1")
    l3 = Label(f_top, text = "y1")
    x1 = Entry(f_top, width = 5)
    y1 = Entry(f_top, width = 5)
 
    l2 = Label(f_bot, text = "x2")
    l4 = Label(f_bot, text = "y2")
    x2 = Entry(f_bot, width = 5)
    y2 = Entry(f_bot, width = 5)
 
    var = IntVar()
    var.set(0)
    r1 = Radiobutton(a, text = "Прямоугольник", value = 1, variable = var)
    r2 = Radiobutton(a, text = "Овал", value = 2, variable = var)
 
    def draw():
        x_1 = x1.get()
        x_2 = x2.get()
        y_1 = y1.get()
        y_2 = y2.get()
        if var.get() == 1:
            c.create_rectangle(x_1, y_1, x_2, y_2, outline="black")
        elif var.get() == 2:
            c.create_oval(x_1, y_1, x_2, y_2, outline="black")
 
 
 
    b2 = Button(a, text = "Нарисовать", command = draw)
 
    f_top.pack()
    f_bot.pack()
    l1.pack(side = LEFT)
    x1.pack(side = LEFT)
 
    l2.pack(side = LEFT)
    x2.pack(side = LEFT)
 
    l3.pack(side = LEFT)
    y1.pack(side = LEFT)
 
    l4.pack(side = LEFT)
    y2.pack(side = LEFT)
 
    r1.pack()
    r2.pack()
 
    b2.pack()
 
 
 
b1 = Button(text = "Добавить фигуру", command = add)
b1.pack(side = BOTTOM)
 
 
root.mainloop()

Я только сегодня понял, что если создать объект внутри функции, то обратиться к нему из другой функции или из основной программы нельзя. Посмотрел, как сделали другие, в основном суют функцию рисования внутрь функции создания второго окна, но мне такой способ вообще в голову не пришел, он мне не кажется логичным... Я опять сделал через глобалки, а везде говорят, не делайте глобалки! А как еще можно вызвать атрибуты объекта из другой функции, подскажите!
from tkinter import *
 
def action1():
    global entry1,entry2,entry3,entry4,r_var
    #global r_var
    root2 = Toplevel()
    root2.minsize(width=300, height=10)
    root2.title('параметры')
    frame1 = Frame(root2);frame1.pack()
    frame2 = Frame(root2);frame2.pack()
    lab1 = Label(frame1, text='x1');lab1.pack(side=LEFT)
    entry1 = Entry(frame1,width=3);entry1.pack(side=LEFT)
    lab2 = Label(frame1, text='y1');lab2.pack(side=LEFT)
    entry2 = Entry(frame1,width=3);entry2.pack(side=LEFT)
    lab3 = Label(frame2, text='x2');lab3.pack(side=LEFT)
    entry3 = Entry(frame2,width=3);entry3.pack(side=LEFT)
    lab4 = Label(frame2, text='y2');lab4.pack(side=LEFT)
    entry4 = Entry(frame2,width=3);entry4.pack(side=LEFT)
    r_var = BooleanVar()
    r_var.set(0)
    rbut1 = Radiobutton(root2, text='прямоугольник',variable=r_var, value=0);rbut1.pack()
    rbut2 = Radiobutton(root2, text='овал',variable=r_var, value=1);rbut2.pack()
    but2 = Button(root2, text='нарисовать', command=action2);but2.pack()
 
def action2():
    x1, y1, x2, y2=entry1.get(),entry2.get(),entry3.get(),entry4.get()
    if r_var.get()==0:
        try:
            c.create_rectangle(x1,y1,x2,y2)
        except:
            return
    else:
        try:
            c.create_oval(x1,y1,x2,y2)
        except:
            return
 
root = Tk()
c = Canvas(root, width=300, height=200, bg="white");c.pack()
but1=Button(root,text='Добавить фигуру',command=action1);but1.pack()
 
root.mainloop()