Tkinter

У Java для створення графічних інтерфейсів користувача ми використовували популярний фреймворк JavaFX. Він був не єдиним у своєму роді, адже також для Java існували Swing, AWT, SWT та можливо інші бібліотеки. У Python є також декілька бібліотек для створення GUI, зокрема Kivy, wxPython, PyQT, однак вже вбудованим модулем є Tkinter.

Tcl — окрема динамічна високорівнева мова програмування, що у першу чергу створена для написання застосунків на власному GUI наборі інструментів Tk. Python вбудував часткову підмножину цієї мови та “огорнув” у свої класи під загальним модулем Tkinter, таким чином надаючи можливість розробникам використовувати Tk для простого створення базових графічних інтерфейсів. Tk є досить базовим, однак теж великим інструментом, тому у цьому розділі ми розглянемо лише основи основ.

Віджетами називають загально усі компоненти, з яких складається GUI — це різні кнопки, текстові поля, слайдери, текстові мітки, прапорці тощо. Для підключення Tkinter можна імпортувати всі його компоненти для зручності: from tkinter import *.

Текстова мітка

Для початку слід створити у програмі вікно, яке буде коренем структири віджетів — найверхнім та початковим її компонентом: root = Tk(). Далі нехай додамо текстову мітку, батьком якої буде вікно root, а текст міститиме “Привіт світ”: label = Label(root, text = "Привіт світ!"). Передостаннім важливим компонентом буде “запакувати” мітку у вікно функцією label.pack(): вона автоматично буде додана до вікна, яке у той самий час набуде розміру, достатнього для вміщення мітки. Нарешті, що запустити програму, необхідно запустити основний цикл: root.mainloop(). Такий цикл виконуватиметься до закриття програми та створений для того, щоб оброблювати всі події, які можуть відбуватись у програмі, наприклад, натискання кнопки чи введення тексту.

from tkinter import *

root = Tk()
label = Label(root, text = "Привіт світ!")
label.pack()

root.mainloop()

Кнопки

Змінити розмір та назву вікна можна відповідно функціями: root.title(<назва>) та root.geometry("<розмір_через_x>"). Крім pack() також є ще один менеджер геометрії grid(column=<стопцець>, row=<рядок>), який розташовуватиме компоненти у вказаних клітинках (стовпець та рядок) вікна. Нехай додамо мітку вже відомим способом у клітинку (0, 0), а під нею (на 0, 1) створимо кнопку Button(), і нову функцію on_clicked(), яка буде виконуватись при натисканні кнопки:

from tkinter import *

root = Tk()
root.title("Перша програма")
root.geometry("240x80")

lbl = Label(root, text="Натисни кнопку!")
lbl.grid(column=0, row=0)

def on_clicked():
    lbl.configure(text="Ви натиснули кнопку!")

btn = Button(root, text="Натисни мене", command=on_clicked)
btn.grid(column=0, row=1)

root.mainloop()

Прив’язування функції до дії кнопки відбувається тут одразу при її створенні через command=<назва_функції>. Метод configure() (або config(): вони аналогічні) дозволяє змінити властивості віджета — у даному випадку текст кнопки.

Створене вікно

Текстове поле

Пропоную додати до програми текстове поле між текстом та кнопкою, довжиною у 25 пікселів: Entry(root, width=25). Текст із поля можна отримати функцією get(). Також змінімо колір тексту fg (від слова foreground — “передній план”) на зелений, а колір тла bg (background — “задній план”) на чорний.

from tkinter import *

root = Tk()
root.title("Текст")
root.geometry("240x80")

lbl = Label(root, text="(тут може бути ваша реклама xD)")
lbl.grid(column=0, row=0)

txt = Entry(root, width=25)
txt.grid(column=0, row=1)

def on_clicked():
    lbl.configure(text = txt.get(),
                  fg = "green",
                  bg = "black")
btn = Button(root, text="Встановити текст", command=on_clicked)
btn.grid(column=0, row=2)

root.mainloop()

Авторські права

Більшість матеріалу цього розділу до даної позначки є адаптацією (скороченим переказом) з англомовного джерела GeeksForGeeks. Решта розділів розроблені власноруч на власних знаннях, досвіді та шкільному матеріалі. Приклад калькулятора нижче теж оригінальний.

Базовий калькулятор

Демонстрація роботи

from tkinter import *

root = Tk()
root.title("Калькулятор")

txtfield = Entry(root)
txtfield.grid(column=0, row=0, columnspan=4, sticky="nwse")

def evaluate():
    txt = txtfield.get().replace('×', '*').replace('÷', '/').replace(' mod ', '%')
    txtfield.delete(0, END)
    try:
        txtfield.insert(0, eval(txt))
    except:
        txtfield.insert(0, "Error")

for col, op in enumerate(['+', '-', '×', '÷']):
    Button(root, text=op, command = lambda op=op: txtfield.insert(END, op))\
        .grid(column=col, row=1, sticky="nwse")
Button(root, text='mod', command = lambda: txtfield.insert(END, ' mod '))\
    .grid(column=3, row=2, sticky="nwse")

numbers = [
    ['1', '2', '3'],
    ['4', '5', '6'],
    ['7', '8', '9']
]
for row in range(2, 5):
    for col, num in enumerate(numbers[row-2]):
        Button(root, text=num, command = lambda num=num: txtfield.insert(END, num))\
            .grid(column=col, row=row, sticky="nwse")
Button(root, text='0', command = lambda: txtfield.insert(END, '0'))\
    .grid(column=0, row=5, columnspan=2, sticky="nwse")

Button(root, text='.', command = lambda: txtfield.insert(END, '.'))\
    .grid(column=2, row=5, sticky="nwse")
Button(root, text='C', command = lambda: txtfield.delete(0, END))\
    .grid(column=3, row=3, sticky="nwse")
Button(root, text='Enter', command = evaluate)\
    .grid(column=3, row=4, rowspan=2, sticky="nwse")

for col in range(4): root.columnconfigure(index=col, weight=1)
for row in range(6): root.rowconfigure(index=row, weight=1)

root.mainloop()

Розберімо приклад. columnspan та rowspan у grid() дозволяють вказати, скільки стовпців та рядків займає віджет (наприклад, columnspan=4 вказує, що текстове поле у 4 стовпці). sticky зазначає, у які напрямки (n — north — північ, тощо) слід розтягувати віджет при розтяганні вікна; “nwse” — всі напрямки.

Entry.delete(<початок>, <кінець>) видаляє із текстового поля символи у проміжку індексів. END — вказує кінець поля. Таким чином txtfield.delete(0, END) видаляє всі символи, або очищає поле. Entry.insert(<індекс>, <текст>) вставляє текст за індексом.

eval(<код>) — особлива функція, що дозволяє виконувати вирази на Python у коді; тут вона використовується для обчислення числових виразів. Це небезпечна функція, адже може виконувати будь-який введений код, зокрема викликати функції. Для прикладу вона використана задля простоти програми, однак у справжніх проєктах уникайте її. enumerate(<послідовність>) зручна для використання у циклах, щоб мати одночасно індекс та значення за ним: for <індекс>, <значення> in enumerate(<послідовність>). \ + новий рядок надає можливість зробити так, наче перенесення на новий рядок немає, що дозволяє писати довгі вирази у кілька рядків.

Навіщо у лямбда-функціях параметр дорівнює самому собі? Через особливості присвоєння змінних у Пайтоні за співвикористанням, про які було кілька разів згадано, всі створені у циклі функції, що залежать від ітератора, посилались би на один і той самий об’єкт:

a = []                  # Створимо список.
for i in range(5):      # Додамо в нього 4 лямбда-функції, що повертають `i`.
    a.append(lambda: i) # На момент створення кожної функції `i` різне.
for f in a:             # Однак, якщо викликати кожну лямбду,
    print(f())          # всі однаково повернуть число 4

Трюк, що ми вказуємо значення i на момент створення як значення для параметра лямбди за замовчуванням, копіює його і використовує за виклику без аргументів. columnconfigure(index=<індекс>, weight=<вага>) і rowconfigure(index=<індекс>, weight=<вага>) встановлюють для стопця і рядка відповідно “вагу” — коефіцієнт, з яким розтягуватиметься рядок чи стовпець порівняно з іншими. Якщо встановити для всіх рядків і стовпців однаковий коефіцієнт (наприклад, 1), то всі розтягуватимуться однаково.


Copyleft 🄯 2020–2024 Михайло Стецюк <yaBobJonez@gmail.com>.
Сайт має ліцензію Creative Commons License.
Дякую рідній школі та вчителям!