Скачайте код уроков с GitLab: https://gitlab.com/PythonRu/tkinter-uroki
Изменение иконки курсора
Tkinter позволяет менять внешний вид иконки курсора при наведении на виджет. Это поведение иногда включается по умолчанию, как, например, в случае с виджетом Entry, который показывает курсор текстового выделения.
Следующее приложение демонстрирует, как показывать курсор загрузки, а также курсор со знаком вопроса, который часто используется во вспомогательных меню.
Иконку можно поменять с помощью параметра cursor
. В этом примере используется значение watch
для демонстрации нативной иконки загрузки, а также question_arrow
— для обычной стрелки со знаком вопроса:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Демо иконки курсора")
self.resizable(0, 0)
self.label = tk.Label(self, text="Нажмите для старта")
self.btn_launch = tk.Button(self, text="Старт !",
command=self.perform_action)
self.btn_help = tk.Button(self, text="Помощь",
cursor="question_arrow")
btn_opts = {"side": tk.LEFT, "expand": True, "fill": tk.X,
"ipadx": 30, "padx": 20, "pady": 5}
self.label.pack(pady=10)
self.btn_launch.pack(**btn_opts)
self.btn_help.pack(**btn_opts)
def perform_action(self):
self.btn_launch.config(state=tk.DISABLED)
self.btn_help.config(state=tk.DISABLED)
self.label.config(text="Запуск...")
self.after(3000, self.end_action)
self.config(cursor="watch")
def end_action(self):
self.btn_launch.config(state=tk.NORMAL)
self.btn_help.config(state=tk.NORMAL)
self.label.config(text="Готово!")
self.config(cursor="arrow")
def set_watch_cursor(self, widget):
widget._old_cursor = widget.cget("cursor")
widget.config(cursor="watch")
for w in widget.winfo_children():
self.set_watch_cursor(w)
def restore_cursor(self, widget):
widget.config(cursor=widget.old_cursor)
for w in widget.winfo_children():
self.restore_cursor(w)
if __name__ == "__main__":
app = App()
app.mainloop()
Полный список валидных значений для cursor
( включая те, которые характерны для определенной ОС) можно посмотреть в официальной документации Tcl/TK на сайте https://www.tcl.tk/man/tcl/TkCmd/cursors.htm.
Как работает изменение курсора
Если виджет не определяет параметр cursor
, он берет значение из родительского контейнера. Таким образом можно запросто задать нужную иконку для всех виджетов, определив значение на уровне root
. Это делается с помощью вызова set_watch_cursor()
внутри метода perform_action()
:
def perform_action(self):
self.config(cursor="watch")
# ...
Исключением здесь является кнопка Помощь
, которая явно задает значение question_arrow
для курсора. Это же можно сделать при создании экземпляра виджета:
self.btn_help = tk.Button(self, text="Помощь",
cursor="question_arrow")
Стоит также обратить внимание на то, что если нажать на кнопку Старт
и разместить курсор над кнопкой Помощь
до вызова запланированного метода, то значение будет help
, а не watch
. Это происходит из-за того что, если параметр cursor
виджета задан, то у него более высокая приоритетность по сравнению со значением в родительском контейнере.
Чтобы избежать этого, можно сохранить текущее значение cursor
и поменять его на watch
, вернув позже. Функцию, которая будет выполнять эту операцию, можно вызывать рекурсивно в дочернем виджете, перебирая список winfo_children()
:
def perform_action(self):
self.set_watch_cursor(self)
# ...
def end_action(self):
self.restore_cursor(self)
# ...
def set_watch_cursor(self, widget):
widget._old_cursor = widget.cget("cursor")
widget.config(cursor="watch")
for w in widget.winfo_children():
self.set_watch_cursor(w)
def restore_cursor(self, widget):
widget.config(cursor=widget.old_cursor)
for w in widget.winfo_children():
self.restore_cursor(w)
В этом коде свойство _old_cursor
было добавлено каждому виджету. При использовании такого же подхода важно помнить, что нельзя вызывать restore_cursor()
до set_watch_cursor()
.
Виджет Text
Виджет Text предлагает расширенную функциональность по сравнению с другими классами виджетов. Он отображает несколько строк редактируемого текста, который можно индексировать по строкам и колонкам. Также на них можно ссылаться с помощью тегов, которые определяют измененные внешний вид и поведение.
Следующее приложение демонстрирует базовый пример использования виджета Text, где можно динамически добавлять и удалять выбранный текст:
Помимо виджета Text это приложение содержит три кнопки, которые вызывают методы для очистки всего содержимого, вставки строки «Hello, world!» в месте, где сейчас находится курсор, и вывода выделения, сделанного с помощью мыши или клавиатуры:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Демо виджета Text")
self.resizable(0, 0)
self.text = tk.Text(self, width=50, height=10)
self.btn_clear = tk.Button(self, text="Очистить",
command=self.clear_text)
self.btn_insert = tk.Button(self, text="Вставить",
command=self.insert_text)
self.btn_print = tk.Button(self, text="Печать",
command=self.print_selection)
self.text.pack()
self.btn_clear.pack(side=tk.LEFT, expand=True, pady=10)
self.btn_insert.pack(side=tk.LEFT, expand=True, pady=10)
self.btn_print.pack(side=tk.LEFT, expand=True, pady=10)
def clear_text(self):
self.text.delete("1.0", tk.END)
def insert_text(self):
self.text.insert(tk.INSERT, "Hello, world")
def print_selection(self):
selection = self.text.tag_ranges(tk.SEL)
if selection:
content = self.text.get(*selection)
print(content)
if __name__ == "__main__":
app = App()
app.mainloop()
Как работает текстовый виджет
Изначально виджет Text пустой и имеет ширину на 50 символов, а высоту на 10 строк. Помимо добавления возможности для пользователей вводить любой текст, разберем также методы каждой кнопки, чтобы понимать, как взаимодействовать с виджетом.
Метод delete(start, end)
удаляет содержимое с индексами от start
до end
. Если второй параметр не указан, то удаляются только символы с индексом start
.
В этом примере будем удалять весь текст от индекса 1.0 (нулевая колонка первой строчки) до tk.END
, которая ссылается на последний символ:
def clear_text(self):
self.text.delete("1.0", tk.END)
Метод insert(index, text)
вставляет выбранный текст в положении index
. Вызываем его с помощью индекса INDEX
, который соответствует позиции курсора:
def insert_text(self):
self.text.insert(tk.INSERT, "Hello, world")
Метод tag_ranges(tag)
возвращает кортеж с первым и последним индексами всех диапазонов конкретного tag
. Здесь был использован тег tk.SEL
, который указывает на текущую позицию. Если ничего не было выбрано, то вызов вернет пустой кортеж. Этот метод объединен с get(start, end)
, который возвращает текст в заданном диапазоне:
Поскольку тег SEL
соответствует лишь одному диапазону, его можно с легкостью извлечь с помощью метода get
.
Добавление HTML тегов в виджет Text
В этом разделе разберем, как настраивать поведение последовательности символов с проставленными тегами в виджете Text.
Эти принципы не отличаются от тех, что применяются к обычным виджетам, таким как последовательности событий или параметры, которые рассматривались в прошлых материалах. Основное отличие в том, что нужно работать с текстовыми индексами для определения контента с тегами вместо того, чтобы использовать ссылки на объекты.
Для демонстрации принципов работы с тегами, создадим виджет Text, который симулирует добавление гиперссылок. При нажатии по таким в браузере по умолчанию будет открываться выбранный URL.
Например, если пользователь введет следующий текст, то python.org можно отметить тегом как гиперссылку:
Определим тег «link», который представляет собой кликабельную гиперссылку. Этот тег будет добавляться к текущему выбранному тексту с помощью кнопки, а клик мышью запустит событие для открытия ссылки в браузере:
import tkinter as tk
import webbrowser
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Демо HTML тегов")
self.text = tk.Text(self, width=50, height=10)
self.btn_link = tk.Button(self, text="Добавить ссылку",
command=self.add_hyperlink)
self.text.tag_config("link", foreground="blue", underline=1)
self.text.tag_bind("link", "", self.open_link)
self.text.tag_bind("link", "",
lambda _: self.text.config(cursor="hand2"))
self.text.tag_bind("link", "",
lambda e: self.text.config(cursor=""))
self.text.pack()
self.btn_link.pack(expand=True)
def add_hyperlink(self):
selection = self.text.tag_ranges(tk.SEL)
if selection:
self.text.tag_add("link", *selection)
def open_link(self, event):
position = "@{},{} + 1c".format(event.x, event.y)
index = self.text.index(position)
prevrange = self.text.tag_prevrange("link", index)
url = self.text.get(*prevrange)
webbrowser.open(url)
if __name__ == "__main__":
app = App()
app.mainloop()
Как работает добавление тега
Изначально создается экземпляр тега с помощью настройки цвета и стиля подчеркивания. Добавим событие для открытия ссылок по нажатию и поменяем внешний вид курсора, когда тот находится над текстом с тегом:
def __init__(self):
super().__init__()
self.title("Демо HTML тегов")
self.text = tk.Text(self, width=50, height=10)
self.btn_link = tk.Button(self, text="Добавить ссылку",
command=self.add_hyperlink)
self.text.tag_config("link", foreground="blue", underline=1)
self.text.tag_bind("link", "", self.open_link)
self.text.tag_bind("link", "",
lambda _: self.text.config(cursor="hand2"))
self.text.tag_bind("link", "",
lambda e: self.text.config(cursor=""))
Внутри метода open_link
поменяем положение клика на соответствующую строку и колонку с помощью метода index
класса Text
:
position = "@{},{} + 1c".format(event.x, event.y)
index = self.text.index(position)
prevrange = self.text.tag_prevrange("link", index)
Стоит обратить внимание на то, что положение, соответствующее индексу, по которому был совершен клик, — «@x,y», но оно сдвинут на один символ. Это сделано из-за того, что tag_prevrange
возвращает предшествующий диапазон конкретного индекса. В таком случае он бы не возвращал текущий диапазон при клике по первому символу.
Наконец, получаем текст из диапазона и открываем его с помощью браузера по умолчанию, используя для этого функцию open
из модуля webbrowser
:
url = self.text.get(*prevrange)
webbrowser.open(url)
Поскольку функция webbrowser.open
не проверяет, является ли URL валидным, то приложение можно улучшить, добавив базовую валидацию гиперссылки. Например, можно использовать функцию urlparse
, чтобы убедиться, что у ссылки есть сетевое положение:
Хотя этот подход не является идеальным, он подойдет на первых этапах, чтобы отбрасывать невалидные ссылки.
В целом, можно использовать теги для создания сложных программ, основанных на тексте: например, IDE с подсветкой синтаксиса. На самом деле, IDLE, которая идет в составе Python, основана на Tkinter.