Курс Tkinter, часть 2

Ссылка на источник

Меню

Для создания иерархического меню в tkinter применяется виджет Menu. Основные параметры Menu:

  • activebackground: цвет активного пункта меню
  • activeborderwidth: толщина границы активного пункта меню
  • activeforeground: цвет текста активного пункта меню
  • background / bg: фоновый цвет
  • bd: толщина границы
  • cursor: курсор указателя мыши при наведении на меню
  • disabledforeground: цвет, когда меню находится в состоянии DISABLED
  • font: шрифт текста
  • foreground / fg: цвет текста
  • tearoff: меню может быть отсоединено от графического окна. В частности, при создании подменю а скриншоте можно увидеть прерывающуюся линию в верху подменю, за которую его можно отсоединить. Однако при значении tearoff=0 подменю не сможет быть отсоединено.

Меню может содержать много элементов, причем эти элементы сами могут представлять меню и содержать другие элементы. В зависимости от того, какой тип элементов мы хотим добавить в меню, будет отличаться метод, используемый для их добавления. В частности, нам доступны следующие методы:

  • add_command(options): добавляет элемент меню через параметр options
  • add_cascade(options): добавляет элемент меню, который в свою очередь может представлять подменю
  • add_separator(): добавляет линию-разграничитель
  • add_radiobutton(options): добавляет в меню переключатель
  • add_checkbutton(options): добавляет в меню флажок

Создадим простейшее меню:

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x150")

main_menu = Menu()
main_menu.add_cascade(label="File")
main_menu.add_cascade(label="Edit")
main_menu.add_cascade(label="View")

root.config(menu=main_menu)
root.mainloop()

Для добавления пунктов меню у объекта Menu вызывается метод add_cascade(). В этот метод передаются параметры пункта меню, в данном случае они представлены текстовой меткой, устанавливаемой через параметр label.

Но просто создать меню – еще недостаточно. Его надо установить для текущего окна с помощью параметра menu в методе config(). В итоге графическое окно будет иметь следующее меню:

Теперь добавим подменю:

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x150")

main_menu = Menu()

file_menu = Menu()
file_menu.add_command(label="New")
file_menu.add_command(label="Save")
file_menu.add_command(label="Open")
file_menu.add_separator()
file_menu.add_command(label="Exit")

main_menu.add_cascade(label="File", menu=file_menu)
main_menu.add_cascade(label="Edit")
main_menu.add_cascade(label="View")

root.config(menu=main_menu)

root.mainloop()

Здесь определяется подменю file_menu, которое добавляется в первый пункт основного меню благодаря установке опции menu=file_menu:

main_menu.add_cascade(label="File", menu=file_menu)

Но обратите внимание на пунктирную линию в подменю, которая совершенно не нужна и непонятно откуда появляется. Чтобы избавиться от этой линии, надо для нужного пункта меню установить параметр tearoff=0:

file_menu = Menu(tearoff=0)

Однако так как подпунктов меню может быть много, чтобы для кажлого не прописывать этот параметр, то проще отключить все это глобально с помощью следующей строки кода

root.option_add("*tearOff", FALSE)

Полный код:

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x150")

root.option_add("*tearOff", FALSE)

main_menu = Menu()

file_menu = Menu()
file_menu.add_command(label="New")
file_menu.add_command(label="Save")
file_menu.add_command(label="Open")
file_menu.add_separator()
file_menu.add_command(label="Exit")

main_menu.add_cascade(label="File", menu=file_menu)
main_menu.add_cascade(label="Edit")
main_menu.add_cascade(label="View")

root.config(menu=main_menu)
root.mainloop()

Подобным образом можно создавать и более глубокие иерархии меню:

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x150")

root.option_add("*tearOff", FALSE)

main_menu = Menu()
file_menu = Menu()
settings_menu = Menu()

settings_menu.add_command(label="Save")
settings_menu.add_command(label="Open")

file_menu.add_cascade(label="Settings", menu=settings_menu)
file_menu.add_separator()
file_menu.add_command(label="Exit")

main_menu.add_cascade(label="File", menu=file_menu)

root.config(menu=main_menu)
root.mainloop()

Взаимодействие с меню

Отличительной особенностью элементов меню является способность реагировать на нажатия пользователя. Для этого у каждого элемента меню можно задать параметр command, который устанавливает ссылку на функцию, выполняемую при нажатии.

from tkinter import *
from tkinter import messagebox

root = Tk()
root.title("METANIT.COM")
root.geometry("250x150")

root.option_add("*tearOff", FALSE)

def edit_click():
messagebox.showinfo("GUI Python", "Нажата опция Edit")

main_menu = Menu()

main_menu.add_cascade(label="File")
main_menu.add_cascade(label="Edit", command=edit_click)
main_menu.add_cascade(label="View")

root.config(menu=main_menu)
root.mainloop()

Notebook. Создание вкладок

Виджет Notebook представляет набор вкладок. Среди параметров виджета следует выделить следующие:

  • width: ширина виджета
  • height: высота виджета
  • cursor: курсор при наведении на виджет
  • padding: отступы от границ виджета до его содержимого
  • style: стиль виджета

Для управления вкладками Notebook предоставляет ряд методов, в частности, для добавления вкладки применяется метод add()

add(child, state, sticky, padding, text, image, compound, underline)

Параметры метода

  • child: добавляемый виджет, для которого собственно и создается вкладка. Обычно это Frame, который затем добавляет другие виджеты
  • state: состояние вкладки. Возможные значения: “normal”, “disabled”, “hidden”
  • sticky: определяет прикрепление виджета к определенной стороне вкладки
  • padding: отступы от границ вкладки до внутреннего содержимого
  • text: заголовок вкладки
  • image: изображение в заголовке вкладке
  • compound: управляет расположением изображения и текста в заголовке вкладки
  • underline: определяет номер подчеркнутого символа в заголовке вкладки

Кроме того, чтобы скрыть временно вкладку, применяется метод hide()

hide(tabId)

В качестве параметра принимает идентификатор вкладки, который по умолчанию представляет числовой индекс вкладки начиная с 0.

Чтобы совсем удалить вкладку, применяется метод forget()

forget(child)

В качестве параметра в метод передается удаляемый виджет.

Рассмотрим простейший пример:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

# создаем набор вкладок
notebook = ttk.Notebook()
notebook.pack(expand=True, fill=BOTH)

# создаем пару фреймвов
frame1 = ttk.Frame(notebook)
frame2 = ttk.Frame(notebook)

frame1.pack(fill=BOTH, expand=True)
frame2.pack(fill=BOTH, expand=True)

# добавляем фреймы в качестве вкладок
notebook.add(frame1, text="Python")
notebook.add(frame2, text="Java")

root.mainloop()

Здесь определяются два фрейма, для которых создаются отдельные вкладки

Добавление изображений

За установку изображения в заголовке вкладки отвечает параметр image метода add. Кроме того, с помощью параметра compound можно задать расположение картинки относительно текста. В частности, параметр compound может принимать следующие значения:

  • top: изображение поверх текста
  • bottom: изображение под текстом
  • left: изображение слева от текста
  • right: изображение справа от текста
  • none: при наличии изображения отображается только изображение
  • text: отображается только текст
  • image: отображается только изображение

Например, отобразим картинку слева от текста:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

# создаем набор вкладок
notebook = ttk.Notebook()
notebook.pack(expand=True, fill=BOTH)

# создаем пару фреймвов
frame1 = ttk.Frame(notebook)
frame2 = ttk.Frame(notebook)

frame1.pack(fill=BOTH, expand=True)
frame2.pack(fill=BOTH, expand=True)

python_logo = PhotoImage(file="./python_mc.png")
java_logo = PhotoImage(file="./java_mc.png")
# добавляем фреймы в качестве вкладок
notebook.add(frame1, text="Python", image=python_logo, compound=LEFT)
notebook.add(frame2, text="Java", image=java_logo, compound=LEFT)

root.mainloop()

Следует отметить, что высота заголовка вкладки устанавливается в соответствии с высотой картинки:

Виджет Text

Создание многострочного текстового поля

Text предназначен для отображения и редактирования многострочного текста. Стоит отметить, что данный виджет доступен только в основном пакете tkinter, в пакете tkinter.ttk аналога нет.

Основные параметры конструктора Text:

  • bd / borderwidth: толщина границы
  • bg/background: фоновый цвет
  • fg/foreground: цвет текста
  • font: шрифт текста, например, font="Arial 14" – шрифт Arial высотой 14px, или font=("Verdana", 13, "bold") – шрифт Verdana высотой 13px с выделением жирным
  • height: высота в строках
  • padx: отступ от границ кнопки до ее текста справа и слева
  • pady: отступ от границ кнопки до ее текста сверху и снизу
  • relief: определяет тип границы, может принимать значения SUNKEN, RAISED, GROOVE, RIDGE
  • state: устанавливает состояние кнопки, может принимать значения DISABLED, ACTIVE, NORMAL (по умолчанию)
  • width: ширина в символах
  • wrap: указывает, каким образом переносить текст, если он не вмещается в границы виджета

Простейший Text:

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

editor = Text()
editor.pack(fill=BOTH, expand=1)

root.mainloop()

Перенос текста

Иногда предложения в текстовом поле могут быть очень большими, что могут не помещаться в отведенное для них пространство виджета. В этом случае большое значение имеет стратегия переноса, которая устанавливается с помощью параметра wrap. Этот параметр может принимать следующие параметры:

  • none: переносы отстуствуют, но можно сделать горизонтальную прокрутку
  • char: переносы осуществляются по символам
  • word: переносы осуществляются по словам

Сравнение:

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

char_editor = Text(height=5, wrap="char")
char_editor.pack(anchor=N, fill=X)

word_editor = Text(height=5, wrap="word")
word_editor.pack(anchor=S, fill=X)

root.mainloop()

Прокрутка текста

Используя Scrollbar, можно добавить в Text прокрутку текста:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")
root.grid_columnconfigure(0, weight = 1)
root.grid_rowconfigure(0, weight = 1)

editor = Text(wrap = "none")
editor.grid(column = 0, row = 0, sticky = NSEW)

ys = ttk.Scrollbar(orient = "vertical", command = editor.yview)
ys.grid(column = 1, row = 0, sticky = NS)
xs = ttk.Scrollbar(orient = "horizontal", command = editor.xview)
xs.grid(column = 0, row = 1, sticky = EW)

editor["yscrollcommand"] = ys.set
editor["xscrollcommand"] = xs.set

root.mainloop()

Здесь для виджета определяются две полосы прокрутки – вертикальная и горизонтальная, соответственно, для каждой определяется свой элемент Scrollbar. Один (ys) имеет вертикальную ориентацию, а второй (xs) – горизонтальную. А у Text устанавливаются команды yscrollcommand и xscrollcommand с помощью соответствующих скроллбаров.

Стоит отметить, что поскольку создание прокрутки для виджета Text является довольно распространенной задачей, то в Tkinter также по умолчанию есть аналог виджета Text с готовой вертикальной прокруткой – ScrolledText (в пакете tkinter.scrolledtext):

from tkinter import *
from tkinter.scrolledtext import ScrolledText

root = Tk()
root.title("METANIT.COM")
root.geometry("250x150")

st = ScrolledText(root, width=50, height=10)
st.pack(fill=BOTH, side=LEFT, expand=True)

root.mainloop()

Основные операции с виджетом Text

Добавление текста

Для добавления текста применяется метод insert():

insert(index, chars)

Первый параметр представляет позицию вставки в формате “line.column” – сначала идет номер строки, а затем номер символа. Второй параметр – собственно вставляемый текст. Например, вставка в начало:

editor.insert("1.0", "Hello")

Для вставки в конец для позиции передается значение END:

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

editor = Text()
editor.pack(fill=BOTH, expand=1)

editor.insert("1.0", "Hello World") # вставка в начало
editor.insert(END, "\nBye World") # вставка в конец
root.mainloop()

Получение текста

Для получения введенного текста применяется метод get():

get(start, end)

Параметр start указывает на начальный символ, а end – на конечный символ, текст между которыми надо получить. Оба параметра в формате “line.colunm”, где line – номер строки, а “column” – номер символа. Для указания последнего символа применяется константа END:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("300x200")

editor = Text(height=5)
editor.pack(anchor=N, fill=X)

label=ttk.Label()
label.pack(anchor=N, fill=BOTH)

def get_text():
label["text"] = editor.get("1.0", "end")

button = ttk.Button(text="Click", command=get_text)
button.pack(side=BOTTOM)

root.mainloop()

В данном случае по нажатию на кнопку срабатывает функция get_text(), которая получает текст и передается его для отображения в метку label:

Удаление текста

Для удаления текста применяется метод delete()

delete(start, end)

Параметр start указывает на начальный символ, а end – на конечный символ, текст между которыми надо удалить. Оба параметра в формате “line.colunm”, где line – номер строки, а “column” – номер символа. Для указания последнего символа применяется константа END. Например, определим кнопку, которая будет удалять весь текст из виджета:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("300x200")

editor = Text(height=10)
editor.pack(anchor=N, fill=BOTH)


def delete_text():
editor.delete("1.0", END)

button = ttk.Button(text="Clear", command=delete_text)
button.pack(side=BOTTOM)

root.mainloop()

Замена текста

Для замены текста применяется метод replace():

replace(start, end, chars)

Параметр start указывает на начальный символ, а end – на конечный символ, текст между которыми надо заменить. Оба параметра в формате “line.colunm”, где line – номер строки, а “column” – номер символа. Для указания последнего символа применяется константа END. Последний параметр – chars – строка, на которую надо заменить. Например, замена первых четырех символов на строку “дама”:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("300x200")

editor = Text(height=10)
editor.pack(anchor=N, fill=BOTH)
editor.insert("1.0", "мама мыла раму")


def edit_text():
editor.replace("1.0", "1.4", "дама")

button = ttk.Button(text="Replace", command=edit_text)
button.pack(side=BOTTOM)

root.mainloop()

Повтор и отмена операций

Методы edit_undo() и edit_redo() позволяют соответственно отменить и повторить операцию (добавление, изменение, удаление текста). Данные методы применяются, если в виджете Text параметр undo равен True. Стоит отметить, что данные методы оперируют своим стеком операций, в котором сохраняются данные операций. Однако если стек для соответствующего метода пуст, то вызов метода вызывает исключение. Простейший пример, где по нажатию на кнопку вызывается отмена или возврат операции:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")
root.grid_columnconfigure(0, weight = 1)
root.grid_columnconfigure(1, weight = 1)
root.grid_rowconfigure(0, weight = 1)

editor = Text(undo=True)
editor.grid(column = 0, columnspan=2, row = 0, sticky = NSEW)

def undo():
editor.edit_undo()

def redo():
editor.edit_redo()

redo_button = ttk.Button(text="Undo", command=undo)
redo_button.grid(column=0, row=1)
clear_button = ttk.Button(text="Redo", command=redo)
clear_button.grid(column=1, row=1)

root.mainloop()

Выделение текста

Для управления выделением текста виджет Text обладает следующими методами:

  • selection_get(): возвращает выделенный фрагмент
  • selection_clear(): снимает выделение

Применим данные методы:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")


def get_selection():
label["text"]=editor.selection_get()


def clear_selection():
editor.selection_clear()

editor = Text(height=5)
editor.pack(fill=X)

label = ttk.Label()
label.pack(anchor=NW)

get_button = ttk.Button(text="Get selection", command=get_selection)
get_button.pack(side=LEFT)
clear_button = ttk.Button(text="Clear", command=clear_selection)
clear_button.pack(side=RIGHT)

root.mainloop()

В данном случае по нажатию на кнопку get_button срабатывает функция get_selection, которая передает в метку label выделенный текст. При нажатии на кнопку clear_button срабатывает функция clear_selection, которая снимает выделение.

События

Достаточно часто встречает необходимость обработки ввода текста. Для виджета Text определено событие <<Modified>>, которое срабатывает при изменении текста в текстовом поле. Однако оно срабатывает один раз. И в этом случае мы можем обработать стандартные события клавиатуры. Например, событие освобождения клавиши <KeyRelease>:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")


def on_modified(event):
label["text"]=editor.get("1.0", END)

editor = Text(height=8)
editor.pack(fill=X)
editor.bind("<KeyRelease>", on_modified)

label = ttk.Label()
label.pack(anchor=NW)

root.mainloop()

В данном случае при освобождении клавиши будет срабатывать функция on_modified, в которой метке label передается весь введенный текст:

Другую распространенную задачу представляет динамическое получение выделенного текста. В этом случае мы можем обработать событие <<Selection>>. Например, при выделении текста выведем выделенный фрагмент в метку Label:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")


def on_modified(event):
label["text"]=editor.selection_get()

editor = Text(height=8)
editor.pack(fill=X)
editor.bind("<<Selection>>", on_modified)

label = ttk.Label()
label.pack(anchor=NW)

root.mainloop()

Стилизация и добавление виджетов в Text

Добавление тегов

Теги позволяют определить форматирование. Тег добавляется с помощью метода add_tag() класса Text:

tag_add(tagName, index1, index2)

Первый параметр устанавливает имя тега, второй параметр – index1 указывает на начальный символ, с которого начинает применяться тег. Дополнительно (но необязательно) можно указать третий параметр, который устанавливает конечный символ, к которому применяется тег.

Для прикрепления тега к определенному тексту также можно использовать метод insert, который добавляет текст, и в качестве второго параметра передать тег или набор тегов, которые будут применяться к добавляемому тексту:

insert(index, text, tagName)
insert(index, text, (tagName1, tagName2,...tagNameN))

С помощью метода tag_configure() для тега можно сконфигурировать стили.

tag_configure(имя_тега, стили)

Стили представляют параметры background, bgstipple, borderwidth, elide, fgstipple, font, foreground, justify, lmargin1, lmargin2, offset, overstrike, relief, rmargin, spacing1, spacing2, spacing3, tabs, tabstyle, underline и wrap, которым передаются некоторые значения.

Посмотрим на примере:

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

editor = Text(wrap = "none")
editor.pack(expand=1, fill=BOTH)
editor.insert("1.0","Hello ")
# создаем тег highlightline и прикрепляем его к символам 1.0 до 1.2
editor.tag_add("highlightline", "1.0", "1.2")
# добавляем текст, к которому применяется тег highlightline
editor.insert("end","World", "highlightline")
editor.insert("end","\nHello All!")
# устанавливаем стили тега highlightline
editor.tag_configure("highlightline", background="#ccc", foreground="red", font="TkFixedFont", relief="raised")

root.mainloop()

Здесь создается тег “highlightline”, который прикрепляется сначала по 2-й символ в первой строке. Далее добавляется текст “World”, к которму применяется данный тег. В конце конфигурируем тег, задавая его стилевые параметры:

Если в процессе работы программы тег стал не нужен, его можно удалить. Метод remove_tag() удаляет тег с определенных символов:

editor.tag_remove("highlightline", "1.0", "1.2")

В данном случае удаляем тег “highlightline” с символов с 0 по 2-й в первой строке.

Также можно вообще удалить тег со всех символов, к которым он применяется:

editor.tag_delete("highlightline")

Добавление изображений и других виджетов

Виджет Text позволяет добавление изображений и других виджетов.

Для добавления изображений применяется метод image_create:

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

editor = Text()
editor.pack(expand=1, fill=BOTH)

python_img = PhotoImage(file="python_sm.png")
editor.image_create("1.0", image=python_img)

root.mainloop()

В метод image_create в качестве первого параметра передается позиция вставки изображения. В качестве второго параметра – image указывается файл изображения:

Аналогично можно добавлять другие виджеты в Text с помощью метода window_create()

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

editor = Text()
editor.pack(expand=1, fill=BOTH)

def click():
editor.insert("2.0", "Click\n")

btn = ttk.Button(editor, text="Click", command=click)
editor.window_create("1.0", window=btn)
root.mainloop()

Первый параметр метода window_create также позиция создания виджета, а второй параметр – window указывает на добавляемый виджет, в данном случае это кнопка, на которую также можно нажимать и также можно обрабатывать ее нажатия

Виджет Treeview. Создание таблиц и деревьев

Управление данными в Treeview

Виджет Treeview предназначен для отображения иерархических данных, причем как в виде дерева, так и в виде таблицы. Среди параметров Treeview следует отметить следующие:

  • columns: столбцы таблицы в виде строки или списка/кортежа строк
  • displaycolumns: отображаемые столбцы таблицы
  • cursor: курсор при наведении на виджет
  • height: высота виджета
  • padding: отступы от границ виджета до содержимого
  • selectmode: режим выбора элементов в виджете
  • show: формат отображения данных. Может принимать одно из следующих значений:
    • tree: отображает столбец #0
    • heading: отображает строку с заголовками
    • tree headings: отображает столбец #0 и строку с заголовками
    • "": не отображает ни столбец #0, ни строку с заголовками

Управление данными

Добавление элементов

Для добавления данных применяется метод insert():

insert: (parent, index, iid, values) -> str

Этот метод создает новый элемент и возвращает его идентификатор, который по умолчанию представляет строку наподобие “IOO1”. Основные параметры метода:

  • parent: представляет идентификатор родительского элемента, в который добавляется элемент. Если создается элемент верхнего уровня, для которого не существует никакого родительского элемента, как, например, в случае с добавлением строки в таблицу, то передается пустая строка.
  • index: указывает индекс для вставки элемента. Если элемент добавляется в конец, то используется значение END или "end", либо указывается число, которое равно количеству элементов или больше его. Если элемент добавляется в самое начало, то указывается 0 или число меньше нуля.
  • iid: если указан данный параметр, то его значение будет использоваться в качестве идентификатора элемента. При этом подобный в виджете не должно быть элемента с подобным идентификатором, иначе генерируется новый идентификатор, как в обшем случае
  • values: список или кортеж значений, которые и составляют добавляемый элемент

Удаление элементов

Для удаления данных применяется метод delete():

delete(items)

В качестве параметра метод принимает удаляемые данные

Перемещение элементов

Для перемещения элемента на другую позицию применяется метод move():

move(item, parent, index)

Этот метод создает новый элемент и возвращает его идентификатор, который по умолчанию представляет строку наподобие “IOO1”. Основные параметры метода:

  • item: идентификатор элемента, который надо переместить.
  • parent: представляет родительский элемент перемещаемого элемента .
  • index: индекс, на который перемещается элемент

Получение элементов

Для получения элементов применяется метод get_children():

get_children(item)

Он принимает идентификатор элемента, дочерние элементы которого надо получить, и возвращает набор идентификаторов полученных элементов:

for k in treeview.get_children(""): print(k)

В данном случае k представляет идентификатор элемент.

В качестве параметра в get_children() передается элемент в Treeview, дочерние элементы которого мы хотим получить. Если надо получить элементы верхнего уровня (например, строки таблицы), то передается пустая строка.

Конкретный элемент по ключу с помощью метода item(), в который передается идентификатор элемента:

for k in treeview.get_children(""): 
print(treeview.item(k))

в данном случае treeview.item(k) возвратит набор значений элемента в Treeview (например, всю строку).

Если нам надо получить не весь набор значений, а только одно значение (отдельную ячейку строки), то применяется метод set(), в который передается идентификатор элемента и номер столбца:

for k in treeview.get_children(""): 
print(treeview.set(k, 0))

В данном случае получаем значение первой ячейки строки (при табличном отображении).

Изменение значений

Если надо изменить один столбец, то применяется метод set()

set(item, column, value)
  • item: идентификатор элемента, который надо изменить.
  • column: индекс элемента в кортеже (столбца в строке), который надо изменить.
  • value: новое значение

например:

treeview.set("I003", 0, "Admin")

Здесь значение в первом столбце элемента с id=I003 меняется на строку “Admin”

Если надо изменить вообще весь элемент со всеми его значениями, то применяется метод item()

item(item, values)
  • tem: идентификатор элемента, который надо изменить.
  • values: кортеж с новыми значениями

например:

treeview.item("I003", values=("Tim", 34, "tim@email.com"))

Здесь элемент с id=I003 в качестве значений принимает кортеж ("Tim", 34, "tim@email.com")

В следующих статьях рассмотрим применение этих методов.

Создание таблиц

Для отображения данных в виде таблицы параметру show предпочтительно передать значение "headings" (если надо отображать заголовки), либо " " (для таблицы без заголовков). Определим небольшую таблицу с тремя столбцами:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

# определяем данные для отображения
people = [("Tom", 38, "tom@email.com"), ("Bob", 42, "bob@email.com"), ("Sam", 28, "sam@email.com")]

# определяем столбцы
columns = ("name", "age", "email")

tree = ttk.Treeview(columns=columns, show="headings")
tree.pack(fill=BOTH, expand=1)

# определяем заголовки
tree.heading("name", text="Имя")
tree.heading("age", text="Возраст")
tree.heading("email", text="Email")

# добавляем данные
for person in people:
tree.insert("", END, values=person)

root.mainloop()

Здесь данные, которые будут отображаться в таблице, определены в виде списка people, который хранит набор кортежей. Каждый кортеж состоит из трех элементов. Условно будем считать, что первый элемент кортежа представляет имя пользователя, второй – возраст, а третий – электронный адрес. И эти данные нам надо отобразить в таблице:

people = [("Tom", 38, "tom@email.com"), ("Bob", 42, "bob@email.com"), ("Sam", 28, "sam@email.com")]

Для отображения этих данных определяем три столбца: name, age и email в виде кортежа и передаем их параметру columns:

columns = ("name", "age", "email")
tree = ttk.Treeview(columns=columns, show="headings")

Далее нам надо настроить заголовки столбца с помощью метода heading() класса Treeview (по умолчанию столбцы не имеют никаких заголовков). Данный метод принимает ряд параметров:

tree.heading("name", text="Имя")

Первый параметр указывает на имя столбца. В примере выше определяем также параметр text, который определяет текст заголовка

И последний момент – добавляем сами данные в таблицу с помощью метода insert() класса Treeview

tree.insert("", END, values=person)

Первый параметр – пустая строка “” указывает, что элемент добавляется как элемент верхнего уровня (то есть у него нет родительского элемента). Значение END указывает, что элемент добавляется в конец набора. И параметр values в качестве добавляемых данных устанавливает кортеж person.

В итоге мы получим следующую таблицу:

Настройка столбца

Вполне возможно, что изначальные настройки столбцов нас не устроят. Например, текст заголовка располагается по умолчанию по центру, а данные столбца выравниваются по левому краю. Кроме того, каждый столбец имеет некоторую начальную ширину, в следствие чего ширина виджета может оказаться больше ширины окна. Либо мы захотим как-то иначе настроить вид столбца.

Прежде всего мы можем настроить заголовки столбца с помощью метода heading():

heading(column, text, image, anchor, command)

Параметры метода:

  • column: имя настраиваемого столбца
  • text: текст заголовка
  • image: картинка для заголовка
  • anchor: устанавливает выравнивание заголовка по определенному краю. Может принимать значения n, e, s, w, ne, nw, se, sw, c
  • command: функция, выполняемая при нажатии на заголовок

Для настройки столбца в целом применяется метод column():

column(column, width, minwidth, stretch, anchor)

Параметры метода:

  • column: индекс настраиваемого столбца в формате “# номер_столбца”
  • width: ширина столбца
  • minwidth: минимальная ширина
  • anchor: устанавливает выравнивание заголовка по определенному краю. Может принимать значения n, e, s, w, ne, nw, se, sw, c
  • stretch: указывает, будет ли столбец растягиваться при растяжении контейнера. Если будет, то значение True, иначе значение False

Применим некоторые из этих параметров:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

# определяем данные для отображения
people = [("Tom", 38, "tom@email.com"), ("Bob", 42, "bob@email.com"), ("Sam", 28, "sam@email.com")]

# определяем столбцы
columns = ("name", "age", "email")

tree = ttk.Treeview(columns=columns, show="headings")
tree.pack(fill=BOTH, expand=1)

# определяем заголовки с выпавниваем по левому краю
tree.heading("name", text="Имя", anchor=W)
tree.heading("age", text="Возраст", anchor=W)
tree.heading("email", text="Email", anchor=W)

# настраиваем столбцы
tree.column("#1", stretch=NO, width=70)
tree.column("#2", stretch=NO, width=60)
tree.column("#3", stretch=NO, width=100)

# добавляем данные
for person in people:
tree.insert("", END, values=person)

root.mainloop()

В данном случае для заголовков устанавливаем выравнивание по левому краю. Для столбцов запрещаем растяжение и устанавливаем ширину.

При добавлении изображения оно помещается в правой части. Например, установка изображения для третьего столбца:

# предполагается, что в папке приложения располагается файл email_icon_micro.png
email_icon = PhotoImage(file="./email_icon_micro.png")
tree.heading("email", text="Email", anchor=W, image=email_icon)

Добавление к Treeview прокрутки

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")
root.rowconfigure(index=0, weight=1)
root.columnconfigure(index=0, weight=1)

# определяем данные для отображения
people = [
("Tom", 38, "tom@email.com"), ("Bob", 42, "bob@email.com"), ("Sam", 28, "sam@email.com"),
("Alice", 33, "alice@email.com"), ("Kate", 21, "kate@email.com"), ("Ann", 24, "ann@email.com"),
("Mike", 34, "mike@email.com"), ("Alex", 52, "alex@email.com"), ("Jess", 28, "jess@email.com"),
]

# определяем столбцы
columns = ("name", "age", "email")

tree = ttk.Treeview(columns=columns, show="headings")
tree.grid(row=0, column=0, sticky="nsew")

# определяем заголовки
tree.heading("name", text="Имя", anchor=W)
tree.heading("age", text="Возраст", anchor=W)
tree.heading("email", text="Email", anchor=W)

tree.column("#1", stretch=NO, width=70)
tree.column("#2", stretch=NO, width=60)
tree.column("#3", stretch=NO, width=100)

# добавляем данные
for person in people:
tree.insert("", END, values=person)

# добавляем вертикальную прокрутку
scrollbar = ttk.Scrollbar(orient=VERTICAL, command=tree.yview)
tree.configure(yscroll=scrollbar.set)
scrollbar.grid(row=0, column=1, sticky="ns")

root.mainloop()

Нажатие на заголовок столбца и сортировка

С помощью параметра command метода heading() можно привязать к заголовку некоторую функцию, которая будет вызываться по нажатию на заголовок.

treeview.heading(имя_заголовка, command=функция)

Рассмотрим на примере сортировки:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

# определяем данные для отображения
people = [("Tom", 38, "tom@email.com"), ("Bob", 42, "bob@email.com"), ("Sam", 28, "sam@email.com")]

# определяем столбцы
columns = ("name", "age", "email")

tree = ttk.Treeview(columns=columns, show="headings")
tree.pack(expand=1, fill=BOTH)

def sort(col, reverse):
# получаем все значения столбцов в виде отдельного списка
l = [(tree.set(k, col), k) for k in tree.get_children("")]
# сортируем список
l.sort(reverse=reverse)
# переупорядочиваем значения в отсортированном порядке
for index, (_, k) in enumerate(l):
tree.move(k, "", index)
# в следующий раз выполняем сортировку в обратном порядке
tree.heading(col, command=lambda: sort(col, not reverse))

# определяем заголовки
tree.heading("name", text="Имя", anchor=W, command=lambda: sort(0, False))
tree.heading("age", text="Возраст", anchor=W, command=lambda: sort(1, False))
tree.heading("email", text="Email", anchor=W, command=lambda: sort(2, False))

tree.column("#1", stretch=NO, width=70)
tree.column("#2", stretch=NO, width=60)
tree.column("#3", stretch=NO, width=100)

# добавляем данные
for person in people:
tree.insert("", END, values=person)

root.mainloop()

Как и в предыдущих примерах, виджет Treeview использует список кортежей people и представляет таблицу с тремя столбцами.

Для сортировки определена функция sort(), которая принимает два параметра: col(номер столбца, по которому идет сортировка) и reverse (направление сортировки – по возрастанию или убыванию)

def sort(col, reverse):

Рассмотрим действие функции по этапно:

  1. При добавлениии элементов в Treeview каждому из них присваивается идентификатор. Используя идентификатор, можно получить все значения элемента в Treeview. Это необходимо, чтобы отсортировать элементы:
l = [(tree.set(k, col), k) for k in tree.get_children("")]

Здесь вначале пробегаемся по всем элементам в Treeview с помощью метода tree.get_children(""). В метод передается пустая строка “”, поскольку мы хотим получить элементы верхнего уровня (по сути строки таблицы). Соответственно переменная k здесь будет представлять идентификатор элемента

2- Для каждого элемента с помощью метода set получаем с помощью идентификатора значение столбца строки:

tree.set(k, col)

Например, если col=0 (то есть сортировка идет по имени, то вызов tree.set(k, col) возвращает имя (“Tom”, “Bob”, “Sam”).

3- Полученное значение помещаем в кортеж:

(tree.set(k, col), k)

4- Из кортежей формируется список:

l = [(tree.set(k, col), k) for k in tree.get_children("")]

То есть если, к примеру, сортировка идет по имени, то на выходе переменная l будет представлять список кортежей типа [("Tom", "I001"), ("Bob", "I002"), ("Sam", "I003")]

5- Затем сортируем список с помощью встроенной метода sort():

l.sort(reverse=reverse)

В методе указываем направление сортировки с помощью параметра reverse

Список отсортирован, но на отображение данных в таблице это пока никак не повлияло. Нам надо переупорядочить строки, привести их в соответствие с отсортированным списком l. Для этого переставляем их с помощью метода move():

for index,  (_, k) in enumerate(l):
tree.move(k, "", index)

6- Сначала с помощью функции enumerate() из списка l получаем набор объектов, который состоит из индекса и собственно данных. При переборе этого набора индекс получаем в переменную index, а набор данных в кортеж (_, k) – первый элемент кортежа представляет имя, но здесь оно нам уже не нужно, оно использовалось на предыдущем шаге для сортировки. А второй элемент кортежа – идентификатор строки.В цикле с помощью вызова tree.move(k, "", index) перемещаем элемент с идентификатором k, который является элементов верхнего уровня (второй аргумент – “”) на позицию с индексом index.

7- На финальной стадии переустанавливаем определение заголовка:

tree.heading(col, command=lambda: sort(col, not reverse))

Параметр command в качестве выполняемой функции получает лямбда-выражение, которое вызывает функцию sort. При этом функции передается противоположное направление сортировки. Благодаря этому при следующем нажатии на заголовок сортировка будет идти в противоположном направлении.

Выделение строк таблицы

Для работы с выделенными строками в Treeview определен ряд методов:

  • selection(): возвращает идентификаторы выделенных строк в виде кортежа
  • selection_add(items): выделяет строки с идентификаторами, которые передаются в качестве параметра
  • selection_remove(items): снимает выделение строк с идентификаторами, которые передаются в качестве параметра
  • selection_set(items): снимает выделение с ранее выделенных строк и выделяет строки с идентификаторами, которые передаются в качестве параметра

Обработка события выделения

Для обработки выделения строк у Treeview применяется событие <<TreeviewSelect>>

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

# определяем данные для отображения
people = [("Tom", 38, "tom@email.com"), ("Bob", 42, "bob@email.com"), ("Sam", 28, "sam@email.com")]

label = ttk.Label()
label.pack(anchor=N, fill=X)
# определяем столбцы
columns = ("name", "age", "email")
tree = ttk.Treeview(columns=columns, show="headings")
tree.pack(expand=1, fill=BOTH)

# определяем заголовки
tree.heading("name", text="Имя", anchor=W)
tree.heading("age", text="Возраст", anchor=W)
tree.heading("email", text="Email", anchor=W)

tree.column("#1", stretch=NO, width=70)
tree.column("#2", stretch=NO, width=60)
tree.column("#3", stretch=NO, width=100)

# добавляем данные
for person in people:
tree.insert("", END, values=person)

def item_selected(event):
selected_people = ""
for selected_item in tree.selection():
item = tree.item(selected_item)
person = item["values"]
selected_people = f"{selected_people}{person}\n"
label["text"]=selected_people

tree.bind("<<TreeviewSelect>>", item_selected)

root.mainloop()

Здесь с помощью метода bind() устанавливаем для события <<TreeviewSelect>> функцию-обработчик item_selected. В этой функции получаем все идентификаторы выделенных строк с помощью метода tree.selection()

for selected_item in tree.selection()

Используя полученный идентификатор, получаем выделенный элемент с помощью метода tree.item

item = tree.item(selected_item)

Для получения самих значений обращаемся к атрибуту values:

person = item["values"]

Склеиваем их в строку selected_people и отображаем ее в метке label.

Режим выделения

По умолчанию в Treeview можно выделить только один элемент (одну строку). За установку режима выделения в Treeview отвечает параметр selectionmode, который может принимать следующие значения:

  • extended: позволяет выбрать несколько строк
  • browse: позволяет выбрать только одну строку
  • none: выделение строк не доступно

Например, изменим код Treeview, установив режим “extended”:

tree = ttk.Treeview(columns=columns, show="headings", selectmode="extended")

И теперь можно выделять несколько строк:

Создание дерева

Для определения дерева параметру show виджета Treeview передается значение tree (дерево без заголовка) или tree headings (дерево с заголовком).

Определим простейшее дерево:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

# создаем дерево
tree = ttk.Treeview(show="tree")
tree.pack(expand=1, fill=BOTH)

# добавляем данные
tree.insert("", END, iid=1, text="Административный отдел")
tree.insert("", END, iid=2, text="IT-отдел")
tree.insert("", END, iid=3, text="Отдел продаж")

root.mainloop()

Здесь данные для отображения представлены условно представлены списком отделов некоторого предприятия. Каждый отдел добавляется как элемент верхнего уровня, поэтому в методе tree.insert в качестве первого аргумента указывается пустая строка “”. Также устанавливаем для каждого добавляемого элемента параметр text – название отдела и его идентификатор – параметр iid. Конечно, мы могли бы положиться на tkinter, который установил бы идентификаторы автоматически. Однако ручная установка идентификаторов потом упростить добавление в них вложенныхи элементов.

В итоге получится следующее дерево:

Однако визуально пока никакого дерева по сути нет, а сами данные не являются иерархическими. Но теперь добавим в некоторые отделы сотрудников:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

tree = ttk.Treeview(show="tree")
tree.pack(expand=1, fill=BOTH)

# добавляем отделы
tree.insert("", END, iid=1, text="Административный отдел", open=True)
tree.insert("", END, iid=2, text="IT-отдел")
tree.insert("", END, iid=3, text="Отдел продаж")

# добавим сотрудников отдела
tree.insert(1, index=END, text="Tom")
tree.insert(2, index=END, text="Bob")
tree.insert(2, index=END, text="Sam")

root.mainloop()

При добавлении каждого сотрудника указываем в качестве первого параметра идентификатор элемента-отдела.

tree.insert(2, index=END, text="Bob")

Также устанавливаем текстовую метку элемента – параметр text – он представляет имя условного сотрудника.

По умолчанию все элементы, которые содержат вложенные подэлементы, закрыты. Чтобы их отрыть по умолчанию, у элемента для параметра open передается значение True (по умолчанию равно False):

tree.insert("", END, iid=1, text="Административный отдел", open=True)

Установка заголовка

Если в Treeview параметр show имеет значение “tree headings” (это значение по умолчанию), то мы можем также установить заголовок:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

tree = ttk.Treeview()
# установка заголовка
tree.heading("#0", text="Отделы", anchor=NW)
tree.pack(expand=1, fill=BOTH)

tree.insert("", END, iid=1, text="Административный отдел", open=True)
tree.insert("", END, iid=2, text="IT-отдел")
tree.insert("", END, iid=3, text="Отдел продаж")

tree.insert(1, index=END, text="Tom")
tree.insert(2, index=END, text="Bob")
tree.insert(2, index=END, text="Sam")

root.mainloop()

Окна

Создание окон

По умолчанию приложение Tkinter имеет одно главное окно, которое представляет класс tkinter.Tk. Запуск приложение приводит к запуску главного окно, в рамках которого помещаются все виджеты. Закрытие главного окна приводит к завершению работы приложения. Однако в рамках главного окна также можно запускать вторичные, неглавные окна. Например, октроем новое окно по нажатию на кнопку:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def click():
window = Tk()
window.title("Новое окно")
window.geometry("250x200")

button = ttk.Button(text="Создать окно", command=click)
button.pack(anchor=CENTER, expand=1)

root.mainloop()

Здесь по нажатию на кнопку создается новый объект window, у него устанавливается заголовок и размеры.

Стоит отметить, что приложение завершит работу, когда будут закрыты все его окна.

Как и главное окно, вторичные окна могут иметь виджеты. Например, определим на новом окне метку:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def click():
window = Tk()
window.title("Новое окно")
window.geometry("250x200")
label=ttk.Label(window, text="Принципиально новое окно")
label.pack(anchor=CENTER, expand=1)

button = ttk.Button(text="Создать окно", command=click)
button.pack(anchor=CENTER, expand=1)

root.mainloop()

Единственное не надо забывать у добавляемых виджетов устанавливать окно в качестве родительского контейнера

Удаление окна

Для удаления окна применяется меnод destroy()

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def click():
window = Tk()
window.title("Новое окно")
window.geometry("250x200")
close_button = ttk.Button(window, text="Закрыть окно", command=lambda: window.destroy())
close_button.pack(anchor="center", expand=1)

open_button = ttk.Button(text="Создать окно", command=click)
open_button.pack(anchor="center", expand=1)

root.mainloop()

В данном случае в новом окне по нажатию на кнопку close_button срабатывает метод window.destroy(), который закрывает окно и по сути аналогичен нажатию на крестик в верхнем правом углу окна.

Определение окна в объектно-ориентированном стиле

В примере выше новое окно, его параметры и вложенные виджеты определялись внутри функции, однако это приводит к разбуханию кода функции. И гораздо проще вынести определение окна в отдельный класс:

from tkinter import *
from tkinter import ttk

class Window(Tk):
def __init__(self):
super().__init__()

# конфигурация окна
self.title("Новое окно")
self.geometry("250x200")

# определение кнопки
self.button = ttk.Button(self, text="закрыть")
self.button["command"] = self.button_clicked
self.button.pack(anchor="center", expand=1)

def button_clicked(self):
self.destroy()

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def click():
window = Window()

open_button = ttk.Button(text="Создать окно", command=click)
open_button.pack(anchor="center", expand=1)

root.mainloop()

Здесь определение окна вынесено в отдельный класс Window, который наследуется от класса tkinter.Tk. Благодаря этому мы можем вынести весь код определения окна в отдельную структурную единицу – класс, что позволит упростить управление кодом.

Окно поверх других окон

Для создания диалогового окна, которое располагается поверх главного окна, применяется класс Toplevel:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def dismiss(window):
window.grab_release()
window.destroy()

def click():
window = Toplevel()
window.title("Новое окно")
window.geometry("250x200")
window.protocol("WM_DELETE_WINDOW", lambda: dismiss(window)) # перехватываем нажатие на крестик
close_button = ttk.Button(window, text="Закрыть окно", command=lambda: dismiss(window))
close_button.pack(anchor="center", expand=1)
window.grab_set() # захватываем пользовательский ввод

open_button = ttk.Button(text="Создать окно", command=click)
open_button.pack(anchor="center", expand=1)

root.mainloop()

Toplevel по сути то же самое окно Tk, которое располагается поверх других окон. В примере выше оно также имеет кнопку. Но кроме того, чтобы пользователь не мог перейти обратно к главному окну пока не закроет это диалоговое окно, применяется ряд методов. Прежде всего захватываем весь пользовательский ввод с помощью метода grab_set():

window.grab_set()

В функции dismiss(), которая закрывает окно, освобождаем ввод с помощью метода grab_release()

window.grab_release()

MessageBox

Окна сообщений

Tkinter имеет ряд встроенных окон для разных ситуаций, в частности, окна сообщений, функционал которых заключен в модуле tkinter.messagebox. Для отображения сообщений этот модуль предоставляет следующие функции:

  • showinfo(): предназначена для отображения некоторой информации
  • showerror(): предназначена для отображения ошибок
  • showwarrning(): предназначена для отображения предупреждений

Все эти функции принимают три параметра:

showinfo(title, message, **options)
showerror(title, message, **options)
showwarrning(title, message, **options)
  • title: заголовок окна
  • message: отображаемое сообщение
  • options: настройки окна

В реальности различие между этими типами сообщений заключается лишь в изображении, которое отображается рядом с текстом сообщения:

from tkinter import *
from tkinter import ttk
from tkinter.messagebox import showerror, showwarning, showinfo

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def open_info():
showinfo(title="Информация", message="Информационное сообщение")

def open_warning():
showwarning(title="Предупреждение", message="Сообщение о предупреждении")

def open_error():
showerror(title="Ошибка", message="Сообщение об ошибке")

info_button = ttk.Button(text="Информация", command=open_info)
info_button.pack(anchor="center", expand=1)

warning_button = ttk.Button(text="Предупреждение", command=open_warning)
warning_button.pack(anchor="center", expand=1)

error_button = ttk.Button(text="Ошибка", command=open_error)
error_button.pack(anchor="center", expand=1)

root.mainloop()

Здесь по нажатию на каждую из трех кнопок отображается соответствующее сообщение:

Окна подтверждения операции

Модуль tkinter.messagebox также предоставляет ряд функций для подтверждения операции, где пользователю предлагается нажать на одну из двух кнопок:

  • askyesno()
  • askokcancel()
  • askretrycancel()

Все эти функции принимают те же три параметра title, message и options. Отличие между ними только в том, что кнопки имеют разный текст. В случае нажатия на кнопку подтверждения, функция возвращает значение True, иначе возвращается False

from tkinter import *
from tkinter import ttk
from tkinter.messagebox import showinfo, askyesno

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def click():
result = askyesno(title="Подтвержение операции", message="Подтвердить операцию?")
if result: showinfo("Результат", "Операция подтверждена")
else: showinfo("Результат", "Операция отменена")

ttk.Button(text="Click", command=click).pack(anchor="center", expand=1)

root.mainloop()

В данном случае по нажатию на кнопку вызывается функция askyesno(), которая отображает диалоговое окно с двумя кнопками “Да” и “Нет”. В зависимости от того, на какую кнопку нажмет пользователь, функция возвратит True или False. Получив результат функции, мы можем проверить его и выполнить те или иные действия.

Особняком стоит функция askquestion – она также отображает две кнопки для подтверждения или отмены действия (кнопки “Yes”(Да) и “No”(Нет)), но в зависимости от нажатой кнопки возвращает строку: “yes” или “no”.

Также отдельно стоит функция askyesnocancel() – она отображает три кнопки: Yes (возвращает True), No (возвращает False) и Cancel (возвращает None):

rom tkinter import *
from tkinter import ttk
from tkinter.messagebox import showinfo, askyesnocancel

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def click():
result = askyesnocancel(title="Подтвержение операции", message="Подтвердить операцию?")
if result==None: showinfo("Результат", "Операция приостановлена")
elif result: showinfo("Результат", "Операция подтверждена")
else : showinfo("Результат", "Операция отменена")

ttk.Button(text="Click", command=click).pack(anchor="center", expand=1)

root.mainloop()

В этом случае диалоговое окно предоставит выбор из трех альтернатив

Настройка окон

Дополнительно все вышерассмотренные функции принимают ряд параметров, которые могут применяться для настройки окон. Некоторые из них:

  • detail: дополнительный текст, который отображается под основным сообщением
  • icon: иконка, которая отображается рядом с сообщением. Должна представлять одно из втроенных изображений: info, error, question или warning
  • default: кнопка по умолчанию. Должна представлять одно из встроенных значений: abort, retry, ignore, ok, cancel, no, yes
from tkinter import *
from tkinter import ttk
from tkinter.messagebox import OK, INFO, showinfo

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def click():
showinfo(title="METANIT.COM", message="Добро пожаловать на сайт METANIT.COM",
detail="Hello World!", icon=INFO, default=OK)

ttk.Button(text="Click", command=click).pack(anchor="center", expand=1)

root.mainloop()

При нажатии на кнопку отобразится следующее окно:

Диалоговые окна

Tkinter обладает рядом встроенных диалоговых окон для различных задач. Рассмотрим некоторые из них.

filedialog

Модуль filedialog предоставляет функциональность файловых диалоговых окон, которые позволяют выбрать файл или каталог для различных задач. В частности в модуле для работы с файлами определены следующие функции:

  • askopenfilename(): открывает диалоговое окно для выбора файла и возвращает путь к выбранному файлу. Если файл не выбран, возвращается пустая строка “”
  • askopenfilenames(): открывает диалоговое окно для выбора файлов и возвращает список путей к выбранным файлам. Если файл не выбран, возвращается пустая строка “”
  • asksaveasfilename(): открывает диалоговое окно для сохранения файла и возвращает путь к сохраненному файлу. Если файл не выбран, возвращается пустая строка “”
  • asksaveasfile(): открывает диалоговое окно для сохранения файла и возвращает сохраненный файл. Если файл не выбран, возвращается None
  • askdirectory(): открывает диалоговое окно для выбора каталога и возвращает путь к выбранному каталогу. Если файл не выбран, возвращается пустая строка “”
  • askopenfile(): открывает диалоговое окно для выбора файла и возвращает выбранный файл. Если файл не выбран, возвращается None
  • askopenfiles(): открывает диалоговое окно для выбора файлов и возвращает список выбранных файлов

Рассмотрим открытие и сохранение файла на примере:

from tkinter import *
from tkinter import ttk
from tkinter import filedialog

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

root.grid_rowconfigure(index=0, weight=1)
root.grid_columnconfigure(index=0, weight=1)
root.grid_columnconfigure(index=1, weight=1)

text_editor = Text()
text_editor.grid(column=0, columnspan=2, row=0)

# открываем файл в текстовое поле
def open_file():
filepath = filedialog.askopenfilename()
if filepath != "":
with open(filepath, "r") as file:
text =file.read()
text_editor.delete("1.0", END)
text_editor.insert("1.0", text)

# сохраняем текст из текстового поля в файл
def save_file():
filepath = filedialog.asksaveasfilename()
if filepath != "":
text = text_editor.get("1.0", END)
with open(filepath, "w") as file:
file.write(text)

open_button = ttk.Button(text="Открыть файл", command=open_file)
open_button.grid(column=0, row=1, sticky=NSEW, padx=10)

save_button = ttk.Button(text="Сохранить файл", command=save_file)
save_button.grid(column=1, row=1, sticky=NSEW, padx=10)

root.mainloop()

Здесь определены две кнопки. По нажатию по на кнопку open_button вызывается функция filedialog.askopenfilename(). Она возвращает путь к выбранному файлу. И если в диалоговом окне не нажата кнопка отмены (то есть путь к файлу не равен пустой строке), то считываем содержимое текстового файла и добавляем его в виджет Text

def open_file():
filepath = filedialog.askopenfilename()
if filepath != "":
with open(filepath, "r") as file:
text =file.read()
text_editor.delete("1.0", END)
text_editor.insert("1.0", text)

По нажатию на кнопку save_button срабатывает функция filedialog.asksaveasfilename(), которая возвращает путь к файлу для сохранения текста из виджета Text. И если файл выбран, то открываем его и сохраняем в него текст:

def save_file():
filepath = filedialog.asksaveasfilename()
if filepath != "":
text = text_editor.get("1.0", END)
with open(filepath, "w") as file:
file.write(text)

Эти функции могут принимать ряд параметров:

  • confirmoverwrite: нужно ли подтверждение для перезаписи файла (для диалогового окна сохранения файла)
  • defaultextension: расширение по умолчанию
  • filetypes: шаблоны типов файлов
  • initialdir: стартовый каталог, который открывается в окне
  • initialfile: файл по умолчанию
  • title: заголовок диалогового окна
  • typevariable: переменная, к которой привязан выбранный файл

Применение параметров:

filedialog.askopenfiles(title="Выбор файла", initialdir="D://tkinter", defaultextension="txt", initialfile="hello.txt")

Выбор шрифта

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

label = ttk.Label(text="Hello World")
label.pack(anchor=NW, padx=10, pady=10)

def font_changed(font):
label["font"] = font

def select_font():
root.tk.call("tk", "fontchooser", "configure", "-font", label["font"], "-command", root.register(font_changed))
root.tk.call("tk", "fontchooser", "show")


open_button = ttk.Button(text="Выбрать шрифт", command=select_font)
open_button.pack(anchor=NW, padx=10, pady=10)

root.mainloop()

По нажатию на кнопку вызывается функция select_font, в которой вначале производится настройка диалогового окна установки шрифта

root.tk.call("tk", "fontchooser", "configure", "-font", label["font"], "-command", root.register(font_changed))

В частности, значение “configure” указывает, что в данном случае производится настройка диалогового окна. Аргумент “-font” указывает, что следующее значение представляет настройка шрифта, который будет выбран в диалоговом окне по умолчанию. В качестве такового здесь используется шрифт метки label.

Аргумент “-command” указывает, что дальше идет определение функции, которая будет срабатывать при выборе шрифта. Здесь такой функцией является функция font_changed. Функция выбора шрифта должна принимать один параметр – через него будет передаваться выбранный шрифт. В данном случае просто переустанавливаем шрифт метки.

Для отображения окна выбора шрифта выполняется вызов

root.tk.call("tk", "fontchooser", "show")

Таким образом, при нажатии на кнопку откроется окно выбора шрифта, а после его выбора он будет применен к метке:

Выбор цвета

Для выбора цвета применяется функции из модуля colorchooser.askcolor():

from tkinter import *
from tkinter import ttk
from tkinter import colorchooser

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

label = ttk.Label(text="Hello World")
label.pack(anchor=NW, padx=10, pady=10)

def select_color():
result = colorchooser.askcolor(initialcolor="black")
label["foreground"] = result[1]

open_button = ttk.Button(text="Выбрать цвет", command=select_color)
open_button.pack(anchor=NW, padx=10, pady=10)

root.mainloop()

Здесь по нажатию на кнопку вызывается функция select_color. В этой функции вызывается функция colorchooser.askcolor. С помощью параметра initialcolor устанавливаем цвет, который выбран по умолчанию в диалоговом окне. В данном случае это черный цвет (“black”)

Результатом функции является кортеж с определениями выбранного цвета. Например, для красного цвета кортеж будет выглядеть следующим образом: ((255, 0, 0), "#ff0000"). То есть, обратившись к второму элементу кортежа, можно получить шестнадцатиричное значение цвета. Здесь выбранный цвет применяется для установки цвета шрифта метки:

label["foreground"] = result[1]

Стилизация

Шрифты

Имена шрифтов

Ряд виджетов, например, Label или Text, поддерживают установку шрифта через параметр font. Каждая платформа может определять свои специфические шрифты. Но также библиотека Tk по умолчанию включает ряд именнованных шрифтов, которые могут использоваться на различных компонентах графического интерфейса и которые доступны на всех платформах:

  • TkDefaultFont: шрифт по умолчанию, который применяется, если для виджета явным образом не определен шрифт
  • TkTextFont: шрифт по умолчанию, который применяется для виджетов Entry, Listbox и ряда других
  • TkFixedFont: шрифт с фиксированной шириной
  • TkMenuFont: шрифт для пунктов меню
  • TkHeadingFont: шрифт для заголовков в Listbox и в таблицах
  • TkCaptionFont: шрифт для строки статуса в окнах
  • TkSmallCaptionFont: шрифт малого размера для диалоговых окон
  • TkIconFont: шрифт для подписей к иконкам
  • TkTooltipFont: шрифт для высплывающих окон

В принципе мы можем использовать эти шрифты не только в любых виджетах:

ttk.Label(text="Hello World", font="TkTextFont")

Tk также предоставляет дополнительный набор именнованных шрифтов, которые определены только на определенных платформах. Для их получения можно использовать функцию names() из пакета tkinter.font:

from tkinter import font

for font_name in font.names():
print(font_name)

Например, на Windows мы получим следующий набор:

fixed
oemfixed
TkDefaultFont
TkMenuFont
ansifixed
systemfixed
TkHeadingFont
device
TkTooltipFont
defaultgui
TkTextFont
ansi
TkCaptionFont
system
TkSmallCaptionFont
TkFixedFont
TkIconFont

В данном случае выводятся и платформа-независимые, и платформо-специфичные шрифты, например, “system”.

ttk.Label(text="Hello World", font="system")

За определение шрифта в Tkinter отвечает класс Font из модуля tkinter.font. Он принимет следующие параметры:

  • name: имя шрифта
  • family: семейство шрифтов
  • size: высота шрифта (в точках при положительном значении или в пикселях при негативном значении)
  • weight: вес шрифта. Принимает значения normal (обычный) или bold (жирный)
  • slant: наклон. Принимает значения roman (обычный) или italic (наклонный)
  • underline: подчеркивание. Принимает значения True (с подчеркиванием) или False (без подчеркивания)
  • overstrike: зачеркивание. Принимает значения True (с зачеркиванием) или False (без зачеркивания)

Для получения всех доступных семейств шрифтов на текущей платформе можно использовать функцию families() из модуля tkinter.font

from tkinter import font

for family in font.families():
print(family)

Пример применения шрифтов:

from tkinter import *
from tkinter import ttk
from tkinter import font


root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

font1 = font.Font(family= "Arial", size=11, weight="normal", slant="roman", underline=True, overstrike=True)
label1 = ttk.Label(text="Hello World", font=font1)
label1.pack(anchor=NW)

font2 = font.Font(family= "Verdana", size=11, weight="normal", slant="roman")
label2 = ttk.Label(text="Hello World", font=font2)
label2.pack(anchor=NW)

root.mainloop()

Также можно использовать определение шрифта в виде строки:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

label1 = ttk.Label(text="Hello World", font="Arial 11 normal roman")
label1.pack(anchor=NW)

label2 = ttk.Label(text="Hello World", font="Verdana 11 normal roman")
label2.pack(anchor=NW)

root.mainloop()

Например, в определении "Arial 11 normal roman", применяется семейство шрифта Arial, высота 11 единиц, нежирный шрифт без наклона.

Установка цвета

Ряд виджетов в Tkinter поддерживают установку цвета для различных аспектов. Например, у виджета Label можно установить параметры foreground и background, которые отвечают за цвет текста и фона соответственно. У некоторых виджетов настройки цвета спрятаны в параметре style.

Цвет можно установить разными способами:

  • Именнованные цвета, например, “red”, который соответствует красному цвету. В зависимости от платформы набор доступных именнованных цветов может отличаться. Все доступные именнованные цвета можно посмотреть в документации. Например:
ttk.Label(text="Hello World", foreground="red")

Можно использовать шестнадцатеричный код RGB в формате #RRGGBB:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

label = ttk.Label(text="Hello World",
padding=8,
foreground="#01579B",
background="#B3E5FC")
label.pack(anchor=CENTER, expand=1)

root.mainloop()

Если нам даны отдельные коды RGB-составляющих, то их можно сконвертировать в шестнадцатеричный код цвета:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

def get_rgb(rgb):
return "#%02x%02x%02x" % rgb

label = ttk.Label(text="Hello World",
padding=8,
foreground=get_rgb((0, 77, 64)),
background=get_rgb((128, 203, 196)))
label.pack(anchor=CENTER, expand=1)

root.mainloop()

Здесь функция get_rgb в качестве параметра получает кортеж из трех составляющих цвет RGB и с помощью форматирования строки переводит значения кортежа в шестнадцатеричный код

Курсоры

Tkinter позволяет настроить форму курсора для виджетов. Для этог у виджетов применяется параметр cursor.

Виджеты могут использовать следующие курсоры:

  • arrow
  • based_arrow_down
  • based_arrow_up
  • boat
  • bogosity
  • bottom_left_corner
  • bottom_right_corner
  • bottom_side
  • bottom_tee
  • box_spiral
  • center_ptr
  • circle
  • clock
  • coffee_mug
  • cross
  • cross_reverse
  • crosshair
  • diamond_cross
  • dot
  • dotbox
  • double_arrow
  • draft_large
  • draft_small
  • draped_box
  • exchange
  • fleur
  • gobbler
  • gumby
  • hand1
  • hand2
  • heart
  • icon
  • iron_cross
  • left_ptr
  • left_side
  • left_tee
  • leftbutton
  • ll_angle
  • lr_angle
  • man
  • middlebutton
  • mouse
  • pencil
  • pirate
  • plus
  • question_arrow
  • right_ptr
  • right_side
  • right_tee
  • rightbutton
  • rtl_logo
  • sailboat
  • sb_down_arrow
  • sb_h_double_arrow
  • sb_left_arrow
  • sb_right_arrow
  • sb_up_arrow
  • sb_v_double_arrow
  • shuttle
  • sizing
  • spider
  • spraycan
  • star
  • target
  • tcross
  • top_left_arrow
  • top_left_corner
  • top_right_corner
  • top_side
  • top_tee
  • trek
  • ul_angle
  • umbrella
  • ur_angle
  • watch
  • xterm
  • X_cursor

Пример использования:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

ttk.Label(text="Hello World!", cursor="pencil").pack(anchor=CENTER, expand=1)

root.mainloop()

Для настройки курсора окна можно установить параметр cursor через метод config():

from tkinter import *

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")
root.config(cursor="watch") # установка курсора

root.mainloop()

Пример программы с разными курсорами

from tkinter import *

root = Tk()
root.title("Cursors")
root.geometry("500x550")
#root.iconbitmap('c:/guis/exe/codemy.ico')
root.config(cursor="fleur")

list = [
"arrow",
"circle",
"clock",
"cross",
"dotbox",
"exchange",
"fleur",
"heart",
"man",
"mouse",
"pirate",
"plus",
"shuttle",
"sizing",
"spider",
"spraycan",
"star",
"target",
"tcross",
"trek",
]

count = 0
row1=0
row2=0
row3=0
row4=0

for cursor in list:
if count < 5:
Button(root, text=cursor, cursor=cursor, width=10, height=5, fg="darkblue").grid(row=row1, column=0, pady=10, padx=10)
row1 += 1
count += 1

elif count >= 5 and count < 10:
Button(root, text=cursor, cursor=cursor, width=10, height=5, fg="darkblue").grid(row=row2, column=1, pady=10, padx=10)
row2 += 1
count += 1

elif count >= 10 and count < 15:
Button(root, text=cursor, cursor=cursor, width=10, height=5, fg="darkblue").grid(row=row3, column=2, pady=10, padx=10)
row3 += 1
count += 1

else:
Button(root, text=cursor, cursor=cursor, width=10, height=5, fg="darkblue").grid(row=row4, column=4, pady=5, padx=5)
row4 += 1
count += 1

root.mainloop()

Установка стилей

Стиль описывает внешний вид виджета. За установку стиля в виджетах отвечает параметр style. Встроенные виджеты по умолчанию применяют некоторые встроенные стили. В частности, все кнопки применяют стиль TButton, который описывает, как выглядят кнопки. Каждый стиль имеет имя. При создании, изменении или применении стиля к виджетам, необходимо знать его имя.

Чтобы узнать стиль определенного виджета, можно обратиться к его параметру style:

label = ttk.Label(text="Hello World")
label.pack(anchor=CENTER, expand=1)
print(label["style"])

Если возвращается пустая строка, то значит, что к виджету применяется стиль по умолчанию. В этом случае название стиля можно получить с помощью метода winfo_class():

label = ttk.Label(text="Hello World") 

print(label.winfo_class()) # TLabel

Как правило, встроенные стили называются по имени класса виджета и предваряются буквой T. Например, для виджета Label – стиль TLabel, для Button – TButton.

Определение и применение стилей

Стиль в Tkinter представляет объект Style. У данного объект есть метод configure(), который позволяет настроить стиль

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

label_style = ttk.Style()
label_style.configure("My.TLabel", # имя стиля
font="helvetica 14", # шрифт
foreground="#004D40", # цвет текста
padding=10, # отступы
background="#B2DFDB") # фоновый цвет

label = ttk.Label(text="Hello World", style="My.TLabel")
label.pack(anchor=CENTER, expand=1)

root.mainloop()

Здесь создается стиль в виде объекта label_style. В методе configure() первым параметром передается имя стиля – в даннои случае “My.TLabel”. Все остальные параметры настраивают различные аспекты стиля, так здесь устанавливаются шрифт, цвет фона и текста и отступы.

label_style.configure("My.TLabel",          # имя стиля
font="helvetica 14", # шрифт
foreground="#004D40", # цвет текста
padding=10, # отступы
background="#B2DFDB") # фоновый цвет

Затем применяем этот стиль, передавая его параметру style:

label = ttk.Label(text="Hello World", style="My.TLabel")

Имена стилей

Имя создаваемых стилей имеют следующий формат:

новый_стиль.встроенный_стиль

Например, в примере выше название стиля “My.TLabel” указывает, что фактически он называется “My” и наследуется от “TLabel”. И те параметры, которые не будут явным образом определены, будут унаследованы от родительского стиля “TLabel”

Расширение встроенных стилей

Вместо создания новых стилей можно просто изменить отдельные характеристики уже существующих:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")


ttk.Style().configure("TLabel", font="helvetica 13", foreground="#004D40", padding=8, background="#B2DFDB")

ttk.Label(text="Hello World!").pack(anchor=NW, padx=6, pady=6)
ttk.Label(text="Bye World..").pack(anchor=NW, padx=6, pady=6)

root.mainloop()

В данном случае изменяется встроенный стиль TLabel. Он применяется к меткам по умолчанию, поэтому данный стиль не надо явным образом устанавливать для меток.

Применение стиля ко всем виджетам

Выше стиль применялся к меткам Label, к другим же типам виджетов он не применялся. Если же мы хотим, чтобы у нас был бы общий стиль для всех типов виджетов, то в метод configure() в качестве имени стиля передается “.”

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

ttk.Style().configure(".", font="helvetica 13", foreground="#004D40", padding=8, background="#B2DFDB")

ttk.Label(text="Hello World!").pack(anchor=NW, padx=6, pady=6)
ttk.Button(text="Click").pack(anchor=NW, padx=6, pady=6)

root.mainloop()

Подобный стиль также не надо явно применять, он применяется автоматически ко всем виджетам

Темы

Тема представляет коллекцию стилей. Все стили одно темы проектируются таким образом, чтобы визуально сочетаться друг с другом. Применение определенной темы означает, что к виджетам будут применяться стили из данной темы.

По умолчанию Tkinter уже предоставляет ряд тем. Чтобы их получить, можно использовать метод theme_names() класса ttk.Style

from tkinter import ttk

for theme in ttk.Style().theme_names():
print(theme)

Стоит учитывать, что на разных операционных системах свои встроенные темы.

Для получения текущей темы можно использовать метод theme_use()

current_theme = ttk.Style().theme_use()
print(current_theme)

Для установки другой темы в этот метод в качестве параметра передается название темы:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

# устанавливаем тему "classic"
ttk.Style().theme_use("classic")

ttk.Button(text="Click").pack(anchor=CENTER, expand=1)

root.mainloop()

Подобным образом мы можем определить небольшое приложение для выбора из текущих тем:

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("METANIT.COM")
root.geometry("250x200")

# выбранная тема
selected_theme = StringVar()
style = ttk.Style()

# изменение текущей темы
def change_theme():
style.theme_use(selected_theme.get())

ttk.Label(textvariable=selected_theme, font="Helvetica 13").pack(anchor=NW)

for theme in style.theme_names():
ttk.Radiobutton(text=theme,
value=theme,
variable=selected_theme,
command=change_theme).pack(anchor=NW)

root.mainloop()

В данном случае каждый элемент Radiobutton представляет определенную тему. При выборе определенной кнопки Radiobutton будет срабатывать функция change_theme(), в которой будет изменены текущая тема:

Полезные ссылки

Изучите Tkinter на примере

Введение в курс

Полезная ссылка 1

Полезная ссылка 2

Полезная ссылка 3

Полезная ссылка 4