Работа с Tkinter: виджеты, компоновка и события
Основы Tkinter
Эффективное решение для быстрого старта
Создадим окно с кнопкой, которая изменяет текст метки. Это минимальный пример, демонстрирующий создание виджетов, привязку событий и запуск главного цикла.
import tkinter as tk
def change_label():
label.config(text="Текст изменён")
root = tk.Tk()
root.title("Простое окно")
root.geometry("300x200")
label = tk.Label(root, text="Начальный текст", font=("Arial", 14))
label.pack(pady=20)
button = tk.Button(root, text="Нажми меня", command=change_label)
button.pack(pady=10)
root.mainloop()
Python графическая библиотека tkinter (графическая библиотека tkinter в python)
Пояснение:
- tk.Tk() создаёт главное окно.
- tk.Label - статический текст.
- tk.Button - кнопка с вызовом функции command.
- pack() - менеджер геометрии, размещает виджеты сверху вниз.
- mainloop() запускает цикл обработки событий, без него окно не появится.
Типичные ошибки:
- Забытый вызов mainloop() - окно не отображается.
- Указание неверного имени функции в command (без скобок, только имя).
- Использование pack и grid одновременно в одном контейнере - приведёт к ошибке
cannot use geometry manager pack with grid. - Проблемы с локалью (лат.) - если система настроена не на UTF-8, возможны кракозябры. Рекомендуется явно указывать кодировку или использовать латинские символы.
Как расположить виджеты с помощью менеджера grid?
Менеджер grid размещает виджеты в таблице. Укажите строку (row) и столбец (column).
import tkinter as tk
root = tk.Tk()
root.title("Сетка")
label1 = tk.Label(root, text="Метка 1", bg="red", fg="white")
label1.grid(row=0, column=0, padx=5, pady=5)
label2 = tk.Label(root, text="Метка 2", bg="blue", fg="white")
label2.grid(row=0, column=1, padx=5, pady=5)
label3 = tk.Label(root, text="Метка 3", bg="green", fg="white")
label3.grid(row=1, column=0, columnspan=2, sticky="we", padx=5, pady=5)
root.mainloop()
Параметр sticky определяет привязку к сторонам ячейки ("nsew" - растянуть на всю ячейку). columnspan объединяет несколько столбцов.
Ошибка: если не задать sticky, виджет может не растянуться при изменении размера окна. Используйте grid_rowconfigure и grid_columnconfigure для весов.
Как использовать менеджер place для точного позиционирования?
place позволяет задавать абсолютные координаты в пикселях.
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
btn = tk.Button(root, text="Кнопка")
btn.place(x=100, y=50, width=80, height=30)
root.mainloop()
Подходит для фиксированных макетов, но плохо адаптируется к изменению размера окна.
Проблема: при изменении размеров окна виджеты остаются на месте. Решение - использовать относительное позиционирование: relx=0.5, rely=0.5, anchor="center".
Как добавить меню в окно Tkinter?
Создайте объект Menu и привяжите его к окну.
import tkinter as tk
def new_file():
print("Новый файл")
root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar, tearoff=0)
file_menu.add_command(label="Новый", command=new_file)
file_menu.add_separator()
file_menu.add_command(label="Выход", command=root.quit)
menubar.add_cascade(label="Файл", menu=file_menu)
root.mainloop()
Параметр tearoff=0 убирает пунктирную линию отрыва.
Как рисовать на холсте Canvas?
Холст поддерживает фигуры: линии, прямоугольники, овалы, текст.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=300, bg="white")
canvas.pack()
canvas.create_rectangle(50, 50, 150, 150, fill="blue", outline="black")
canvas.create_oval(200, 50, 300, 150, fill="red")
canvas.create_line(10, 10, 390, 290, fill="green", width=3)
root.mainloop()
Координаты задаются как (x1, y1, x2, y2).
Ошибка: забыли обновить холст после изменений? Canvas обновляется автоматически при каждом вызове метода create_*.
Как открыть диалог выбора файла?
Используйте модуль filedialog из Tkinter.
import tkinter as tk
from tkinter import filedialog
def open_file():
file_path = filedialog.askopenfilename(
title="Выберите файл",
filetypes=(("Текстовые файлы", "*.txt"), ("Все файлы", "*.*"))
)
if file_path:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
text.insert("1.0", content)
root = tk.Tk()
text = tk.Text(root)
text.pack(fill="both", expand=True)
btn = tk.Button(root, text="Открыть", command=open_file)
btn.pack()
root.mainloop()
Функция возвращает путь к файлу или пустую строку, если пользователь нажал Отмена.
Как избежать заморозки интерфейса при длительных операциях?
Используйте метод after для планирования задач.
import tkinter as tk
import time
def long_task():
# Имитация долгой работы
for i in range(10):
time.sleep(0.5)
progress_var.set(i+1)
root.update() # принудительное обновление
def start_task():
# Запускаем в отдельном потоке? Нет, используем after для прерывания
root.after(100, long_task) # Неправильно! long_task блокирует цикл
root = tk.Tk()
progress_var = tk.IntVar()
progress = tk.Progressbar(root, variable=progress_var, maximum=10)
progress.pack(pady=20)
btn = tk.Button(root, text="Старт", command=start_task)
btn.pack()
root.mainloop()
Правильный подход - разбить задачу на маленькие шаги и выполнять их через after.
Проблема: прямой вызов time.sleep() в обработчике блокирует главный цикл. Решение - использовать root.after() для планирования следующего шага.
Расширенные примеры использования Tkinter
Пример 1: Простой калькулятор с Grid
import tkinter as tk
def calculate():
try:
result = eval(entry.get())
entry.delete(0, tk.END)
entry.insert(tk.END, str(result))
except Exception as e:
entry.delete(0, tk.END)
entry.insert(tk.END, "Ошибка")
root = tk.Tk()
root.title("Калькулятор")
entry = tk.Entry(root, width=16, font=("Arial", 14), justify="right")
entry.grid(row=0, column=0, columnspan=4, padx=5, pady=5)
buttons = [
'7', '8', '9', '/',
'4', '5', '6', '*',
'1', '2', '3', '-',
'0', '.', '=', '+'
]
row = 1
col = 0
for text in buttons:
if text == '=':
btn = tk.Button(root, text=text, width=5, height=2, command=calculate)
else:
btn = tk.Button(root, text=text, width=5, height=2, command=lambda t=text: entry.insert(tk.END, t))
btn.grid(row=row, column=col, padx=2, pady=2)
col += 1
if col > 3:
col = 0
row += 1
root.mainloop()
Результат: окно калькулятора с кнопками и полем ввода. При нажатии '=' выполняется eval и отображается результат.
Пример 2: Анимация на Canvas с использованием after
import tkinter as tk
def move_ball():
global x, y, dx, dy
canvas.move(ball, dx, dy)
x += dx
y += dy
if x <= 0 or x >= 350:
dx = -dx
if y <= 0 or y >= 250:
dy = -dy
root.after(20, move_ball)
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=300, bg="lightgray")
canvas.pack()
x, y, dx, dy = 50, 50, 2, 3
ball = canvas.create_oval(x, y, x+30, y+30, fill="red")
move_ball()
root.mainloop()
Результат: красный круг движется по холсту, отскакивая от границ. Анимация плавная.
Пример 3: Приложение с вкладками (ttk.Notebook)
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("Вкладки")
notebook = ttk.Notebook(root)
notebook.pack(fill="both", expand=True)
tab1 = ttk.Frame(notebook)
notebook.add(tab1, text="Первая вкладка")
label1 = ttk.Label(tab1, text="Содержимое первой вкладки")
label1.pack(pady=20)
tab2 = ttk.Frame(notebook)
notebook.add(tab2, text="Вторая вкладка")
entry2 = ttk.Entry(tab2)
entry2.pack(pady=20)
root.mainloop()
Результат: окно с двумя вкладками, переключение между ними.
Пример 4: Использование StringVar и trace для реакции на изменение
import tkinter as tk
def on_text_change(*args):
label.config(text="Вы печатаете: " + var.get())
root = tk.Tk()
var = tk.StringVar()
var.trace_add("write", on_text_change)
entry = tk.Entry(root, textvariable=var)
entry.pack(pady=10)
label = tk.Label(root, text="Вы печатаете: ")
label.pack(pady=10)
root.mainloop()
Результат: при каждом вводе символа в поле, метка обновляется.