Класс ttk.Treeview – пример принципиально нового элемента, который входит в библиотеку tkinter.ttk. С его помощью информация может быть представлена в иерархической или табличной форме. Это позволит более удобно ее считывать.
Каждый элемент, который добавляется к классу ttk.Treeview, состоит из нескольких колонок (одной – также возможный вариант). С помощью первой отображаются текст и иконка, показывающие, возможно ли раскрытие элемента. А все остальные колонки показывают сами значения.
Первая строка этого класса содержит заголовки, которые можно спрятать. Также этот класс может работать с CSV-файлами. И мы наглядно это продемонстрируем.
Практика использования виджета Treeview
Давайте попробуем создать такое приложение. Оно получает список контактов, которые содержатся в CSV-файле и представляет его в виде таблицы. Выглядеть приложение будет так.
Давайте создадим объект класса ttk.Treeview, который будет содержать три колонки, в которых будут содержаться имя, фамилия и адрес электронной почты.
Эти данные будут загружаться из файла формата CSV, а потом будет осуществляться связывание виртуального элемента <<TreeviewSelect>>. Отсюда исходит вопрос – откуда он берется? Он появляется, если выбирается хотя бы один элемент.
Нагляднее будет с примером кода.
import csv import tkinter as tk import tkinter.ttk as ttk class App(tk.Tk): def __init__(self, path): super().__init__() self.title("Ttk Treeview") columns = ("#1", "#2", "#3") self.tree = ttk.Treeview(self, show="headings", columns=columns) self.tree.heading("#1", text="Фамилия") self.tree.heading("#2", text="Имя") self.tree.heading("#3", text="Почта") ysb = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscroll=ysb.set) with open("../lesson_13/contacts.csv", newline="") as f: for contact in csv.reader(f): self.tree.insert("", tk.END, values=contact) self.tree.bind("<<TreeviewSelect>>", self.print_selection) self.tree.grid(row=0, column=0) ysb.grid(row=0, column=1, sticky=tk.N + tk.S) self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) def print_selection(self, event): for selection in self.tree.selection(): item = self.tree.item(selection) last_name, first_name, email = item["values"][0:3] text = "Выбор: {}, {} <{}>" print(text.format(last_name, first_name, email)) if __name__ == "__main__": app = App(path=".") app.mainloop()
При запуске этого приложения после выбора контакта информация о нем будет выводиться в стандартный вывод.
Принцип использования виджета Treeview
Чтобы создать экземпляр класса ttk.Treeview, в котором есть несколько колонок, необходимо задать идентификатор каждого столбца с использованием параметра columns. После этого уже осуществляется настройка текста заголовка, для чего используется функция heading(), которая вызывается из объекта tree(проще говоря, это – метод).
В нашем примере мы используем идентификаторы #1, #2, #3. Все дело в том, что первая колонка всегда идентифицируется, как #0.
Кроме этого, необходимо передать значение headings параметру show. Это делается с целью обозначить, что нужно спрятать нулевую колонку. Ведь в нашем случае вложенных элементов не предусматривается.
Для параметра show следующие значения могут использоваться:
- tree. Показывает нулевую колонку.
- headings. Показывает строку заголовка.
- tree headings. Показывает и нулевую колонку, и строку заголовка. Это стандартное значение. То есть, если не задать другое, то будет использоваться это.
- “». Не показывает ни строку заголовка, ни нулевую колонку.
Затем к виджету мы добавляем вертикальную прокрутку.
columns = ("#1", "#2", "#3") self.tree = ttk.Treeview(self, show="headings", columns=columns) self.tree.heading("#1", text="Фамилия") self.tree.heading("#2", text="Имя") self.tree.heading("#3", text="Почта") ysb = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscroll=ysb.set)
Чтобы осуществить загрузку контактов в таблицу, необходимо воспользоваться функцией render(), которая находится в модуле CSV, чтобы обработать ее. В процессе этого, строки на каждой из итераций, добавляются к дереву.
Как это делается? Используется метод insert(), получающий родительский узел и положение для размещения элемента.
Так, как все контакты отображаются, как объекты верхнего уровня, передаем в качестве первого параметра пустую строку, а также константу END. Это делается для того, чтобы добавлять новые контакты на последнюю позицию.
Кроме этого, возможно использование и других ключевых слов для этого метода. Тут применяется параметр values, принимающий последовательность значений, которые и показываются в колонках нашего дерева.
with open("../lesson_13/contacts.csv", newline="") as f: for contact in csv.reader(f): self.tree.insert("", tk.END, values=contact) self.tree.bind("<<TreeviewSelect>>", self.print_selection)
<<TreeviewSelect>> – это виртуальное событие, генерация которого осуществляется при выборе элементов таблицы. Далее используем метод selection для того, чтобы получить текущее выделение. Эта команда исполняется в обработчике print_selection().
Далее для каждого результата выполняется следующая последовательность действий:
- Используется метод item(), который помогает нам получить параметры и значения выбранного объекта в виде словаря.
- Получаем имя, фамилию и адрес электронной почты. Для этого необходимо получить первые три значения этого словаря.
- После этого значения выводятся в стандартный вывод. Предварительно осуществляется их форматирование.
def print_selection(self, event): for selection in self.tree.selection(): item = self.tree.item(selection) last_name, first_name, email = item["values"][0:3] text = "Выбор: {}, {} <{}>" print(text.format(last_name, first_name, email))
Мы рассмотрели основные особенности класса ttk.Treeview, так как мы работали со стандартной таблицей. Тем не менее, она поддерживает значительно больше функций.
Как использовать теги в элементах Treeview?
С помощью тегов разработчик может связать последовательности события с конкретными строками таблицы. Допустим, появилась потребность открывать новое окно, чтобы добавить информацию об электронной почте по двойному клику. Правда, это должно работать исключительно для тех записей, в которых поле email уже содержит какую-то информацию.
Чтобы это реализовать, необходимо добавить тег с условием во время вставки. Затем необходимо осуществить вызов tag_bind() на элементе виджета, содержащего последовательность <Double-Button-1>. Тут просто возможна ссылка на функцию-обработчика send_email_to_contact() по имени.
columns = ("Фамилия", "Имя", "Почта") tree = ttk.Treeview(self, show="headings", columns=columns) for contact in csv.reader(f): email = contact[2] tags = ("dbl-click",) if email else () self.tree.insert("", tk.END, values=contact, tags=tags) tree.tag_bind("dbl-click", "<Double-Button-1>", send_email_to_contact)
Аналогично процессам, которые осуществляются при связывании событий с элементами Canvas, необходимо помнить о том, что нужно добавлять элементы с тегами к ttk.Treeview до того, как будет осуществлен вызов tag_bind(), поскольку добавление связываний осуществляется исключительно к имеющимся совпадениям в элементах.
Как осуществлять заполнение вложенных элементов?
ttk.Treeview нередко используется в качестве обычной таблицы. Тем не менее, намного интереснее использовать его по своему основному назначению – для отображения иерархической структуры.
Давайте попробуем создать такое приложение.
Для заполнения дерева будет использоваться метод populate_node().
При раскрытии узла осуществляется еще один вызов этого метода, после чего туда записываются вложенные элементы.
import os import tkinter as tk import tkinter.ttk as ttk class App(tk.Tk): def __init__(self, path): super().__init__() self.title("Ttk Treeview") abspath = os.path.abspath(path) self.nodes = {} self.tree = ttk.Treeview(self) self.tree.heading("#0", text=abspath, anchor=tk.W) ysb = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.tree.yview) xsb = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.tree.xview) self.tree.configure(yscroll=ysb.set, xscroll=xsb.set) self.tree.grid(row=0, column=0, sticky=tk.N + tk.S + tk.E + tk.W) ysb.grid(row=0, column=1, sticky=tk.N + tk.S) xsb.grid(row=1, column=0, sticky=tk.E + tk.W) self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.tree.bind("<<TreeviewOpen>>", self.open_node) self.populate_node("", abspath) def populate_node(self, parent, abspath): for entry in os.listdir(abspath): entry_path = os.path.join(abspath, entry) node = self.tree.insert(parent, tk.END, text=entry, open=False) if os.path.isdir(entry_path): self.nodes[node] = entry_path self.tree.insert(node, tk.END) def open_node(self, event): item = self.tree.focus() abspath = self.nodes.pop(item, False) if abspath: children = self.tree.get_children(item) self.tree.delete(children) self.populate_node(item, abspath) if __name__ == "__main__": app = App(path="../") app.mainloop()
Как видим, ничего нет сложного. Все просто и интуитивно понятно. Достаточно выполнять инструкции, которые приводятся здесь, и можно создавать самые сложные иерархические структуры и таблицы.