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

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

Начало

Первая программа

Окно приложения

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

Установка иконки

Перехват закрытия окна

Атрибуты окна

Виджеты

Введение в виджеты. Tk и ttk

Параметры виджета

Получение информации о виджете

Кнопки

Обработка нажатия на кнопку

Отключение кнопки

Позиционирование. Pack

Растяжение виджета

Позиционирование. Grid

Обработка событий

Привязка событий

Шаблон события

Клавиши

Глобальная регистрация события

Текстовая метка Label

Поле ввода Entry

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

Вставка и удаление текста

Валидация

Привязка виджетов к переменным

Отслеживание изменения переменной

Checkbutton

Radiobutton

Обработка выбора пользователя

Установка изображения

Установка родительского контейнера

Frame

Listbox

Scrollbar и прокрутка виджета

Combobox

Scale

Spinbox

Progressbar

Начало

Многие программы на сегодняшний день используют графический интерфейс, который более интуитивен и удобен для пользователя, чем консоль. И с помощью языка программирования Python также можно создавать графические программы. Для этого в Python по умолчанию применяется специальный тулкит – набор компонентов, который называется tkinter. Тулкит tkinter доступен в виде отдельного встроенного модуля, который содержит все необходимые графические компоненты – кнопки, текстовые поля и т.д.

По сути Tkinter представляет интерфейс в Python для графической библиотеки Tk (Собственно само название “Tkinter” является сокращением “Tk interface”). Первоначально данная библиотека разрабатывалась для языка Tcl – ее создал в 1988 году Джон Остерхаут (John Ousterhout), профессор computer science из Беркли для создания графических приложений для своего языка Tcl. Но впоследствии Tk была адаптирована для широкого ряда динамических языков, в частности, для Ruby, Perl и естественно для языка Python (в 1994 году). И на сегодняшний день и библиотека Tk, и сам тулкит tkinter доступны для большинства операционных систем, в том числе для Mac OS, Linux и Windows.

Преимущества Tkinter:

  • Данный тулкит по умолчанию включен в стандартную библиотеку языка Python в виде отдельного модуля, поэтому не потребуется что-то дополнительно устанавливать
  • Tkinter – кроссплатформенный, один и тот же код будет работать одинаково на разных платформах (Mac OS, Linux и Windows)
  • Tkinter легко изучать. Сам тулкит, хотя и содержит некоторый готовый код, виджеты и графические элементы, но при этом довольно лаконичен и прост.
  • Tk распространяется по BSD-лицензии, поэтому библиотека может быть использована как в опенсорсных проектах, так и в коммерческих наработках.

Если необходимо или интересно узнать версию библиотеки Tk, которая будет использоваться, в интерпертаторе Python можно выполнить следующую инструкцию:

tkinter.Tcl().eval("info patchlevel")
C:\Users\eugen>python
Python 3.10.1 (tags/v3.10.1:2cd268a, Dec  6 2021, 19:10:37) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import tkinter
>>>
>>> tkinter.Tcl().eval("info patchlevel")
'8.6.12'
>>>

В некоторых ОС на базе Linux иногда при установке python не устанавливается пакет tkinter. В этом случае мы можем доустановить thinkter командой

 sudo apt-get install python3-tk

Первая программа

Создадим первую программу с использованием Tkinter. Для этого определим следующий скрипт:

from tkinter import *

root = Tk() # создаем корневой объект - окно
root.title("Приложение на Tkinter") # устанавливаем заголовок окна
root.geometry("300x250") # устанавливаем размеры окна

label = Label(text="Hello METANIT.COM") # создаем текстовую метку
label.pack() # размещаем метку в окне

root.mainloop()

Для создания графического окна применяется конструктор Tk(), который определен в модуле tkinter. Создаваемое окно присваивается переменной root, и через эту переменную мы можем управлять атрибутами окна. В частности, с помощью метода title() можно установить заголовок окна.

С помощью метода geometry() – размер окна. Для установки размера в метод geometry() передается строка в формате “Ширина x Высота”. Если при создании окна приложения метод geometry() не вызывается, то окно занимает то пространство, которое необходимо для размещения внутреннего содержимого.

Создав окно, мы можем разместить в нем другие графические элементы. Эти элементы еще называются виджетами. В данном случае мы размещаем в окне текстовую метку. Для это создаем объект класса Label, которые хранит некоторый текст. Затем для размещения элемента label в окне вызываем у него метод pack()

Для отображения окна надо вызвать у него метод mainloop(), который запускает цикл обработки событий окна для взаимодействия с пользователем.

В результате при запуске скрипта мы увидим такое пустое окошко:

На скриншоте выше определено окно, создаваемое в ОС Windows, на каждой конкретной системе отдельные визуальные моменты, отрисовка графических компонентов может несколько отличаться.

Окно приложения

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

root = Tk()

Для отображения окна и взаимодействия с пользователем у окна вызывается метод mainloop()

from tkinter import *

root = Tk()
root.mainloop()

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

Размеры и начальная позиция окна

По умолчанию окно имеет некоторые стандартные размеры. Для установки размеров используется метод geometry(). Например, определение окна с шириной в 300 единиц и высотой 250 единиц:

from tkinter import *

root = Tk()
root.geometry("300x250")

root.mainloop()

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

from tkinter import *

root = Tk()
root.geometry("300x250+400+200")

root.mainloop()

Теперь строка в методе geometry имеет следующий формат: “Ширина x Высота + координатаX + координатаY”. То есть при запуске окно шириной в 300 единиц и высотой 250 единиц будет находиться на 400 пикселей вправо и на 200 пикселей вниз от верхнего левого угла экрана.

Для получения данных о размере и позиции также можно использовать метод geometry(), который возвращает данные значения в виде строки в формате “widthxheight+x+y”:

from tkinter import *

root = Tk()
root.geometry("300x250+400+200")

root.update_idletasks()
print(root.geometry()) # "300x250+400+200"

root.mainloop()

Чтобы приложение еще до метода mainloop() принименило для окна переданные ему значения по ширине, высоте и позиции, вызывается метод root.update_idletasks(). В итоге вызов root.geometry() возвратить строку “300×250+400+200”

По умолчанию мы можем изменять размеры окна. Тем не менее иногда может потребоваться сделать размер окна фиксированным. В этом случае мы можем использовать метод resizable(). Его первый параметр указывает, может ли пользователь растягивать окно по ширине, а второй параметр – можно ли растягивать по высоте. Чтобы запретить растягивание по какой-либо стороне, необходимо для соответствующего параметра передать значение False. Например, запретим какое-либо изменение размеров:

from tkinter import *

root = Tk()
root.geometry("300x250")

root.resizable(False, False)

root.mainloop()

Также можно установить минимальные и максимальные размеры окна:

root.minsize(200,150)   # минимальные размеры: ширина - 200, высота - 150
root.maxsize(400,300) # максимальные размеры: ширина - 400, высота - 300

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

По умолчанию заголовок окна – “tk”. Для установки заголовка применяется метод title(), в который передается текст заголовка:

from tkinter import *

root = Tk()
root.title("Hello METANIT.COM")
root.geometry("300x250")
root.mainloop()

Установка иконки

Перед заголовком отображается иконка. По умолчанию это иконка пера. С помощью метода iconbitmap() можно задать любую другую иконку. Например, определим в одной папке с файлом приложения какой-нибудь файл с иконкой, допустип, он называется “favicon.ico” и используем его для установки иконки:

from tkinter import *

root = Tk()
root.title("Hello METANIT.COM")
root.iconbitmap(default="favicon.ico")
root.geometry("300x250")
root.mainloop()

через параметр default в метод iconbitmap передается путь к иконки. В данном случае файл иконки располагается с файлом приложения в одной папке, поэтому в качестве пути указывается просто имя файла.

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

from tkinter import *

root = Tk()
root.geometry("250x200")

root.title("Hello METANIT.COM")
icon = PhotoImage(file = "icon2.png")
root.iconphoto(False, icon)

root.mainloop()

Первый параметр метода iconphoto() указывает, надо ли использовать иконку по умолчанию для всех окон приложения. Второй параметр – объект PhotoImage, который собственно и устанавливает файл изображения (здесь файл “icon2.png)

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

from tkinter import *
import tempfile, base64, zlib

ICON = zlib.decompress(base64.b64decode("eJxjYGAEQgEBBiDJwZDBysAgxsDAoAHEQCEGBQaIOAg4sDIgACMUj4JRMApGwQgF/ykEAFXxQRc="))

_, ICON_PATH = tempfile.mkstemp()
with open(ICON_PATH, "wb") as icon_file:
icon_file.write(ICON)

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

root.iconbitmap(default=ICON_PATH)

root.mainloop()

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


Перехват закрытия окна

from tkinter import *

def finish():
root.destroy() # ручное закрытие окна и всего приложения
print("Закрытие приложения")

root = Tk()
root.geometry("250x200")

root.title("Hello METANIT.COM")
root.protocol("WM_DELETE_WINDOW", finish)

root.mainloop()

Первый параметр метода protocol() представляет имя события, в данном случае это “WM_DELETE_WINDO”. Второй параметр представляет функцию, которая вызывается при возникновении события. Здесь эта функция finish(), в котором с помощью метода destroy() вручную вызываем закрытие окна (а с ним и всего приложения), а затем выводим на консоль некоторое сообщение.

Атрибуты окна

С помощью специального метода attributes() можно установать отдельные атрибуты окна, для которых нет специальных методов. В качестве первого параметра метод принимает название атрибута, которое предваряется дефисом. А второй параметр – значение для этого атрибута. Например, растяжение окна на весь экран:

root.attributes("-fullscreen", True)

Здесь атрибуту fullscreen передается значение True, благодаря чему устанавливается полноэкранный режим.

Другой пример – установка прозрачности с помощью атрибута alpha:

root.attributes("-alpha", 0.5)

Значение 0.5 указывает на полупрозрачность.

Третий пример – отключение верхней панели окна (за исключением заголовка и крестика для закрытия):

root.attributes("-toolwindow", True)

Виджеты

Введение в виджеты. Tk и ttk

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

  • Button: кнопка
  • Label: текстовая метка
  • Entry: однострочное текстовое поле
  • Text: многострочное текстовое поле
  • Checkbutton: флажок
  • Radiobutton: переключатель или радиокнопка
  • Frame: фрейм, который организует виджеты в группы
  • Listbox: список
  • Combobox: выпадающий список
  • Menu: элемент меню
  • Scrollbar: полоса прокрутки
  • Treeview: позволяет создавать древовидные и табличные элементы
  • Scale: текстовая метка
  • Spinbox: список значений со стрелками для перемещения по элементам
  • Progressbar: текстовая метка
  • Canvas: текстовая метка
  • Notebook: панель вкладок

Tkinter предоставляет виджеты в двух вариантах: виджеты, которые располагаются непосредственно в пакете tkinter, и виджеты из пакета tkinter.ttk. С одной стороны, оба пакета предоставляют практически одни и те же виджеты, например, виджет Button есть в обоих пакетах. Но с другой стороны, ttk предоставляет чуть больше функциональности по настройке виджетов, в частности, по их стилизации. И считается, что виджеты из ttk несколько современнее, чем стандартные, в то же время с ttk, возможно, чуть сложнее работать. Что именно использовать остается на выбор разработчика.

Рассмотрим разницу на примере виджета Button. Сначала посмотрим на стандартный виджет Button из общего пакета tkinter:

from tkinter import *

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

btn = Button(text="Click") # создаем кнопку из пакета tkinter
btn.pack() # размещаем кнопку в окне

root.mainloop()

Теперь посмотрим на примере кнопки из пакета ttk:

from tkinter import *
from tkinter import ttk # подключаем пакет ttk

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

btn = ttk.Button(text="Click") # создаем кнопку из пакета ttk
btn.pack() # размещаем кнопку в окне

root.mainloop()

По сути мы создаем ту же самую кнопку с той же надписью, однако ее внешний вид будет несколько иной:

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

Параметры виджета

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

ttk.Button(text="Click") # устанавливаем параметр text

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

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button()
btn.pack()
# устанавливаем параметр text
btn["text"]="Send"
# получаем значение параметра text
btnText = btn["text"]
print(btnText)

root.mainloop()

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

btn = ttk.Button()
btn.pack()
# устанавливаем параметр text
btn.config(text="Send Email")

Получение информации о виджете

Для получения информации о виджете можно использовать ряд его атрибутов. Рассмотрим некоторые из них:

  • winfo_class: возвращает класс виджета, например, для кнопки это класс TButton
  • winfo_children: возвращает для текущего виджета список вложенных виджетов
  • winfo_parent: возвращает родительский виджет
  • winfo_toplevel: возвращает окно, которое содержит данный виджет
  • winfo_width и winfo_height: текущая ширина и высота виджета
  • winfo_reqwidth и winfo_reqheight: запрошенная виджетом ширина и высота
  • winfo_x и winfo_y: x и y координаты верхнего левого угла виджета относительно родительского элемента
  • winfo_rootx и winfo_rooty: x и y координаты верхнего левого угла виджета относительно экрана
  • winfo_viewable: указывает, отображается ли виджет или скрыт

Например, получим информацию о всех виджетах в окне:

from tkinter import *

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

btn = Button(text="Hello")
btn.pack()


def print_info(widget, depth=0):
widget_class=widget.winfo_class()
widget_width = widget.winfo_width()
widget_height = widget.winfo_height()
widget_x = widget.winfo_x()
widget_y = widget.winfo_y()
print(" "*depth + f"{widget_class} width={widget_width} height={widget_height} x={widget_x} y={widget_y}")
for child in widget.winfo_children():
print_info(child, depth+1)

root.update() # обновляем информацию о виджетах

print_info(root)

root.mainloop()

Здесь определена функция print_info(), которая в качестве параметров получает виджет, информацию о котором надо вывести на консоль, и уровень в визуальной иерархии элементов (depth).

В самой функции для виджета выводим информацию о классе, ширине, высоте и координатах Х и Y, а также для каждого вложенного виджета рекурсивно вызываем эту же функцию.

Чтобы установленные размеры и позиции были применены к виджетам до вызыва root.mainloop(), вызываем метод root.update()

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

Tk width=250 height=200  x=104 y=104
   Button width=39 height=26  x=105 y=0

Кнопки

Одним из наиболее используемых компонентов в графических программах является кнопка. В tkinter кнопки представлены классом Button. Основные параметры виджета Button:

  • command: функция, которая вызывается при нажатии на кнопку
  • compund: устанавливает расположение картинки и текста относительно друг друга
  • cursor: курсор указателя мыши при наведении на метку
  • image: ссылка на изображение, которое отображается на метке
  • pading: отступы от границ вилжета до его текста
  • state: состояние кнопки
  • text: устанавливает текст метки
  • textvariable: устанавливает привязку к элементу StringVar
  • underline: указывает на номер символа в тексте кнопки, который подчеркивается. По умолчанию значение -1, то есть никакой символ не подчеркивается
  • width: ширина виджета

Добавим в окно обычную кнопку из пакета ttk:

from tkinter import *
from tkinter import ttk

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

# стандартная кнопка
btn = ttk.Button(text="Button")
btn.pack()

root.mainloop()

Для создания кнопки используется конструктор Button(). В этом конструкторе с помощью параметра text можно установить текст кнопки.

Чтобы разместить виджет в контейнере (главном окне), у него вызывается метод pack(). На ОС Windows мы получим следующую кнопку:

Конструктор Button определяет различные параметры, которые позволяют настроить поведение и внешний вид кнопки. Однако конкретный набор параметров зависит от того, используем ли мы кнопки из пакета tkinter или из пакета tkinter.ttk.

Обработка нажатия на кнопку

Для обработки нажатия на кнопку необходимо установить в конструкторе параметр command, присвоив ему ссылку на функцию, которая будет срабатывать при нажатии:

from tkinter import *
from tkinter import ttk

clicks = 0

def click_button():
global clicks
clicks += 1
# изменяем текст на кнопке
btn["text"] = f"Clicks {clicks}"

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

btn = ttk.Button(text="Click Me", command=click_button)
btn.pack()

root.mainloop()

Здесь в качестве обработчика нажатия устанавливается функция click_button. В этой функции изменяется глобальная переменная clicks, которая хранит число кликов. Кроме того, изменяем текст кнопки, чтобы визуально было видно сколько нажатий произведено. Таким образом, при каждом нажатии кнопки будет срабатывать функция click_button, и количество кликов будет увеличиваться:

Отключение кнопки

Для ttk-кнопки мы можем установить отключенное состояние с помощью метода state(), передав ему значение “disabled”. С такой кнопкой пользователь не сможет взаимодействовать:

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click Me", state=["disabled"])
btn.pack()

root.mainloop()

При этом в метод state мы можем передать набор состояний, поэтому значение “disabled” передается внутри списка.

Позиционирование. Pack

Для позиционирования виджетов в контейнере применяются различные способы. Один из них представляет вызов у виджета метода pack(). Этот метод принимает следующие параметры:

  • expand: если равно True, то виджет заполняет все пространство контейнера.
  • fill: определяет, будет ли виджет растягиваться, чтобы заполнить свободное пространство вокруг. Этот параметр может принимать следующие значения: NONE (по умолчанию, элемент не растягивается), X (элемент растягивается только по горизонтали), Y (элемент растягивается только по вертикали) и BOTH (элемент растягивается по вертикали и горизонтали).
  • anchor: помещает виджет в определенной части контейнера. Может принимать значения n, e, s, w, ne, nw, se, sw, c, которые являются сокращениями от Noth(север – вверх), South (юг – низ), East (восток – правая сторона), West (запад – левая сторона) и Center (по центру). Например, значение nw указывает на верхний левый угол
  • side: выравнивает виджет по одной из сторон контейнера. Может принимать значения: TOP (по умолчанию, выравнивается по верхней стороне контейнера), BOTTOM (выравнивание по нижней стороне), LEFT (выравнивание по левой стороне), RIGHT (выравнивание по правой стороне).
  • ipadx: устанавливает отступ содержимого виджета от его границы по горизонтали.
  • ipady: устанавливают отступ содержимого виджета от его границы по вертикали.
  • padx: устанавливает отступ виджета от границ контейнера по горизонтали.
  • pady: устанавливает отступ виджета от границ контейнера по вертикали.

Растяжение виджета

Для растяжения виджета применяется параметру expand передается значение True (или соответствующее число). Причем при отсутствии других параметров позиционирования значение expand=True позволяет поместить виджет по центру:

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.pack(expand=True)

root.mainloop()

Anchor

Параметр anchor помещает виджет в определенной части контейнера. Может принимать следующие значения:

  • n: положение вверху по центру
  • e: положение в правой части контейнера по центру
  • s: положение внизу по центру
  • w: положение в левой части контейнера по центру
  • nw: положение в верхнем левом углу
  • ne: положение в верхнем правом углу
  • se: положение в нижнем правом углу
  • sw: положение в нижнем левом углу
  • center: положение центру

Схематически это выглядит следующим образом:

Стоит отметить, что значение в кавычках для параметра anchor передается в нижнем регистре, без кавычек – в верхнем регистре

btn.pack(anchor="nw")
btn.pack(anchor=NW)

Также стоит отметить, что для некоторых сценариев (например, помещение в нижней части контейнера) может потребоваться указать для параметра expand значение True. Например, поместим кнопку в верхнем левом углу:

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.pack(anchor="nw")

root.mainloop()

Заполнение контейнера

Параметр fill позволяет заполнить пространство контейнер по горизонтали (значение X), по вертикали (значение Y) или по обеим сторонам (значение BOTH). По умолчанию значение NONE, при котором заполнение контейнера отсутствует. Например, заполним все пространство контейнера по горизонтали

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.pack(fill=X)

root.mainloop()

Для заполнения контейнера по всем сторонам также требуется установить параметр expand = True

btn.pack(fill=BOTH, expand=True)

Отступы

Параметры padx и pady позволяют указать отступы виджета от границ контейнера:

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.pack(anchor="nw", padx=20, pady=30)

root.mainloop()

Здесь кнопка смещена относительно верхнего левого угла на 20 единиц вправо и на 30 единиц вниз

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

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.pack(fill=X, padx=[20, 60], pady=30)

root.mainloop()

В данном случае отступ слева – 20 единиц, а справа – 60 единиц

Параметры ipadx и ipady позволяют указать отступы содержимого виджета от границ виджета по горизонтали и вертикали соответственно:

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.pack(expand=True, ipadx=10, ipady=10)

root.mainloop()

Позиционирование по стороне

Используем параметр side:

from tkinter import *
from tkinter import ttk

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

btn1 = ttk.Button(text="BOTTOM")
btn1.pack(side=BOTTOM)

btn2 = ttk.Button(text="RIGHT")
btn2.pack(side=RIGHT)

btn3 = ttk.Button(text="LEFT")
btn3.pack(side=LEFT)

btn4 = ttk.Button(text="TOP")
btn4.pack(side=TOP)

root.mainloop()

Комбинируя параметры side и fill, можно растянуть элемент по вертикали:

btn1 = ttk.Button(text="CLICK ME")
btn1.pack(side=LEFT, fill=Y)

Позиционирование. Place

Метод place() позволяет более точно настроить координаты и размеры виджета. Он принимает следующие параметры:

  • height и width: устанавливают соответственно высоту и ширину элемента в пикселях
  • relheight и relwidth: также задают соответственно высоту и ширину элемента, но в качестве значения используется число float в промежутке между 0.0 и 1.0, которое указывает на долю от высоты и ширины родительского контейнера
  • x и y: устанавливают смещение элемента по горизонтали и вертикали в пикселях соответственно относительно верхнего левого угла контейнера
  • relx и rely: также задают смещение элемента по горизонтали и вертикали, но в качестве значения используется число float в промежутке между 0.0 и 1.0, которое указывает на долю от высоты и ширины родительского контейнера
  • bordermode: задает формат границы элемента. Может принимать значение INSIDE (по умолчанию) и OUTSIDE
  • anchor: устанавливает опции растяжения элемента. Может принимать значения n, e, s, w, ne, nw, se, sw, c, которые являются сокращениями от North(север – вверх), South (юг – низ), East (восток – правая сторона), West (запад – левая сторона) и Center (по центру). Например, значение nw указывает на верхний левый угол

Установка расположения

Параметры x и y позволяют задать точные параметры расположения относительно верхнего левого угла контейнера:

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.place(x=20, y=30)

root.mainloop()

В данном случае кнопка относительно верхнего левого угла контейнера спещена на 20 единиц по оси X и на 30 единиц по оси Y:

Параметры relx и rely также позволяют сместить виджет, но в качестве значения используется число float в промежутке между 0.0 и 1.0, которое указывает на долю от высоты и ширины родительского контейнера:

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.place(relx=0.4, rely=0.25)

root.mainloop()

В данном случае кнопка смещена относительно верхнего левого угла контейнера на 40% ширины контейнера по оси Х и на 25% высоты контейнера по оси Y.

Anchor

Параметр anchor помещает виджет в определенной части контейнера. Может принимать следующие значения:

  • n: положение вверху по центру
  • e: положение в правой части контейнера по центру
  • s: положение внизу по центру
  • w: положение в левой части контейнера по центру
  • nw: положение в верхнем левом углу
  • ne: положение в верхнем правом углу
  • se: положение в нижнем правом углу
  • sw: положение в нижнем левом углу
  • center: положение центру

Схематически это выглядит следующим образом:

Стоит отметить, что значение в кавычках для параметра anchor передается в нижнем регистре, без кавычек – в верхнем регистре

btn.pack(anchor=NW)

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

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.place(relx=.5, rely=.5, anchor="c")

root.mainloop()

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

Размеры

Параметры height и width устанавливают соответственно высоту и ширину элемента в пикселях:

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.place(relx=0.5, rely=0.5, anchor="c", width=80, height=40)

root.mainloop()

Здесь кнопка имеет ширину в 80 единиц и высоту в 40 единиц.

Параметры relheight и relwidth также задают соответственно высоту и ширину элемента, но в качестве значения используется число float в промежутке между 0.0 и 1.0, которое указывает на долю от высоты и ширины родительского контейнера:

from tkinter import *
from tkinter import ttk

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

btn = ttk.Button(text="Click me")
btn.place(relx=0.5, rely=0.5, anchor="c", relwidth=0.33, relheight=0.25)

root.mainloop()

Здесь ширина кнопки составляет треть ширины контейнера, а высота кнопки – четверть высоты контейнера. И по мере изменения размеров контейнера размеры кнопки тоже будут изменяться.

Позиционирование. Grid

Метод grid() позволяет поместить виджет в определенную ячейку условной сетки или грида.

Метод grid применяет следующие параметры:

  • column: номер столбца, отсчет начинается с нуля
  • row: номер строки, отсчет начинается с нуля
  • columnspan: сколько столбцов должен занимать элемент
  • rowspan: сколько строк должен занимать элемент
  • ipadx и ipady: отступы по горизонтали и вертикали соответственно от границ элемента до его содержимого
  • padx и pady: отступы по горизонтали и вертикали соответственно от границ ячейки грида до границ элемента
  • sticky: выравнивание элемента в ячейке, если ячейка больше элемента. Может принимать значения n, e, s, w, ne, nw, se, sw, которые указывают соответствующее направление выравнивания

Установка ячейки виджета

Например, определим грид из 9 кнопок:

from tkinter import *
from tkinter import ttk

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

for r in range(3):
for c in range(3):
btn = ttk.Button(text=f"({r},{c})")
btn.grid(row=r, column=c)

root.mainloop()

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

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

Конфигурация грида

Для конфигурации грида в контейнере применяются два метода:

container.columnconfigure(index, weight)
container.rowconfigure(index, weight)

Метод columnconfigure() настраивает столбец. В качестве параметра index метод получает индекс столбца, а через параметр weight устанавливает его вес. Столбцы распределяются по всей ширине контейнера в соответствии со своим весом.

Метод rowconfigure() настраивает строку аналогичным образом. В качестве параметра index метод получает индекс строки, а через параметр weight устанавливает ее вес. Строки распределяются по всей длине контейнера в соответствии со своим весом.

Например, изменим код выше, добавив конфигурацию строк и столбцов:

from tkinter import *
from tkinter import ttk

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

for c in range(3): root.columnconfigure(index=c, weight=1)
for r in range(3): root.rowconfigure(index=r, weight=1)

for r in range(3):
for c in range(3):
btn = ttk.Button(text=f"({r},{c})")
btn.grid(row=r, column=c)

root.mainloop()

Поскольку у нас три строки, для упрощения в цикле для каждой строки устанавливаем вес 1. То есть в итоге каждая из трех строк будет занимать треть высоты контейнера (пространство_контейнера / сумму всех весов строк).

Аналогично в цикле для каждого столбца устанавливаем вес 1. То есть в итоге каждый из трех столбец будет занимать треть ширины контейнера.

Для параметров padx и pady можно установить отступы с двух сторон в виде списка:

btn.grid(row=r, column=c, ipadx=6, ipady=6, padx=[15, 4], pady=4)

Здесь внешний отступ слева равен 10, а справа – 4 единицам.

Объединение ячеек

Параметр columnspan указывает, столько столбцов, а параметр

rowspan сколько строк должен занимать виджет. То есть с помощью подобных параметров мы можем объединить ячейки.

Растяжение на два столбца:

from tkinter import *
from tkinter import ttk

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

for c in range(2): root.columnconfigure(index=c, weight=1)
for r in range(2): root.rowconfigure(index=r, weight=1)

btn1 = ttk.Button(text="button 1")
# columnspan=2 - растягиваем на два столбца
btn1.grid(row=0, column=0, columnspan=2, ipadx=70, ipady=6, padx=5, pady=5)

btn3 = ttk.Button(text="button 3")
btn3.grid(row=1, column=0, ipadx=6, ipady=6, padx=5, pady=5)

btn4 = ttk.Button(text="button 4")
btn4.grid(row=1, column=1, ipadx=6, ipady=6, padx=5, pady=5)

root.mainloop()

Растяжение на две строки:

from tkinter import *
from tkinter import ttk

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

for c in range(2): root.columnconfigure(index=c, weight=1)
for r in range(2): root.rowconfigure(index=r, weight=1)

btn2 = ttk.Button(text="button 2")
# rowspan=2 - растягиваем на две строки
btn2.grid(row=0, column=1, rowspan=2, ipadx=6, ipady=55, padx=5, pady=5)

btn1 = ttk.Button(text="button 1")
btn1.grid(row=0, column=0, ipadx=6, ipady=6, padx=5, pady=5)

btn3 = ttk.Button(text="button 3")
btn3.grid(row=1, column=0, ipadx=6, ipady=6, padx=5, pady=5)

root.mainloop()

Выравнивание

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

  • n: положение вверху по центру
  • e: положение в правой части контейнера по центру
  • s: положение внизу по центру
  • w: положение в левой части контейнера по центру
  • nw: положение в верхнем левом углу
  • ne: положение в верхнем правом углу
  • se: положение в нижнем правом углу
  • sw: положение в нижнем левом углу
  • ns: растяжение по вертикали
  • ew: растяжение по горизонтали
  • nsew: растяжение по горизонтали и вертикали

По умолчанию виджет позиционируется по центру ячейки

Наглядно растяжение по вертикали и горизонтали

Стоит отметить, что значение в кавычках для параметра anchor передается в нижнем регистре, без кавычек – в верхнем регистре

sticky=NW
sticky="nw"

Например, растянем виджет по всему пространству ячейки (значение NSEW):

from tkinter import *
from tkinter import ttk

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

for c in range(3): root.columnconfigure(index=c, weight=1)
for r in range(3): root.rowconfigure(index=r, weight=1)

for r in range(3):
for c in range(3):
btn = ttk.Button(text=f"({r},{c})")
btn.grid(row=r, column=c, ipadx=6, ipady=6, padx=4, pady=4, sticky=NSEW)

root.mainloop()

Обработка событий

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

def click(): 
print("Hello")

btn = ttk.Button(text="Click", command=click)

Ряд виджетов также позволяют с помощью параметра command задать обработчик для одного из событий данного виджета.

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

  • Activate: окно становится активным.
  • Deactivate: окно становится неактивным.
  • MouseWheel: прокрутка колеса мыши.
  • KeyPress: нажатие клавиши на клавиатуре.
  • KeyRelease: освобождение нажатой клавиши
  • ButtonPress: нажатие кнопки мыши.
  • ButtonRelease: освобождение кнопки мыши.
  • Motion: движение мыши.
  • Configure: изменение размера и положения виджета
  • Destroy: удаление виджета
  • FocusIn: получение фокуса
  • FocusOut: потеря фокуса.
  • Enter: указатель мыши вошел в пределы виджета.
  • Leave: указатель мыши покинул виджет.

Привязка событий

Для привязки события к виджету применяется метод bind():

	
bind(событие, функция)

В качестве первого параметра указывается обрабатываемое событие, а в качестве второго – функция, которая обрабатывает событие.

Например, обработаем события получения и потери фокуса для кнопки:

from tkinter import *
from tkinter import ttk


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

def entered(event):
btn["text"] ="Entered"

def left(event):
btn["text"] ="Left"

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

btn.bind("<Enter>", entered)
btn.bind("<Leave>", left)

root.mainloop()

Название событие передается в угловных скобках, например, “<Enter>” или “<Leave>”. Для события Enter (получение фокуса) определен обработчик-функция entered, которая изменяет текст кнопки:

def entered(event): 
btn["text"] ="Entered"

Стоит обратить внимание, что функция обработки события должна принимать в качестве параметра объект события – в примере выше параметр event, даже если он в самой функции не используется.

Событие потери фокуса связывыется с функцией left:

btn.bind("<Leave>", left)

Кроме обычных событий некоторые виджеты Tkinter могут использовать виртуальные события или высокоуровневые события (выше были описаны низкоуровневые события), которые помещаются в двойные угловные скобки, например, событие выделения списка “<<ListboxSelect>>”. Наиболее используемые виртуальные события будут рассмотрены в соответствующих темах про виджеты.

Шаблон события

В примере выше при привязке события указывалось только имя события например, “<Enter>” или “<Leave>”. Но в реальности в угловых скобках указывается не просто имя события, а его шаблон. Шаблон события имеет следующую форму:

<модификатор-имя_события-клавиша>

Модификаторы события

Часто используемые модификаторы:

  • Alt: нажата клавиша Alt
  • Control: нажата клавиша Ctrl
  • Shift: нажата клавиша Shift
  • Any: нажата любая клавиша

Клавиши

Также в шаблоне можно указать конкретные клавиши или комбинации. Некоторые из них:

  • Alt_L: левая клавиша alt
  • Alt_R: правая клавиша alt
  • BackSpace: клавиша backspace
  • Cancel: клавиша break
  • Caps_Lockклавиша CapsLock
  • Control_L: левая клавиша control
  • Control_R: правая клавиша control
  • Delete: клавиша Delete
  • Down: клавиша ↓
  • End: клавиша end
  • Escape: клавиша esc
  • Execute: клавиша SysReq
  • F1: клавиша F1
  • F2: клавиша F2
  • Fi: функциональная клавиша Fi
  • F12: клавиша F12
  • Home: клавиша home
  • Insert: клавиша insert
  • Left: клавиша ←
  • Linefeed: клавиша Linefeed (control-J)
  • KP_0: клавиша 0
  • KP_1: клавиша 1
  • KP_2: клавиша 2
  • KP_3: клавиша 3
  • KP_4: клавиша 4
  • KP_5: клавиша 5
  • KP_6: клавиша 6
  • KP_7: клавиша 7
  • KP_8: клавиша 8
  • KP_9: клавиша 9
  • KP_Add: клавиша +
  • KP_Begin: центральная клавиша (5)
  • KP_Decimal: клавиша точка (.)
  • KP_Delete: клавиша delete
  • KP_Divide: клавиша /
  • KP_Down: клавиша ↓
  • KP_End: клавиша end
  • KP_Enter: клавиша enter
  • KP_Home: клавиша home
  • KP_Insert: клавиша insert
  • KP_Left: клавиша ←
  • KP_Multiply: клавиша ×
  • KP_Next: клавиша PageDown
  • KP_Prior: клавиша PageUp
  • KP_Right: клавиша →
  • KP_Subtract: клавиша –
  • KP_Up: клавиша ↑
  • Next: клавиша PageDown
  • Num_Lock: клавиша NumLock
  • Pause: клавиша pause
  • Print: клавиша PrintScrn
  • Prior: клавиша PageUp
  • Return: клавиша Enter
  • Right: клавиша →
  • Scroll_Lock: клавиша ScrollLock
  • Shift_L: левая клавиша shift
  • Shift_R: правая клавиша shift
  • Tab: клавиша tab

Например

from tkinter import *
from tkinter import ttk

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

def single_click(event):
btn["text"] ="Single Click"

def double_click(event):
btn["text"] ="Double Click"

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

btn.bind("<ButtonPress-1>", single_click)
btn.bind("<Double-ButtonPress-1>", double_click)

root.mainloop()

Здесь в шаблоне “<ButtonPress-1>” ButtonPress – название события – нажатие кнопки мыши, а “1” указывает на конкретную кнопку – левую кнопку мыши (например, 3 – представляет правую кнопку)

А в шаблоне “<Double-ButtonPress-1>” добавляется модификатор Doubles, который указывает на двойное нажатие.

Глобальная регистрация события

В примерах выше обработка события устанавливалась для одного конкретного объекта – для одной кнопки. Но что, если у нас много кнопок и мы хотим, чтобы для всех была установлена привязка одного и тоже события с одной и той же функцией_обработчиком? В этом случае мы можем установить привязку события глобально ко всем объектам класса с помощью метода bind_class класса Tk:

from tkinter import *
from tkinter import ttk

clicks = 0

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

def clicked(event):
global clicks
clicks = clicks + 1
btn["text"] =f"{clicks} Clicks"

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

# привязка события к кнопкам ttk.Button
root.bind_class("TButton", "<Double-ButtonPress-1>", clicked)

root.mainloop()

В данном случае для кнопок для обработки двойного нажатия установаливается обработчик – функция clicked. Причем события привязывается к кнопкам из пакета tkinter.ttk, поэтому в качестве типа виджетов используется “TButton” (а не просто Button).

Удаление события

Для открепления события от виджета вызывается метод unbind(), в который передается шаблон события:

widget.unbind(event)

Текстовая метка Label

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

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

	
Label(master, options)

Параметр master представляет ссылку на родительский контейнер, а параметр options представляет следующие именованные параметры

  • anchor: устанавливает позиционирование текста
  • background: фоновый цвет
  • borderwidth: толщина границы метки
  • cursor: курсор указателя мыши при наведении на метку
  • font: шрифт текста
  • foreground: цвет текста
  • height: высота виджета
  • image: ссылка на изображение, которое отображается на метке
  • justify: устанавливает выравнивание текста. Значение LEFT выравнивает текст по левому краю, CENTER – по центру, RIGHT – по правому краю
  • pading: отступы от границ вилжета до его текста
  • relief: определяет тип границы, по умолчанию значение FLAT
  • text: устанавливает текст метки
  • textvariable: устанавливает привязку к элементу StringVar
  • underline: указывает на номер символа в тексте метки, который подчеркивается. По умолчанию значение -1, то есть никакой символ не подчеркивается
  • width: ширина виджета
  • wraplength: при положительном значении строки текста будут переносится для вмещения в пространство виджета

Выведем в окне приложения простейший текст:

from tkinter import *
from tkinter import ttk

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

label = ttk.Label(text="Hello METANIT.COM")
label.pack()

root.mainloop()

Установка шрифта

Параметр font принимает определение шрифта в виде:

font = ("имя шрифта", размер_шрифта)

Первое значение передает имя шрифта в кавычках, а второе – числовой размер шрифта. Например, установим шрифт Arial высотой в 14 единиц:

from tkinter import *
from tkinter import ttk

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

label = ttk.Label(text="Hello METANIT.COM", font=("Arial", 14))
label.pack()

root.mainloop()

Установка изображения

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

from tkinter import *
from tkinter import ttk

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

python_logo = PhotoImage(file="./python_logo.png")

label = ttk.Label(image=python_logo)
label.pack()

root.mainloop()

В моем случае изображение представляет файл python_logo.png, которое находится в одной папке с файлом приложения и которое изображает логотип python:

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

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

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

from tkinter import *
from tkinter import ttk

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

python_logo = PhotoImage(file="./python_logo.png")

label = ttk.Label(image=python_logo, text="Python", compound="top")
label.pack()

root.mainloop()

Стилизация

По умолчанию метка не имеет границы. Для установки толщины границы используется параметр borderwidth, при этом нам также надо установить тип границы с помощью параметра releaf, который может принимать значения: “flat”, “raised”, “sunken”, “ridge”, “solid” и “groove”:

from tkinter import *
from tkinter import ttk

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


label = ttk.Label(text="Hello Tkinter", borderwidth=2, relief="ridge", padding=8)
label.pack(expand=True)

root.mainloop()

Установка цвета фона и текста:

from tkinter import *
from tkinter import ttk

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


label = ttk.Label(text="Hello Tkinter", background="#FFCDD2", foreground="#B71C1C", padding=8)
label.pack(expand=True)

root.mainloop()

Поле ввода Entry

Элемент Entry представляет поле для ввода текста. С помощью конструктора Entry можно установить ряд параметров, основные из них:

  • background: фоновый цвет
  • cursor: курсор указателя мыши при наведении на текстовое поле
  • foreground: цвет текста
  • font: шрифт текста
  • justify: устанавливает выравнивание текста. Значение LEFT выравнивает текст по левому краю, CENTER – по центру, RIGHT – по правому краю
  • show: задает маску для вводимых символов
  • state: состояние элемента, может принимать значения NORMAL (по умолчанию) и DISABLED
  • textvariable: устанавливает привязку к элементу StringVar
  • width: ширина элемента

Элемент Entry имеет ряд методов. Основные из них:

  • insert(index, str): вставляет в текстовое поле строку по определенному индексу
  • get(): возвращает введенный в текстовое поле текст
  • delete(first, last=None): удаляет символ по индексу first. Если указан параметр last, то удаление производится до индекса last. Чтобы удалить до конца, в качестве второго параметра можно использовать значение END.
  • focus(): установить фокус на текстовое поле

Простейшее текстовое поле:

from tkinter import *
from tkinter import ttk

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

ttk.Entry().pack(anchor=NW, padx=8, pady= 8)

root.mainloop()

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

Для получения текста из Entry, можно использовать его метод get(). Так, определим элемент Entry и по нажатию на кнопку выведем введенный текст на текстовую метку:

from tkinter import *
from tkinter import ttk

def show_message():
label["text"] = entry.get() # получаем введенный текст

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

entry = ttk.Entry()
entry.pack(anchor=NW, padx=6, pady=6)

btn = ttk.Button(text="Click", command=show_message)
btn.pack(anchor=NW, padx=6, pady=6)

label = ttk.Label()
label.pack(anchor=NW, padx=6, pady=6)

root.mainloop()

Вставка и удаление текста

Рассмотрим вставку и удаление текста в Entry:

from tkinter import *
from tkinter import ttk

def clear():
entry.delete(0, END) # удаление введенного текста

def display():
label["text"] = entry.get() # получение введенного текста

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

label = ttk.Label()
label.pack(anchor=NW, padx=6, pady=6)

entry = ttk.Entry()
entry.pack(anchor=NW, padx=6, pady=6)

# вставка начальных данных
entry.insert(0, "Hello World")

display_button = ttk.Button(text="Display", command=display)
display_button.pack(side=LEFT, anchor=N, padx=6, pady=6)

clear_button = ttk.Button(text="Clear", command=clear)
clear_button.pack(side=LEFT, anchor=N, padx=6, pady=6)

root.mainloop()

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

entry.insert(0, "Hello World")

Кнопка Clear очищает оба поля, вызывая метод delete:

def clear():
entry.delete(0, END)

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

def display():
label["text"] = entry.get()

 from tkinter import *
root = Tk()
root.title('Мой первый графический интерфейс')
root.geometry('350x250')

def get(event): # Срабатывает когда нажимаем Enter
a = en_1.get() # Записываем введенный текст в переменную "а"
print(a) # выводим в консоль переменную "а"

def insert(event): # Срабатывает когда нажимаем пробел
en_1.insert(END, ' МИР ') # Вставляет текст в текстовое поле

def delete(event): # Срабатывает когда нажимаем правую кнопку мыши
en_1.delete(0, END) # Удаляет текст в текстовое поле

en_1 = Entry(font='Hack 20') # создаем однострочное текстовое поле
en_1.bind('<Return>', get) # Обработчик событий нажатия Enter
en_1.bind('<space>', insert) # Обработчик событий нажатия пробел
en_1.bind('<Button-3>', delete) # Обработчик событий нажатия правой кнопкой мыши
en_1.pack()
root.mainloop()

Метод «get()» – позволяет из текстового поля брать текст. Создадим текстовое поле «en_1», добавим обработку событий при нажатии кнопки «Enter», который будет записывать текст в переменную «а» и выводить его в терминал.

Метод «insert()» – позволяет вставить текст в текстовое поле. При этом получает индекс элемента перед которым вставить текст и сам текст. Если передать «0» – в самое начало, если «END» – то в самый конец. Добавим обработчик событий, по нажатию на пробел, он будет вставлять слово « МИР » в наше текстовое поле. Передадим с начало «0». Запустим программу, введем в текстовое поле «Привет» и нажмем «пробел». Видим что слово «МИР» вставилось в самом начале. Передадим «3», слово «МИР» вставилось перед третьим индексом строки «Привет». И передадим «END» теперь слово «МИР» вставилось в самый конец.

Метод «delete()» – удаляет символ, срез или полностью очищает поле. Может принимать один или два аргумента. Если передать один аргумент удаляет элемент данного индекса, если передать два аргумента удалит срез между двумя индексами, не включая последний. А если передать первый аргумент «0», а второй «END», то полностью очистит текстовое поле. Добавим, обработчик событий, который будет удалять элемент или срез при нажатии на правую кнопку мыши. Передадим один аргумент «3». Запустим программу, введем слово «КЛАВИАТУРА» и кликнем правой кнопкой мыши. «В» удалилась, осталось «КЛАИАТУРА», еще раз кликаем и у нас удаляется символ «И» который стоял под индексом № 3.

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

import tkinter as tk

root = tk.Tk()
root.geometry("400x50")


def setTextInput(text):
textExample.delete(0, "end")
textExample.insert(0, text)


textExample = tk.Entry(root)
textExample.pack()

btnSet = tk.Button(
root, height=1, width=10, text="Set", command=lambda: setTextInput("new content")
)
btnSet.pack()

root.mainloop()

delete Метод Entry удаляет указанный диапазон символов в Entry.

0 является первым символом и "end"последним символом содержимого виджета Entry. Таким образом, delete(0, "end")удаляет все содержимое внутри Entryполя.

textExample.insert(0, text)

insert Метод вставляет текст в указанную позицию. В коде выше он вставляет 
text в начало.

textExample.insert(0, text)

Метод Tkinter StringVarдля установки содержимого Entryвиджета Tkinter

Если содержимое Entryвиджета Tkinter связано с StringVar объектом, он может автоматически изменять содержимое виджета Tkinter каждый раз при обновлении Entry значения .StringVar

import tkinter as tk

root = tk.Tk()
root.geometry("400x50")


def setTextInput(text):
textEntry.set(text)


textEntry = tk.StringVar()

textExample = tk.Entry(root, textvariable=textEntry)
textExample.pack()

btnSet = tk.Button(
root, height=1, width=10, text="Set", command=lambda: setTextInput("new content")
)
btnSet.pack()

root.mainloop()

textEntry является StringVar объектом и связан с текстовым содержимым или, другими словами, 
textvariable опцией в Entry виджете.

textEntry = tk.StringVar()

textExample = tk.Entry(root, textvariable=textEntry)

Если textEntry значение обновлено и имеет новое значение 
text, то виджеты, textvariable связанные с ним, будут автоматически обновлены.

textEntry.set(text)

В Tkinter есть два метода установки текста по умолчанию для виджета Tkinter Entry.

  1. insertМетод Tkinter
  2. StringVarМетод Tkinter

1. insert Метод установки текста Entry виджета по умолчанию

Виджет Tkinter Entry не имеет специального text свойства для установки текста по умолчанию, например text="example". Он имеет insertметод для вставки текста виджета Entry, чтобы он мог эквивалентно установить текст по умолчанию, Entry если мы вызовем insert метод после Entry инициализации объекта.

import tkinter as tk

root = tk.Tk()
root.geometry("200x100")

textExample = tk.Entry(root)
textExample.insert(0, "Default Text")
textExample.pack()

root.mainloop()

insert Метод вставляет текст в указанную позицию. 
0— первый символ, поэтому он вставляется 
Default Text в начало.

textExample.insert(0, "Default Text")

2. Метод Tkinter StringVarдля установки текста по умолчанию для Entryвиджета Tkinter

textvariable связывает содержимое виджета Entry с переменной Tkinter StringVar. Он может установить StringVar для установки текста по умолчанию Entry виджета после создания правильной ассоциации.

import tkinter as tk

root = tk.Tk()
root.geometry("200x100")

textEntry = tk.StringVar()
textEntry.set("Default Text")
textExample = tk.Entry(root, textvariable=textEntry)

textExample.pack()

root.mainloop()

textEntry является 
StringVar переменной и связана с текстовым содержимым 
Entry объекта с помощью textvariable = textEntry.

textEntry = tk.StringVar()
textEntry.set("Default Text")
textExample = tk.Entry(root, textvariable=textEntry)

Как установить текст виджета Tkinter Entry с помощью кнопки

У нас есть два способа задать или изменить текст Entry виджета Tkinter, нажав кнопку Tkinter :

  1. Tkinter deleteи insert метод
  2. StringVar Метод Tkinter

1. Tkinter delete и insert метод установки содержимого Entry

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

import tkinter as tk

root = tk.Tk()
root.geometry("400x50")


def setTextInput(text):
textExample.delete(0, "end")
textExample.insert(0, text)


textExample = tk.Entry(root)
textExample.pack()

btnSet = tk.Button(
root, height=1, width=10, text="Set", command=lambda: setTextInput("new content")
)
btnSet.pack()

root.mainloop()

delete Метод Entryудаляет указанный диапазон символов в Entry.

0 является первым символом и "end"последним символом содержимого виджета Entry. Таким образом, delete(0, "end") удаляет все содержимое внутри Entryполя.

textExample.delete(0, "end")

insertМетод вставляет текст в указанную позицию. В коде выше он вставляет text в начало.

textExample.insert(0, text)

2. Метод Tkinter StringVarдля установки содержимого Entryвиджета Tkinter

Если содержимое Entry виджета Tkinter связано с StringVar объектом, он может автоматически изменять содержимое виджета Tkinter каждый раз при обновлении Entry значения .StringVar.

import tkinter as tk

root = tk.Tk()
root.geometry("400x50")


def setTextInput(text):
textEntry.set(text)


textEntry = tk.StringVar()

textExample = tk.Entry(root, textvariable=textEntry)
textExample.pack()

btnSet = tk.Button(
root, height=1, width=10, text="Set", command=lambda: setTextInput("new content")
)
btnSet.pack()

root.mainloop()

textEntry является StringVar объектом и связан с текстовым содержимым или, другими словами, 
textvariable опцией в Entry виджете.

textEntry = tk.StringVar()

textExample = tk.Entry(root, textvariable=textEntry)

Если textEntry значение обновлено и имеет новое значение 
text, то виджеты, textvariable связанные с ним, будут автоматически обновлены.

textEntry.set(text)

Пример 6

Вырезать, копировать и вставить данные в виджет «Текст»

Мы скопируем (или вырежем) данные из одного текстового поля и вставим их в другой текстовый виджет.

Tkinter выбирает все данные из текстового поля и копирует или вырезает в буфер обмена, чтобы вставить в другое текстовое поле.

Весь код:

import tkinter as tk
my_w = tk.Tk()
my_w.geometry("400x350")
my_w.title("plus2net.com") # Добавление заголовка
global data

l1 = tk.Label(my_w, text='Name',font=20) # добавлен один Label
l1.grid(row=0,column=0,padx=2,pady=10)

e1 = tk.Text(my_w,font=20,height=4,width=28,bg='yellow') # текстовое полн
e1.grid(row=0,column=1,columnspan=4)

def select_all(): # чтобы выделить весь текст внутри текстового поля
e1.tag_add("sel", "1.0","end") # all text selected
e1.tag_config("sel",background="green",foreground="red")

def cut_select(): # Вырезать выделенный текст в буфер обмена
global data
if e1.selection_get():
data=e1.selection_get() # скопировать выделенный текст в буфер обмена
e1.delete('sel.first','sel.last') # удалить выделенный текст

def copy_select(): # скопировать выделенный текст в буфер обмена
global data
if e1.selection_get():
data=e1.selection_get() # скопировать выделенный текст в буфер обмена

def paste_select():
global data
e2.insert(tk.END,data) # Вставка данных из буфера обмена

b1=tk.Button(my_w,text='Select All',command=lambda:select_all(),
font=20,bg='lightgreen')
b1.grid(row=1,column=1,padx=2,pady=5)

b2=tk.Button(my_w,text='Cut',command=lambda:cut_select(),
font=20,bg='lightyellow')
b2.grid(row=1,column=2)

b3=tk.Button(my_w,text='Copy',command=lambda:copy_select(),
font=20,bg='lightblue')
b3.grid(row=1,column=3)

b4=tk.Button(my_w,text='Paste',command=lambda:paste_select(),
font=20,bg='cyan')
b4.grid(row=1,column=4)

e2 = tk.Text(my_w,font=20,height=4,width=28,bg='yellow') # добавлено одно поле для ввода
e2.grid(row=2,column=1,columnspan=4,pady=10)

my_w.mainloop()

Button b1Select AllИспользуется для выбора всех данных в текстовом поле e1.
Button b2CutВырезать выбранные данные e1 и сохранить в буфере обмена.
Button b3CopyКопировать выбранные данные e1 и сохранить в буфере обмена.
Button b4PasteВставить данные буфера обмена в текст e2.

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

При нажатии кнопки b1 команда вызовет функцию select_all() .

def select_all(): # to select all text inside Text box 
    e1.tag_add("sel", "1.0","end") # all text selected
    e1.tag_config("sel",background="green",foreground="red")

Вырезать или скопировать выделенный текст

Данные будут перемещены в буфер обмена. Наша кнопка b2 вырежет выделенный текст, а b3 скопирует выделенный текст.

b2=tk.Button(my_w,text='Cut',command=lambda:cut_select(),
    font=20,bg='lightyellow')
def cut_select(): # Cut the selection of text to clipboard 
    global data 
    if e1.selection_get():
        data=e1.selection_get() # copy selected text to clipboard 
        e1.delete('sel.first','sel.last') # delete selected text 

Скопировать выделенный текст

b3=tk.Button(my_w,text='Copy',command=lambda:copy_select(),
    font=20,bg='lightblue')
def copy_select(): # copy selected text to clipboard
    global data 
    if e1.selection_get():
        data=e1.selection_get() # copy selected text to clipboard

Вставка данных из буфера обмена в виджет «Текст»

Здесь e2 — еще один текстовый виджет, куда будут вставлены данные из буфера обмена.

b4=tk.Button(my_w,text='Paste',command=lambda:paste_select(),
    font=20,bg='cyan')
def paste_select():
    global data
    e2.insert(tk.END,data) # Paste data from clipboard

Валидация

С помощью параметра validate конструктора Entry можно задать, когда проводить валидацию введенного значения. Этот параметр может принимать следующие значения:

  • none: отсутствие валидации, значение по умолчанию
  • focus: валидация при получении фокуса
  • focusin: валидация при изменении фокуса
  • focusout: валидация при потере фокуса
  • key: валидация при каждом вводе нового символа
  • all: валидация при измении фокуса и вводе символов в поле

Параметр validatecommand позволяет установить команду валидации.

Рассмотрим небольшой пример. Допустим, пользовтаель должен ввести номер телефона в формете +xxxxxxxxxxx. То есть сначала должен идти знак +, а затем 11 цифр, например, +12345678901:

from tkinter import *
from tkinter import ttk
import re

def is_valid(newval):
return re.match("^\+\d{0,11}$", newval) is not None

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

check = (root.register(is_valid), "%P")

phone_entry = ttk.Entry(validate="key", validatecommand=check)
phone_entry.pack(padx=5, pady=5, anchor=NW)

root.mainloop()

Итак, параметр validate="key" указывает, что мы будем валидировать ввод при каждом нажати на клавиатуру. Параметр validatecommand=check говорит, что валидировать ввод будет команда “check”. Эта команда представляет кортеж из двух элементов:

check = (root.register(is_valid), "%P")

Первый элемент – вызов метода root.register(is_valid) регистрирует функцию, которая собственно будет производить валидацию – это функция is_valid(). Второй элемент – подстановка “%P” представляет новое значение, которое передается в функцию валидации.

Собственно саму валидацию выполняет функция is_valid(). Она принимает один параметр – текущее значение Entry, которое надо валидировать. Она возвращает True, если значение прошло валидацию, и False, если не прошло. Сама логика валидации представляет проверку строки на регулярное выражение "^\+\d*$". Если новое значение соответствует этому выражению, и в нем не больше 12 символов, то оно прошло валидацию.

В итоге мы сможем ввести в текстовое поле только символ + и затем только 11 цифр.

Теперь немного изменим код и добавим вывод ошибок валидации:

from tkinter import *
from tkinter import ttk
import re

def is_valid(newval):
result= re.match("^\+{0,1}\d{0,11}$", newval) is not None
if not result and len(newval) <= 12:
errmsg.set("Номер телефона должен быть в формате +xxxxxxxxxxx, где x представляет цифру")
else:
errmsg.set("")
return result

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

check = (root.register(is_valid), "%P")

errmsg = StringVar()

phone_entry = ttk.Entry(validate="key", validatecommand=check)
phone_entry.pack(padx=5, pady=5, anchor=NW)

error_label = ttk.Label(foreground="red", textvariable=errmsg, wraplength=250)
error_label.pack(padx=5, pady=5, anchor=NW)

root.mainloop()

Здесь для вывода ошибок валидации добавлен виджет Label. Если введенное значение не соответствует регулярному выражению (например, пользователь попытался ввести нецифровой символ), и длина ввода меньше и равно 12 символов (проверять ввод больше 12 символов нет смысла, так как номер телефона содержит только 12 символов), то в метке выводим сообщение об ошибке

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

check = (root.register(is_valid), "%P", "%V")

Здесь значение “%V” представляет событие, которое вызывает валидацию (focus/focusin/focusout/key). Тогда в функции валидации с помощью второго параметра мы сможем получить это значение:

def is_valid(newval, op):
result= re.match("^\+\d{0,11}$", newval) is not None
if op=="key":
# некоторые действия
elif op=="focus":
# некоторые действия
return result

Привязка виджетов к переменным

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

  • StringVar
  • IntVar
  • BooleanVar
  • DoubleVar

Простейший пример:

from tkinter import *
from tkinter import ttk

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

message = StringVar()

label = ttk.Label(textvariable=message)
label.pack(anchor=NW, padx=6, pady=6)

entry = ttk.Entry(textvariable=message)
entry.pack(anchor=NW, padx=6, pady=6)

button = ttk.Button(textvariable=message)
button.pack(side=LEFT, anchor=N, padx=6, pady=6)

root.mainloop()

В данном случае определяется переменная message, которая представляет класс StringVar, то есть такая переменная, которая хранит некоторую строку.

С помощью параметра textvariable эта переменная привязана к тексту поля Entry, а также к тексту кнопки и метки:

ttk.Label(textvariable=message)

И если мы изменим текст в поле Entry, автоматически синхронно изменится и значение привязанной переменной message. а поскольку к этой переменной также привязаны кнопка и метка, то автоматически также изменится текст метки и кнопки.

Типы имеют параметр value, который позволяет установить значение по умолчанию. Кроме того, они имеют два метода:

  • get(): возвращает значение
  • set(value): устанавливает значение, которое передано через параметр

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

from tkinter import *
from tkinter import ttk

def click_button():
value = clicks.get() # получаем значение
clicks.set(value + 1) # устанавливаем новое значение


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

clicks = IntVar(value=0) # значение по умолчанию

btn = ttk.Button(textvariable=clicks, command=click_button)
btn.pack(anchor=CENTER, expand=1)

root.mainloop()

Отслеживание изменения переменной

Класс Stringvar позволяет отслеживать чтение и изменение своего значения. Для отслеживания у объекта StringVar вызывается метод trace_add()

trace_add(trace_mode, function)

Первый параметр представляет отслеживаемое событие и может принимать следующие значения:

  • write: изменение значения
  • read: чтение значения
  • unset: удаление значения

Также можно передать список из этих значений, если нам надо отслеживать несколько событий.

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

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

from tkinter import *
from tkinter import ttk

def check(*args):
print(name)
if name.get()=="admin":
result.set("запрещенное имя")
else:
result.set("норм")

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

name = StringVar()
result = StringVar()

name_entry = ttk.Entry(textvariable=name)
name_entry.pack(padx=5, pady=5, anchor=NW)

check_label = ttk.Label(textvariable=result)
check_label.pack(padx=5, pady=5, anchor=NW)

# отслеживаем изменение значения переменной name
name.trace_add("write", check)

root.mainloop()

В данном случае текстовое поле name_entry привязано к переменной name, а метка check_label – к переменной result.

Здесь мы отлеживаем изменение значения переменной name – при изменении срабатывает функция check, в которой изменяем переменную result в зависимости от значения name. Условимся, что name представляет имя пользователя, но имя “admin” запрещено.

Checkbutton

Элемент Checkbutton представляет собой флажок, который может находиться в двух состояниях: отмеченном и неотмеченном.

Конструктор Checkbutton принимает ряд параметров, отметим основные из них:

  • command: ссылка на функцию, которая вызывается при нажатии на флажок
  • cursor: курсор при наведении на элемент
  • image: графическое изображение, отображаемое на элементе
  • offvalue: значение флажка в неотмеченном состоянии, по умолчанию равно 0
  • onvalue: значение флажка в отмеченном состоянии, по умолчанию равно 1
  • padding: отступы от текста до границы флажка
  • state: состояние элемента, может принимать значения NORMAL (по умолчанию), DISABLED и ACTIVE
  • text: текст элемента
  • textvariable: привязанный к тексту объект StringVar
  • underline: индекс подчеркнутого символа в тексте флажка
  • variable: ссылка на переменную, как правило, типа IntVar, которая хранит состояние флажка
  • width: ширина элемента

Создадим простейший флажок:

from tkinter import *
from tkinter import ttk

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

enabled = IntVar()

enabled_checkbutton = ttk.Checkbutton(text="Включить", variable=enabled)
enabled_checkbutton.pack(padx=6, pady=6, anchor=NW)

enabled_label = ttk.Label(textvariable=enabled)
enabled_label.pack(padx=6, pady=6, anchor=NW)

root.mainloop()

Отличительной чертой Checkbutton является возможность привязки к переменной через параметр variable, который представляет значение флажка. Здесь данный параметр привязан к переменной enabled типа IntVar. В отмеченном состоянии привязанный объект IntVar имеет значение 1, а в неотмеченном – 0. В итоге через IntVar мы можем получать значение, указанное пользователем.

Обработка изменения флажка

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

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

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

def checkbutton_changed():
if enabled.get() == 1:
showinfo(title="Info", message="Включено")
else:
showinfo(title="Info", message="Отключено")

enabled = IntVar()

enabled_checkbutton = ttk.Checkbutton(text="Включить", variable=enabled, command=checkbutton_changed)
enabled_checkbutton.pack(padx=6, pady=6, anchor=NW)

root.mainloop()

Здесь при изменении состояния флажка срабатывает функция checkbutton_changed. В ней в зависимости от состояния флажка (а точнее в зависимости от значения переменной enabled) с помощью встроенной функции showinfo() отображаем сообщение о состоянии флажка:

onvalue и offvalue

Параметры onvalue и offvalue позволяют задать значение флажка в отмеченном и неотмеченном состоянии. По умолчанию они равны 1 и 0 соответственно. Однако мы можем передать им и другие, более удобные для нас значения.

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

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

def checkbutton_changed():
showinfo(title="Info", message=enabled.get())

enabled = StringVar()

enabled_checkbutton = ttk.Checkbutton(text="Включить", variable=enabled, offvalue="Отключено", onvalue="Включено", command=checkbutton_changed)
enabled_checkbutton.pack(padx=6, pady=6, anchor=NW)

root.mainloop()

Теперь переменная enabled представляет StringVar, то есть хранит строку. Соответственно параметры offvalue и onvalue тоже представляют строку

Текст флажка

Для установки текста флажка можно использовать параметры text и textvariable. Причем мы можем привязать текст флажка к его значению с помощью textvariable:

from tkinter import *
from tkinter import ttk

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

enabled_on = "Включено"
enabled_off = "Отключено"
enabled = StringVar(value=enabled_on)

enabled_checkbutton = ttk.Checkbutton(textvariable=enabled, variable=enabled, offvalue=enabled_off, onvalue=enabled_on)
enabled_checkbutton.pack(padx=6, pady=6, anchor=NW)

root.mainloop()

В данном случае для хранения текста в отмеченном и неотмеченном состояниях определены две переменные: enabled_on и enabled_off. Переменная enabled инициализируется тем же значением (enabled_on), что и параметр onvalue, поэтому по умолчанию флажок будет отмечен. А поскольку его параметры textvariable и variable привязаны к одной и той же переменной enabled, то они будет изменяться синхронно

Обработка нескольких флажков

Аналогичным образом можно использовать наборы флажков:

from tkinter import *
from tkinter import ttk

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

def select():
result = "Выбрано: "
if python.get() == 1: result = f"{result} Python"
if javascript.get() == 1: result = f"{result} JavaScript"
if java.get() == 1: result = f"{result} Java"
languages.set(result)

position = {"padx":6, "pady":6, "anchor":NW}

languages = StringVar()
languages_label = ttk.Label(textvariable=languages)
languages_label.pack(**position)

python = IntVar()
python_checkbutton = ttk.Checkbutton(text="Python", variable=python, command=select)
python_checkbutton.pack(**position)

javascript = IntVar()
javascript_checkbutton = ttk.Checkbutton(text="JavaScript", variable=javascript, command=select)
javascript_checkbutton.pack(**position)

java = IntVar()
java_checkbutton = ttk.Checkbutton(text="Java", variable=java, command=select)
java_checkbutton.pack(**position)

root.mainloop()

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

Radiobutton

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

Среди параметров конструктора Radiobutton стоит выделить следующие:

  • command: ссылка на функцию, которая вызывается при нажатии на переключатель
  • cursor: курсор при наведении на виджет
  • image: графическое изображение, отображаемое виджетом
  • padding: отступы от содержимого до границы переключателя
  • state: состояние виджета, может принимать значения NORMAL (по умолчанию), DISABLED и ACTIVE
  • text: текст виджета
  • textvariable: устанавливает привязку к переменной StringVar, которая задает текст переключателя
  • underline: индекс подчеркнутого символа в тексте виджета
  • variable: ссылка на переменную, как правило, типа IntVar, которая хранит состояние переключателя
  • value: значение переключателя
  • width: ширина виджета

Определим группу переключателей:

from tkinter import *
from tkinter import ttk

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

position = {"padx":6, "pady":6, "anchor":NW}

python = "Python"
java = "Java"
javascript = "JavaScript"

lang = StringVar(value=java) # по умолчанию будет выбран элемент с value=java

header = ttk.Label(textvariable=lang)
header.pack(**position)

python_btn = ttk.Radiobutton(text=python, value=python, variable=lang)
python_btn.pack(**position)

javascript_btn = ttk.Radiobutton(text=javascript, value=javascript, variable=lang)
javascript_btn.pack(**position)

java_btn = ttk.Radiobutton(text=java, value=java, variable=lang)
java_btn.pack(**position)

root.mainloop()

Здесь определено три переключателя. Все они привязаны к одной переменной lang, которая представляет тип StringVar. При этом они имеют разные значения, устанавливаемые через параметр value. Начальное значение переменной lang (“java”) соответствует значению value последнего переключателя, поэтому по умолчанию будет выбран последний переключатель.А при выборе одного переключателя, другой автоматически перейдет в неотмеченное состояние.

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

В примере выше отображаемый текст (параметр text) и значение (параметр value) совпадают, но это необязательно

Обработка выбора пользователя

Параметр command позволяет установить функцию, которая обрабатывает выбор переключателя. Например:

from tkinter import *
from tkinter import ttk

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

position = {"padx":6, "pady":6, "anchor":NW}
languages = ["Python", "JavaScript", "Java", "C#"]
selected_language = StringVar() # по умолчанию ничего не выборанно

header = ttk.Label(text="Выберите язык")
header.pack(**position)

def select():
header.config(text=f"Выбран {selected_language.get()}")

for lang in languages:
lang_btn = ttk.Radiobutton(text=lang, value=lang, variable=selected_language, command=select)
lang_btn.pack(**position)

root.mainloop()

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

Установка изображения

Для установки изображения применяется параметр image:

from itertools import chain
from tkinter import *
from tkinter import ttk

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

position = {"padx":6, "pady":6, "anchor":NW}

python = "Python"
java = "Java"
csharp = "C#"

lang = StringVar(value=java) # по умолчанию будет выбран элемент с value=java

header = ttk.Label(textvariable=lang)
header.pack(**position)

python_img = PhotoImage(file="./python_sm.png")
csharp_img = PhotoImage(file="./csharp_sm.png")
java_img = PhotoImage(file="./java_sm.png")

python_btn = ttk.Radiobutton( value=python, variable=lang, image=python_img)
python_btn.pack(**position)

csharp_btn = ttk.Radiobutton(value=csharp, variable=lang, image=csharp_img)
csharp_btn.pack(**position)

java_btn = ttk.Radiobutton(value=java, variable=lang, image=java_img)
java_btn.pack(**position)

root.mainloop()

Параметру image передается объект PhotoImage, в конструкторе которого через параметр file устанавливается путь к изображению. Здесь предполагается, что в одной папке с файлом приложения находятся файлы изображений “python_sm.png”, “csharp_sm.png” и “java_sm.png”.

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

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

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

from itertools import chain
from tkinter import *
from tkinter import ttk

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

position = {"padx":6, "pady":6, "anchor":NW}

languages = [
{"name": "Python", "img": PhotoImage(file="./python_sm.png")},
{"name": "C#", "img": PhotoImage(file="./csharp_sm.png")},
{"name": "Java", "img": PhotoImage(file="./java_sm.png")}
]


lang = StringVar(value=languages[0]["name"]) # по умолчанию будет выбран элемент с value=python

header = ttk.Label(textvariable=lang)
header.pack(**position)

for l in languages:
btn = ttk.Radiobutton(value=l["name"], text=l["name"], variable=lang, image=l["img"], compound="top")
btn.pack(**position)

root.mainloop()

Установка родительского контейнера

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

from tkinter import *
from tkinter import ttk

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

lbl = ttk.Label(text="Hello")
lbl.pack()

root.mainloop()

Здесь для метки lbl контейнером выступает главное окно – root. Однако графическое приложение может иметь более сложную структуру со множеством вложенных контейнеров. И для каждого виджета можно явным образом установить контейнер с помощью первого параметра конструктора, который называтся master. Например, в примере выше мы могли бы явным образом прописать для Label родительский контейнер:

lbl = ttk.Label(master=root, text="Hello")
# или так
lbl = ttk.Label(root, text="Hello")

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

Frame

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

  • borderwidth: толщина границы фрейма, по умолчанию равно 0
  • relief: определяет тип границы, может принимать значения SUNKEN, RAISED, GROOVE, RIDGE
  • cursor: устанавливает курсор при наведении на фрейм
  • height: высота фрейма
  • width: ширина фрейма
  • padding: отступы от вложенного содержимого до границ фрейма

Используем фреймы:

import re
from tkinter import *
from tkinter import ttk

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

frame = ttk.Frame(borderwidth=1, relief=SOLID, padding=[8, 10])
name_label = ttk.Label(frame, text="Введите имя")
name_label.pack(anchor=NW)

name_entry = ttk.Entry(frame)
name_entry.pack(anchor=NW)

frame.pack(anchor=NW, fill=X, padx=5, pady=5)

root.mainloop()

Здесь фрейм имеет границу толщиной в 1 пиксель. Граница представляет обычную линию (relief=SOLID). Также для фрейма заданы внутренние отступы: 8 по горизонтали и 10 по вертикали. Для установки отступов можно использовать следующие формы:

padding=10              # устанавливает общий доступ в 10 единиц
padding=[8, 10] # отступ по горизонтали - 8, отступ по вертикали - 10
padding=[8, 10, 6, 5] # отступ слева 8, сверху - 10, справа - 6 и снизу 5

В сам фрейм добавляются два других виджета: Label и Entry. Для этого для обоих виджетов указываем фрейм в качестве родительского контейнера.

При этом мы можем вынести во вне создание фрейма:

from tkinter import *
from tkinter import ttk

def create_frame(label_text):
frame = ttk.Frame(borderwidth=1, relief=SOLID, padding=[8, 10])
# добавляем на фрейм метку
label = ttk.Label(frame, text=label_text)
label.pack(anchor=NW)
# добавляем на фрейм текстовое поле
entry = ttk.Entry(frame)
entry.pack(anchor=NW)
# возвращаем фрейм из функции
return frame

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

name_frame = create_frame("Введите имя")
name_frame.pack(anchor=NW, fill=X, padx=5, pady=5)

email_frame = create_frame("Введите email")
email_frame.pack(anchor=NW, fill=X, padx=5, pady=5)

root.mainloop()

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

Listbox

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

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

  • listvariable: список элементов, которые добавляются в ListBox
  • bg: фоновый цвет
  • bd: толщина границы вокруг элемента
  • cursor: курсор при наведении на Listbox
  • font: настройки шрифта
  • fg: цвет текста
  • highlightcolor: цвет элемента, когда он получает фокус
  • highlightthickness: толщина границы элемента, когда он находится в фокусе
  • relief: устанавливает стиль элемента, по умолчанию имеет значение SUNKEN
  • selectbackground: фоновый цвет для выделенного элемента
  • selectmode: определяет, сколько элементов могут быть выделены. Может принимать следующие значения: BROWSE, SINGLE, MULTIPLE, EXTENDED. Например, если необходимо включить множественное выделение элементов, то можно использовать значения MULTIPLE или EXTENDED.
  • height: высота элемента в строках. По умолчанию отображает 10 строк
  • width: устанавливает ширину элемента в символах. По умолчанию ширина – 20 символов
  • xscrollcommand: задает горизонтальную прокрутку
  • yscrollcommand: устанавливает вертикальную прокрутку

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

from tkinter import *
from tkinter import ttk

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

languages = ["Python", "JavaScript", "C#", "Java"]
languages_var = Variable(value=languages)

languages_listbox = Listbox(listvariable=languages_var)

languages_listbox.pack(anchor=NW, fill=X, padx=5, pady=5)

root.mainloop()

Для наполнения listboxa элементами определяем список languages, затем этот список передаем в переменную типа Variable. Затем привязываем эту переменную к параметру listvariable у Listbox

Основные методы Listbox

Listbox имеет ряд методов для управления поведением элемента и его содержимым. Некоторые из них:

  • curselection(): возвращает набор индексов выделенных элементов
  • delete(first, last = None): удаляет элементы с индексами из диапазона [first, last]. Если второй параметр опущен, то удаляет только один элемент по индексу first.
  • get(first, last = None): возвращает кортеж, который содержит текст элементов с индексами из дипазона [first, last]. Если второй параметр опущен, возвращается только текст элемента с индексом first.
  • insert(index, element): вставляет элемент по определенному индексу
  • size(): возвращает количество элементов

Для рассмотрения этих методов напишем небольшой скрипт по управлению данными:

from tkinter import *
from tkinter import ttk


# удаление выделенного элемента
def delete():
selection = languages_listbox.curselection()
# мы можем получить удаляемый элемент по индексу
# selected_language = languages_listbox.get(selection[0])
languages_listbox.delete(selection[0])


# добавление нового элемента
def add():
new_language = language_entry.get()
languages_listbox.insert(0, new_language)

root = Tk()
root.title("METANIT.COM")
root.geometry("300x250")
root.columnconfigure(index=0, weight=4)
root.columnconfigure(index=1, weight=1)
root.rowconfigure(index=0, weight=1)
root.rowconfigure(index=1, weight=3)
root.rowconfigure(index=2, weight=1)

# текстовое поле и кнопка для добавления в список
language_entry = ttk.Entry()
language_entry.grid(column=0, row=0, padx=6, pady=6, sticky=EW)
ttk.Button(text="Добавить", command=add).grid(column=1, row=0, padx=6, pady=6)

# создаем список
languages_listbox = Listbox()
languages_listbox.grid(row=1, column=0, columnspan=2, sticky=EW, padx=5, pady=5)

# добавляем в список начальные элементы
languages_listbox.insert(END, "Python")
languages_listbox.insert(END, "C#")

ttk.Button(text="Удалить", command=delete).grid(row=2, column=1, padx=5, pady=5)

root.mainloop()

Для манипулирования элемента списка здесь определено две кнопки. Первая кнопка вызывает функцию add(), которая получает введенное в текстовое поле значение и добавляет его на первое место в списке с помощью метода insert().

Вторая кнопка по нажатию удаляет выделенный элемент. Для этого мы сначала получаем выделенные индексы через метод curselection(). Так как в нашем случае выделяется только один элемент, то получаем его индекс через выражение selection[0]. И этот индекс передаем в метод delete() для удаления.

Подобным образом мы можем управлять элементами, если Listbox привязан к переменной типа Var/StringVar:

from tkinter import *
from tkinter import ttk

# добавление нового элемента
def add():
new_language = language_entry.get()
languages_listbox.insert(0, new_language)

root = Tk()
root.title("METANIT.COM")
root.geometry("300x250")
root.columnconfigure(index=0, weight=4)
root.columnconfigure(index=1, weight=1)
root.rowconfigure(index=0, weight=1)
root.rowconfigure(index=1, weight=3)

languages = ["Python", "C#"]
languages_var = StringVar(value=languages)

# текстовое поле и кнопка для добавления в список
language_entry = ttk.Entry()
language_entry.grid(column=0, row=0, padx=6, pady=6, sticky=EW)
ttk.Button(text="Добавить", command=add).grid(column=1, row=0, padx=6, pady=6)

# создаем список
languages_listbox = Listbox(listvariable=languages_var)
languages_listbox.grid(row=1, column=0, columnspan=2, sticky=NSEW, padx=5, pady=5)

root.mainloop()

Для упрощения здесь я убрал код для удаления, потому что суть будет та же. А именно: у нас есть стандартный список строк languages и есть переменная languages_var, которая использует этот список и к которой привязан Listbox. Все операции с элементами внутри Listbox, например, добавление с помощью вызова languages_listbox.insert(0, new_language) повлияют на переменную languages_var – она изменит свое значение.

Но! изначальный список строк languages останется без изменений.

Что если нам также надо изменить сам изначальный список languages, особенно когда у нас в программе несколько функциональных частей, которые используют этот список и которые должны быть синхронизированы? В этом случае мы можем добавлять и удалять элементы напрямую в списке languages, но тогда необходимо переустанавливать значение переменной languages_var, к которой привязан Listbox:

from tkinter import *
from tkinter import ttk

# добавление нового элемента
def add():
new_language = language_entry.get()
# добавляем новый элемент в список languages
languages.append(new_language)
# переустанавливаем значение переменной languages_var
languages_var.set(languages)

root = Tk()
root.title("METANIT.COM")
root.geometry("300x250")
root.columnconfigure(index=0, weight=4)
root.columnconfigure(index=1, weight=1)
root.rowconfigure(index=0, weight=1)
root.rowconfigure(index=1, weight=3)

# базовый список
languages = ["Python", "C#"]
languages_var = StringVar(value=languages)

# текстовое поле и кнопка для добавления в список
language_entry = ttk.Entry()
language_entry.grid(column=0, row=0, padx=6, pady=6, sticky=EW)
ttk.Button(text="Добавить", command=add).grid(column=1, row=0, padx=6, pady=6)

# создаем список
languages_listbox = Listbox(listvariable=languages_var)
languages_listbox.grid(row=1, column=0, columnspan=2, sticky=NSEW, padx=5, pady=5)

root.mainloop()

Режим и обработка выбора

По умолчанию Listbox позволяет выбрать один элемент. Но с помощью параметра selectmode это поведение можно переопределить. Данный параметр принимает одно из следующих значений:

  • BROWSE: позволяет выбирать один элемент и перетаскивать его мышкой. Режим по умолчанию.
  • EXTENDED: позволяет выбрать группу элементов, выделив начальный и конечный элементы
  • SINGLE: позволяет выбрать один элемент, но не позволяет перетаскивать его мышкой
  • MULTIPLE: позволяет выбрать множество элементов, надимая на строку элемента.

Например, установка выбора нескольких элементов:

languages_listbox = Listbox(listvariable=languages_var, selectmode=EXTENDED)

Для обработки выбора элементов в Listbox необходимо прикрепить функцию обработки к событию <<ListboxSelect>> с помощью метода bind:

listbox.bind("<<ListboxSelect>>", функция_обработки)

Например, динамически обработаем выбор в списке:

from tkinter import *
from tkinter import ttk

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

def selected(event):
# получаем индексы выделенных элементов
selected_indices = languages_listbox.curselection()
# получаем сами выделенные элементы
selected_langs = ",".join([languages_listbox.get(i) for i in selected_indices])
msg = f"вы выбрали: {selected_langs}"
selection_label["text"] = msg

languages = ["Python", "JavaScript", "C#", "Java"]
languages_var = Variable(value=languages)

selection_label = ttk.Label()
selection_label.pack(anchor=NW, fill=X, padx=5, pady=5)

languages_listbox = Listbox(listvariable=languages_var, selectmode=EXTENDED)
languages_listbox.pack(anchor=NW, fill=X, padx=5, pady=5)
languages_listbox.bind("<<ListboxSelect>>", selected)

root.mainloop()

В данном случае при изменении выбора в списке срабатывает функция selected. Функция должна принимать один параметр, который несет информацию о событии – здесь это параметр event. Хотя в данном случае он никак не используется.

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

Программное выделение

Ряд методов Listbox позволяют программно управлять выделением элементов:

  • select_set(first, last): выделяет с индекса first по индекс last. Если надо выделить только один элемент, то применяется только параметр first
  • select_includes(index): возвращает True, среди элемент с индексом index выделен
  • select_clear(first, last): снимает выделение с индекса first по индекс last. Если надо снять выделение только с одного элемента, то применяется только параметр first

Например, выделим элементы с 1 по 2 индексы:

from tkinter import *

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

languages = ["Python", "C#", "Java", "JavaScript"]
languages_var = StringVar(value=languages)

languages_listbox = Listbox(listvariable=languages_var, selectmode=EXTENDED)
languages_listbox.pack(expand=1, fill=BOTH)
languages_listbox.select_set(first=1, last=2)

root.mainloop()

from tkinter import *
from tkinter import messagebox

def add_task():# делаем строкой
task = name_input.get()
start_dte = start_date_input.get()
due_dte = due_date_input.get()
pri = pri_input.get()
stat = status_input.get()
test_string = task+" "+start_dte+" "+due_dte+" "+pri+" "+stat
listbox.insert(1,test_string)

def clear_all():
listbox.delete(0,END)

root = Tk()
root.title("Todo list") # title of the application
#root.geometry("900x400") # size of the application
titlelbl=Label(root, text = 'Welcome to your To-do-list', font='Times 30 bold').grid(row=0, column=5)#pack(),place(x = 25, y = 30)


name_input = StringVar()
start_date_input = StringVar()
due_date_input = StringVar()
pri_input = StringVar()
status_input = StringVar()

Label(root, text = "Name").grid(row = 6, column = 4)#pack()
name = Entry(root, textvariable = name_input)
name.grid(row = 7, column = 4)#pack()

Label(root, text = "Start date").grid(row = 6, column = 5)#pack()
start_date = Entry(root, textvariable = start_date_input)
start_date.grid(row = 7, column = 5)#pack()

Label(root, text = "Due date").grid(row = 6, column = 6)#pack()
due_date = Entry(root, textvariable = due_date_input).grid(row = 7, column = 6)#pack()

Label(root, text = "Priority").grid(row = 6, column = 7)#pack()
priority = Entry(root, textvariable = pri_input)
priority.grid(row = 7, column = 7)#pack()

Label(root, text = "Status").grid(row = 6, column = 8)#pack()
status = Entry(root, textvariable = status_input)
status.grid(row = 7, column = 8)#pack()

add_btn = Button(root, text = 'Add', width = 10, height = 3, command = add_task)
add_btn.grid(row = 9, column= 7)#pack()#place(x = 15, y = 50)

listbox = Listbox(root,font=('', 12), width = 60, height = 10)
listbox.grid(row = 3, column = 5)#pack()
listbox.insert(1, 'Name Start date Due Date Priority Status')

Button(root, text = 'Clear',width = 10, height = 3, command = clear_all).grid(row =9, column =10)

root.mainloop()

from tkinter import *
from tkinter import messagebox

def get_list():
"""
функция для чтения выбора(ов) в списке
(можно выбрать несколько строк)
и поместите результат(ы) в метку
"""
# кортеж индексов строк
sel = listbox1.curselection()
# получить текст, может быть многострочным
seltext = '\n'.join([listbox1.get(x) for x in sel])
choices.set(seltext)

root = Tk()
# used in label
choices = StringVar(root)

# расширенный режим позволяет выбирать мышью CTRL или SHIFT.
listbox1 = Listbox(root, selectmode = EXTENDED)
listbox1.pack()

# нажмите кнопку, чтобы показать выбор(ы)
button1 = Button(root, text = "Get Selection(s)", command = get_list)
button1.pack()

# выбор(ы) отображения
label1 = Label(root, textvariable = choices)
label1.pack()

# сохранить некоторые элементы в списке
items = ["one", "two", "three", "four", "five", "six"]
for item in items:
listbox1.insert(END, item)

# выделить строку 3 списка (строка 1 равна нулю)
# lb.selection_set(first, Last=None) может выбрать более 1 строки
listbox1.selection_set(3)

root.mainloop()
from tkinter import *
from tkinter import messagebox

root = Tk()
root.title('Radiobutton')

fruit=[('Passion fruit\nSecond line', 1), ('Loganberries', 2),
('Mangoes\nin syrup', 3), ('Oranges', 4),
('Apples', 5), ('Grapefruit', 6)]
var = IntVar()
print(Radiobutton)
for text, value in fruit:
Radiobutton(root, text=text, value=value, variable=var).pack(anchor=W)
var.set(3)
root.mainloop()
print(var)
from tkinter import *
root=Tk()
root.grid_anchor(anchor='center')
# эта функция показывает использование индексного метода виджета списка
def get_index():
getval=listbox.index('active')
label=Label(root, text=str(getval), font="arial 16 bold")
label.grid()
# эта функция показывает использование метода Curselection виджета списка
def get_list():
getval=listbox.curselection()
label=Label(root, text=str(getval), font="arial 16 bold")
label.grid()
# listvar — это переменная, которая связана со списком.
listvar=StringVar()
listvar.set(['apple', 'banana', 'orange', 'kiwi', 'pineapple'])
listbox=Listbox(root, height=10, width=20, listvariable=listvar, font="arial 20 bold", selectmode="extended")
listbox.grid()
# эта кнопка используется для вызова функции get_index
get_itm_index_btn=Button(root, text='Получить индексный номер выбранного элемента', command=get_index, font="arial 14 bold")
get_itm_index_btn.grid()
# эта кнопка используется для вызова функции get_list
get_list_of_index=Button(root, text='Получить список индексных номеров', command=get_list, font="arial 14 bold")
get_list_of_index.grid()
root.mainloop()

Создание раскрывающегося списка с множественным выбором в Tkinter

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Multi-Select Drop-Down List")

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

listbox = tk.Listbox(root, selectmode=tk.MULTIPLE)
listbox.pack()

options = ["Option 1", "Option 2", "Option 3", "Option 4", "Option 5"]

for option in options:
listbox.insert(tk.END, option)


# Чтобы разрешить пользователям добавлять и удалять элементы, создайте кнопки и связанные с ними функции:
def add_item():
selected_indices = listbox.curselection()
for index in selected_indices:
selected_item = listbox.get(index)
if selected_item not in selected_items:
selected_items.append(selected_item)
selected_listbox.insert(tk.END, selected_item)

def remove_item():
selected_indices = selected_listbox.curselection()
for index in selected_indices:
item_to_remove = selected_listbox.get(index)
selected_items.remove(item_to_remove)
selected_listbox.delete(index)

add_button = tk.Button(root, text="Добавить", command=add_item)
remove_button = tk.Button(root, text="Удалить", command=remove_item)
add_button.pack()
remove_button.pack()

# Выбранные элементы отображаются в отдельном списке:
selected_items = []
selected_listbox = tk.Listbox(root)
selected_listbox.pack()

root.mainloop()

Scrollbar и прокрутка виджета

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

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

  • orient: направление прокрутки. Может принать следующие значения: vertical (вертикальная прокрутка) и horizontal (горизонтальная прокрутка).
  • command: команда прокрутки

Scrollbar не используется сам по себе, он применяется лишь для прокручиваемого виджета. Не все виджеты в tkinter являются прокручиваемыми. Для прокрутки по вертикали прокручиваемый виджет имеет yview, а для прокрутки по горизонтали – метод xview (виджет может иметь только один из этих методов). Примером прокручиваемого виджета может служить Listbox или Text. Этот метод используется в качестве команды для Scrollbar:

listbox = Listbox()
# вертикальная прокрутка
scrollbar = ttk.Scrollbar(orient="vertical", command = listbox.yview)

Но прокручиваемый виджет должен также взаимодействовать со Scrollbar. Для этого у прокручиваемого виджета имеются параметры yscrollcommand и/или xscrollcommand, которые должны принимать вызов метода set объекта Scrollbar:

from tkinter import *
from tkinter import ttk

languages = ["Python", "JavaScript", "C#", "Java", "C++", "Rust", "Kotlin", "Swift",
"PHP", "Visual Basic.NET", "F#", "Ruby", "R", "Go", "C",
"T-SQL", "PL-SQL", "Typescript", "Assembly", "Fortran"]

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


languages_var = StringVar(value=languages)
listbox = Listbox(listvariable=languages_var)
listbox.pack(side=LEFT, fill=BOTH, expand=1)

scrollbar = ttk.Scrollbar(orient="vertical", command=listbox.yview)
scrollbar.pack(side=RIGHT, fill=Y)

listbox["yscrollcommand"]=scrollbar.set

root.mainloop()

В конструкторе scrollbar ассоциируется с функцией, которую надо выполнять при прокрутке. В данном случае это метод yview элемента listbox. В итоге мы сможем прокручивать элементы по вертикали:

И так как необходимо прокручивать listbox по вертикали, то у него задается параметр listbox["yscrollcommand"]=scrollbar.set

Ручная прокрутка

В принципе для прокрутки виджета (который поддерживает прокрутку) использовать Scrollbar необязательно. Для прокрутки виджет может содержать специальные методы:

  • yview_scroll(number, what): сдвигает текущее положение по вертикали. Параметр number указывает количество, на которое надо сдвигать. А параметр what определяет единицы сдвига и может принимать следующие значения: "units" (элемент) и "pages" (страницы)
  • xview_scroll(number, what): сдвигает текущее положение по горизонтали
  • yview_moveto(fraction): сдвигает область просмотра по вертикали на определенную часть, которая выражается во float от 0 до 1
  • xview_moveto(fraction): сдвигает область просмотра на определенную часть по горизонтали

Например, сдвиг на два элемента списка вниз:

from tkinter import *
from tkinter import ttk

languages = ["Python", "JavaScript", "C#", "Java", "C++", "Rust", "Kotlin", "Swift",
"PHP", "Visual Basic.NET", "F#", "Ruby", "R", "Go",
"T-SQL", "PL-SQL", "Typescript"]

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

languages_var = StringVar(value=languages)
listbox = Listbox(listvariable=languages_var)
listbox.pack(expand=1, fill=BOTH)
# сдвигаем скрол на 1 элемент внизу
listbox.yview_scroll(number=1, what="units")

root.mainloop()

Combobox

Виджет Combobox представляет выпадающий список, из которого пользователь может выбрать один элемент. Фактически он представляет комбинацию виджетов Entry и Listbox.

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

  • values: список строк для отображения в Combobox
  • background: фоновый цвет
  • cursor: курсор указателя мыши при наведении на текстовое поле
  • foreground: цвет текста
  • font: шрифт текста
  • justify: устанавливает выравнивание текста. Значение LEFT выравнивает текст по левому краю, CENTER – по центру, RIGHT – по правому краю
  • show: задает маску для вводимых символов
  • state: состояние элемента, может принимать значения NORMAL (по умолчанию) и DISABLED
  • textvariable: устанавливает привязку к элементу StringVar
  • height: высота элемента
  • width: ширина элемента

Определим простейший выпадающий список:

from tkinter import *
from tkinter import ttk

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

languages = ["Python", "C#", "Java", "JavaScript"]
combobox = ttk.Combobox(values=languages)
combobox.pack(anchor=NW, padx=6, pady=6)

root.mainloop()

Здесь для элемента combobox в качестве источника значений устанавливается список languages:

С помощью параметра textvariable мы можем установить привязку к выбранному в Combobox значению:

from cProfile import label
from cgitb import text
from tkinter import *
from tkinter import ttk

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

languages = ["Python", "C#", "Java", "JavaScript"]
# по умолчанию будет выбран первый элемент из languages
languages_var = StringVar(value=languages[0])

label = ttk.Label(textvariable=languages_var)
label.pack(anchor=NW, padx=6, pady=6)

combobox = ttk.Combobox(textvariable=languages_var, values=languages)
combobox.pack(anchor=NW, padx=6, pady=6)

print(combobox.get())
root.mainloop()

Здесь выбранный в Combobox элемент привязан к переменной languages_var. По умолчанию выбран первый элемент списка languages. Для отслеживания изменения выбора определена метка Label, которая отображает выбранный элемент:

По умолчанию мы можем ввести в текстовое поле в Combobox любое значение, даже то, которого нет в списке. Такое поведение не всегда может быть удобно. В этом случае мы можем установить для виджета состояние только для чтения, передав параметру “state” значение “readonly”:

combobox = ttk.Combobox(textvariable=languages_var, values=languages, state="readonly")

Выбранный элемент можно получить с помощью метода get() класса Combobox

selection = combobox.get()

либо с помощью метода get() привязанной переменной

selection = languages_var.get()

Для установки нового значения можно использовать метод set():

languages_var.set(new_value)
combobox.set(new_value)

Для установки по индексу из привязанного набора значений также можно использовать метод current(newindex), где с помощью параметра newindex задается индекс выбранного значения. Например, выберем второй элемент:

combobox.current(1)

Отслеживание выбора значения

Для обработки выбора элементов в Combobox необходимо прикрепить функцию обработки к событию <<ComboboxSelect>> с помощью метода bind:

combobox.bind("<<ComboboxSelected>>", функция_обработки)

Например, динамически обработаем выбор в Combobox:

from tkinter import *
from tkinter import ttk

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

def selected(event):
# получаем выделенный элемент
selection = combobox.get()
print(selection)
label["text"] = f"вы выбрали: {selection}"

languages = ["Python", "C#", "Java", "JavaScript"]
label = ttk.Label()
label.pack(anchor=NW, fill=X, padx=5, pady=5)

combobox = ttk.Combobox(values=languages, state="readonly")
combobox.pack(anchor=NW, fill=X, padx=5, pady=5)
combobox.bind("<<ComboboxSelected>>", selected)

root.mainloop()

В данном случае при изменении выбора в списке срабатывает функция selected. Функция должна принимать один параметр, который несет информацию о событии – здесь это параметр event. Хотя в данном случае он никак не используется.

В самой функции получаем выбранный элемент и выводит соответствующую информацию на метку Label.

Динамическое добавление и удаление текста в Combobox

import tkinter as tk
from tkinter import ttk

# Create the main window
window = tk.Tk()


def add_option():
new_option = entry.get()
options.append(new_option)
combobox.configure(values=options)
entry.delete(0, tk.END)

entry = tk.Entry(window)
entry.pack()


# Create a Combobox with initial options
options = ["Option 1", "Option 2", "Option 3"]
combobox = ttk.Combobox(window, values=options)
combobox.pack()

add_button = tk.Button(window, text="Добавить параметр", command=add_option)
add_button.pack()



# Start the main event loop
window.mainloop()
  • Нажатие кнопки «Добавить параметр» добавляет новый параметр в список параметров и обновляет Combobox.
  • Пользователь может ввести новую опцию в виджете ввода.
  • Этот код демонстрирует, как динамически добавлять новые параметры в Combobox.
import tkinter as tk
from tkinter import ttk

# Create the main window
window = tk.Tk()


def add_option():
new_option = entry.get()
options.append(new_option)
combobox.configure(values=options)
entry.delete(0, tk.END)


def on_select(event):
selected_value = combobox.get()
print("Selected value:", selected_value)




entry = tk.Entry(window)
entry.pack()


# Create a Combobox with initial options
options = ["Option 1", "Option 2", "Option 3"]
combobox = ttk.Combobox(window, values=options)


# Bind a function to the Combobox's selection event
combobox.bind("<<ComboboxSelected>>", on_select)

combobox.pack()

add_button = tk.Button(window, text="Add Option", command=add_option)
add_button.pack()



# Start the main event loop
window.mainloop()
  • Когда пользователь выбирает опцию, on_selectвызывается функция и выбранное значение выводится на печать.
  • Этот код привязывает функцию с именем on_selectк <<ComboboxSelected>>событию Combobox.
import tkinter as tk

Scale

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

Среди параметров Scale следует отметить следующие:

  • orient: направление виджета. Может принимать значения HORIZONTAL/"horizontal" и VERTICAL/"vertical"
  • from_: начальное значение шкалы виджета, представляет тип float
  • to: конечное значение шкалы виджета, представляет тип float
  • length: длина виджета
  • command: функция, которая выполняется при изменении текущего значения
  • value: текущее значение шкалы виджета, представляет тип float
  • variable: переменная IntVar или DoubleVar, к которой привязано текущее значение виджета

Простейший Scale в горизонтальной и вертикальной ориентации:

from tkinter import *
from tkinter import ttk

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

verticalScale = ttk.Scale(orient=VERTICAL, length=200, from_=1.0, to=100.0, value=50)
verticalScale.pack()

horizontalScale = ttk.Scale(orient=HORIZONTAL, length=200, from_=1.0, to=100.0, value=30)
horizontalScale.pack()

root.mainloop()

Привязка к переменной

from tkinter import *
from tkinter import ttk

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

val = IntVar(value=10)

ttk.Label(textvariable=val).pack(anchor=NW)

horizontalScale = ttk.Scale(orient=HORIZONTAL, length=200, from_=1.0, to=100.0, variable=val)
horizontalScale.pack(anchor=NW)

root.mainloop()

В данном случае и метка Label, и виджет Scale привязаны к переменной val:

Обработка изменения значения

Параметр command позволяет установить функцию, которая будет выполняться при изменении текущего значения Scale. В качестве параметра в эту функцию передается новое значение:

from tkinter import *
from tkinter import ttk

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

def change(newVal):
label["text"] = newVal
# или так
# label["text"] = scale.get()

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

scale = ttk.Scale(orient=HORIZONTAL, length=200, from_=1.0, to=100.0, command=change)
scale.pack(anchor=NW)

root.mainloop()

В данном случае новое значение Scale передается в метку label:

Для получения текущего значения Scale можно использовать его метод get():

label["text"] = scale.get()

Стоит учитывать, что передаваемое в функцию значение newVal представляет строку, а точнее значение типа float в строковом виде. Но что делать, если мы хотим выводить в метке label не строку или даже float, а целое число? В этом случае необходимо выполнить цепь преобразований:

def change(newVal):
float_value = float(newVal) # получаем из строки значение float
int_value = round(float_value) # округляем до целочисленного значения
label["text"] = int_value

Spinbox

Виджет Spinbox позволяет выбрать значение (чаще число) из некоторого списка.

Основные параметры Spinbox:

  • values: набор значений виджета в виде списка или кортежа
  • from_: минимальное значение (тип float)
  • to: максимальное значение (тип float)
  • increment: приращение значения (тип float)
  • textvariable: определяет переменную StringVar, которая хранит текущее значение виджета
  • command: указывает на функцию, которая вызывается при изменении значения виджета
  • wrap: при значении True создает зацикленный список, при котором после минимального значения идет максимальное.
  • background: фоновый цвет
  • foreground: цвет текста
  • font: шрифт виджета
  • justify: выравнивание текста, принимает значения “left” (по левому краю), “right” (по правому краю) и “center” (по центру)
  • width: ширина виджета
  • state: состояние виджета

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

from tkinter import *
from tkinter import ttk

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

spinbox = ttk.Spinbox(from_=1.0, to=100.0)
spinbox.pack(anchor=NW)

root.mainloop()

В данном случае мы можем выбрать одно из чисел от 1 до 100. При нажатии на стрелочки вверх и вниз на виджете значение виджета будет увелиличивается и уменьшаться на единицу:

По умолчанию приращение идет на единицу, но с помощью параметра increment можно установить другое значение, например, приращение на 2:

ttk.Spinbox(from_=1.0, to=100.0, increment=2)

Также по умолчанию мы можем, не используя стрелочки, ввести в текстовое поле виджета какое-либо значение, даже то, которое не входит в диапазон значений. Если нам надо запретить ввод значений в текстовое поле и оставить доступными для выбора значений только стрелочки, то для этого можно установить для параметра state значение readonly:

spinbox = ttk.Spinbox(from_=1.0, to=100.0, state="readonly")

С помощью параметра textvariable можно привязать значение Spinbox к переменной StringVar:

from tkinter import *
from tkinter import ttk

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

spinbox_var = StringVar(value=22) # начальное значение 22

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

spinbox = ttk.Spinbox(from_=1.0, to=100.0, textvariable=spinbox_var)
spinbox.pack(anchor=NW)

root.mainloop()

Здесь для наглядности добавлена метка, которая выводит выбранное значение. В качестве начального значения применяется число 22.

Получение текущего значения

Для получения текущего значения у Spinbox вызывается метод get()

current_value = spinbox.get()

Обработка изменения значения

Чтобы обработать изменение значения нужно определить функцию, которая будет срабатывать при изменении значения, и передать ее параметру command:

from tkinter import *
from tkinter import ttk

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

def change():
label["text"] = spinbox.get()

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

spinbox = ttk.Spinbox(from_=1.0, to=100.0, command=change)
spinbox.pack(anchor=NW)

root.mainloop()

В данном случае при изменении значении срабатывает функция change в которой измененяем текст метки label в соответствии с новым значением

Установка набора значений

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

from tkinter import *
from tkinter import ttk

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

spinbox_var = StringVar()

languages=["Python", "JavaScript", "C#", "Java", "C++"]

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

spinbox = ttk.Spinbox(textvariable=spinbox_var, values=languages)
spinbox.pack(anchor=NW)

root.mainloop()

В данном случае Spinbox позволяет выбрать одно из значений из списка languages:

Progressbar

Виджет Progressbar предназначен для отображения хода выполнения какого-либо процесса. Основные параметры Progressbar:

  • value: текущее значение виджета (тип float)
  • maximum: максимальное значение (тип float)
  • variable: определяет переменную IntVar/DoublerVar, которая хранит текущее значение виджета
  • mode: определяет режим, принимает значения “determinate” (конечный) и “indeterminate” (бесконечный)
  • orient: определяет ориентацию виджета, принимает значения “vertical” (вертикальый) и “horizontal” (горизонтальный)
  • length: длина виджета

Определим вертикальный и горизонтальный Progressbar:

from tkinter import *
from tkinter import ttk

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

# вертикальный Progressbar
ttk.Progressbar(orient="vertical", length=100, value=40).pack(pady=5)

# горизонтальный Progressbar
ttk.Progressbar(orient="horizontal", length=150, value=20).pack(pady=5)

root.mainloop()

С помощью параметра variable можно привязать значение прогрессбара к переменной типа IntVar или DoublerVar:

from tkinter import *
from tkinter import ttk

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

value_var = IntVar(value=30)

progressbar = ttk.Progressbar(orient="horizontal", variable=value_var)
progressbar.pack(fill=X, padx=6, pady=6)

label = ttk.Label(textvariable=value_var)
label.pack(anchor=NW, padx=6, pady=6)

root.mainloop()

В данном случае значение прогрессбара привязано к переменной value_var, значение которой выводит метка label:

Методы Progressbar

Некоторые важные методы виджета:

  • start([interval]): запускает перемещение индикатора через определенные интервалы времени. Каждый раз, когда пройдет очередной интервал, индикатор смещается на одно деление вперед. По умолчанию интервал равен 50 миллисекунд
  • step([delta]): увеличивает значение индикатора на значение из параметра delta (по умолчанию равен 1.0)
  • stop(): останавливает перемещение индикатора

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

from tkinter import *
from tkinter import ttk

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

value_var = IntVar()

progressbar = ttk.Progressbar(orient="horizontal", variable=value_var)
progressbar.pack(fill=X, padx=6, pady=6)

label = ttk.Label(textvariable=value_var)
label.pack(anchor=NW, padx=6, pady=6)

def start(): progressbar.start(1000) # запускаем progressbar
def stop(): progressbar.stop() # останавливаем progressbar


start_btn = ttk.Button(text="Start", command=start)
start_btn.pack(anchor=SW, side=LEFT, padx=6, pady=6)
stop_btn = ttk.Button(text="Stop", command=stop)
stop_btn.pack(anchor=SE, side=RIGHT, padx=6, pady=6)

root.mainloop()

В данном случае по нажатию на кнопку start_btn запускаем перемещение индикатора – через каждые 1000 миллисекунд (1 секунду) индикатор перемещается на одно деление вперед. По нажатию на кнопку stop_btn останавливаем движение индекатора.

Режим прогрессбара

Параметр mode отвечает за установку режима прогрессбара и может принимать два значения:

  • "indeterminate": прогрессбар показывает индикатор, который перемещается без остановки между двумя краями виджета, то есть фактически бесконечно продолжает перемещение. Данный режим подходит, когда сложно расчитать, насколько должен перемещаться индикатор при отображении хода некоторой задачи
  • "determinate": индикатор прогрессбара проходит от начала до конца и затем завершает перемещение. Значение по умолчанию. Подходит для отображения таких процессов, где можно подсчитать перемещение индикатора. Например, копируется 100 файлов, и, если параметр maximum равен 100, при копирования одного файла перемещаем индикатор на одно деление вперед.

Применение режима “determinate” по сути уже рассматривалось выше, так как это режим по умолчанию. Посмотрим на пример применения режима “indeterminate”:

from tkinter import *
from tkinter import ttk

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

progressbar = ttk.Progressbar(orient="horizontal", mode="indeterminate")
progressbar.pack(fill=X, padx=10, pady=10)

start_btn = ttk.Button(text="Start", command=progressbar.start)
start_btn.pack(anchor=SW, side=LEFT, padx=10, pady=10)

stop_btn = ttk.Button(text="Stop", command=progressbar.stop)
stop_btn.pack(anchor=SE, side=RIGHT, padx=10, pady=10)

root.mainloop()

По нажатию на кнопку start_btn также запускается процесс. Когда индикатор дойдет до конца, он начинает обратное движение: