Работа с цветами и шрифтами / tkinter 7

Скачайте код уроков с GitLab: https://gitlab.com/PythonRu/tkinter-uroki

Работа с цветами

В примерах из прошлых материалов цвета задавались с помощью их названий: например, white, blue или yellow. Эти значения передаются в виде строк параметрам foreground и background, которые изменяют цвет текста и фона виджета соответственно.

Названия цветов дальше уже привязываются к RGB-значениям (аддитивной модели, которая представляет цвета за счет комбинации интенсивности красного, зеленого и синего цветов). Этот перевод делается на основе таблицы, которая отличается от платформы к платформе. Если же нужно отображать один и тот же цвет на разных платформах, то можно передавать RGB-значение в параметры виджета.

Следующее приложение показывает, как можно динамически менять параметры foreground и background у метки, которая демонстрирует зафиксированный текст:

Работа с цветами tkinter

Цвета определены в формате RGB и выбираются с помощью нативного модального окна. На следующем скриншоте представлено диалоговое окно из Windows 10:

Работа с цветами tkinter

Традиционно будем работать с настройками виджета с помощью кнопок — по одной для каждого параметра. Основное отличие по сравнению с предыдущими примерами в том, что значения могут быть прямо выбраны с помощью диалогового окна askcolor из модуля tkinter.colorchooser:


from functools import partial

import tkinter as tk
from tkinter.colorchooser import askcolor

class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Демо с цветами")
text = "Шустрая бурая лисица прыгает через ленивого пса"
self.label = tk.Label(self, text=text)
self.fg_btn = tk.Button(self, text="Установить цвет текста",
command=partial(self.set_color, "fg"))
self.bg_btn = tk.Button(self, text="Установить цвет фона",
command=partial(self.set_color, "bg"))

self.label.pack(padx=20, pady=20)
self.fg_btn.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.bg_btn.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

def set_color(self, option):
color = askcolor()[1]
print("Выбрать цвет:", color)
self.label.config(**{option: color})

if __name__ == "__main__":
app = App()
app.mainloop()

Если нужно проверить RGB-значение выбранного цвета, то его можно вывести в консоль при подтверждении выбора. Если же ничего не было выбрано, то и консоль останется пустой.

Как работает настройка цвета

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


tk.Button(self, command=partial(self.set_color, "fg"), ...)

Предыдущая инструкция выполняет то же действие, что и следующая:


tk.Button(self, command=lambda: self.set_color("fg"), ...)

Так делается для того, чтобы переиспользовать метод set_color() из модуля functools. Это особенно полезно в более сложных сценариях, например, когда нужно создать несколько функций, и очевидно, что некоторые аргументы заданы заранее.

Нужно лишь помнить тот нюанс, что foreground и background кратко записаны как fg и bg. Эти строки распаковываются с помощью ** при настройке виджета в инструкции:


def set_color(self, option):
color = askcolor()[1]
print("Выбрать цвет:", color)
self.label.config(**{option: color}) # same as (fg=color)
or (bg=color)

askcolor возвращает кортеж с двумя элементами, которые представляют собой выбранный цвет. Первый — кортеж цветов из RGB-значений, а второй — шестнадцатеричное представление в виде строки. Поскольку первый вариант не может быть прямо передан в параметры виджета, используется второй.

Если нужно преобразовать название цвета в RGB-формат, можно использовать метод winfo_rgb() из предыдущего виджета. Поскольку он возвращает кортеж целых чисел от 0 до 65535, которые представляют 16-битные RGB-значения, их можно конвертировать в более привычное представление #RRGGBB, сдвинув вправо 8 битов:


rgb = widget.winfo_rgb("lightblue")
red, green, blue = [x>>8 for x in rgb]
print("#{:02x}{:02x}{:02x}".format(red, green, blue))

В предыдущем коде использовался {:02x} для форматирования каждого целого числа в два шестнадцатеричных.

Задание шрифтов виджета

В Tkinter можно менять шрифт виджета на кнопках, метках и записях. По умолчанию он соответствует системному, но его можно поменять с помощью параметра font.

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

Задание шрифтов виджета

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


import tkinter as tk

class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Демо шрифтов")
text = "Шустрая бурая лисица прыгает через ленивого пса"
self.label = tk.Label(self, text=text)

self.family = tk.StringVar()
self.family.trace("w", self.set_font)
families = ("Times", "Courier", "Helvetica")
self.option = tk.OptionMenu(self, self.family, *families)

self.size = tk.StringVar()
self.size.trace("w", self.set_font)
self.spinbox = tk.Spinbox(self, from_=8, to=18,
textvariable=self.size)

self.family.set(families[0])
self.size.set("10")
self.label.pack(padx=20, pady=20)
self.option.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.spinbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

def set_font(self, *args):
family = self.family.get()
size = self.size.get()
self.label.config(font=(family, size))

if __name__ == "__main__":
app = App()
app.mainloop()

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

Как работает настройка шрифтов?

Кортеж FAMILIES включает три типа шрифтов, которые Tk гарантированно поддерживает на всех платформах: Times (Times New Roman), Courier и Helvetica. Между ними можно переключаться с помощью виджета OptionMenu, который привязан к переменной self.family.

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


def set_font(self, *args):
family = self.family.get()
size = self.size.get()
self.label.config(font=(family, size))

Кортеж, который передается в font, также может определять один или несколько следующих параметров шрифта: полужирный, стандартный, курсивный, подчеркнутый или перечеркнутый:


widget1.config(font=("Times", "20", "bold"))
widget2.config(font=("Helvetica", "16", "italic underline"))

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


import tkinter as tk
from tkinter import font
root = tk.Tk()
print(font.families())

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

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

Работа с классом Font напоминает работу с дескрипторами шрифтов. Например, этот скрипт создает полужирный шрифт Courier размером 18 пикселей:


from tkinter import font
courier_18 = font.Font(family="Courier", size=18, weight=font.BOLD)

Чтобы получить или изменить значение, можно использовать методы cget и configure:


family = courier_18.cget("family")
courier_18.configure(underline=1)

Использование параметров базы данных

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

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

несколько виджетов с разными стилями

Добавим кое-какие параметры с помощью метода option_add(), который доступен из всех классов виджетов:


import tkinter as tk

class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Демо опции")
self.option_add("*font", "helvetica 10")
self.option_add("*header.font", "helvetica 18 bold")
self.option_add("*subtitle.font", "helvetica 14 italic")
self.option_add("*Button.foreground", "blue")
self.option_add("*Button.background", "white")
self.option_add("*Button.activeBackground", "gray")
self.option_add("*Button.activeForeground", "black")

self.create_label(name="header", text="Это 'header'")
self.create_label(name="subtitle", text="Это 'subtitle'")
self.create_label(text="Это параграф")
self.create_label(text="Это следующий параграф")
self.create_button(text="Больше...")

def create_label(self, **options):
tk.Label(self, **options).pack(padx=20, pady=5, anchor=tk.W)

def create_button(self, **options):
tk.Button(self, **options).pack(padx=5, pady=5, anchor=tk.E)

if __name__ == "__main__":
app = App()
app.mainloop()

Вместо настройки шрифтов, foreground и background и других параметров Tkinter будет использовать значения по умолчанию из базы.

Как работают стили для виджетов

Начнем с объяснения каждого вызова option_add. Первый из них добавляет параметр, который задает атрибут font для всех виджетов — звездочка представляет любое название приложения:

Следующий вызов ограничивает вызов для элементов с именем header. Чем он конкретнее, тем выше приоритет. Это же имя позже используется при создании экземпляра с меткой name="header":


self.option_add("*header.font", "helvetica 18 bold")

То же применимо и к self.option_add("*subtitle.font", "helvetica 14 italic"), где каждый параметр соответствует своему экземпляру виджета.

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


self.option_add("*Button.foreground", "blue")
self.option_add("*Button.background", "white")
self.option_add("*Button.activeBackground", "gray")
self.option_add("*Button.activeForeground", "black")

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

Эти параметры не применяются к существующим виджетам, а лишь к тем, которые были созданы после изменения базы данных. Таким образом всегда рекомендуется вызывать option_add() в начале приложения.

Есть несколько примеров, где один приоритетнее предыдущего:

  • *Frame*background: работает для фона всех виджетов во фрейме.
  • *Frame.background: фон всех фреймов.
  • *Frame.myButton.background: фон виджета myButton.
  • *myFrame.myButton.background: фон виджета myButton внутри контейнера myFrame.

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


*font: helvetica 10
*header.font: helvetica 18 bold
*subtitle.font: helvetica 14 italic
*Button.foreground: blue
*Button.background: white
*Button.activeBackground: gray
*Button.activeForeground: black

Этот файл затем загружается в приложение с помощью метода option_readfile() и заменяет все вызовы к option_add(). В этом примере предположим, что файл называется my_options_file и что находится он в одной папке с кодом:


def __init__(self):
super().__init__()
self.title("Options demo")
self.option_readfile("my_options_file")
# ...

Если такого файла не существует или его формат неверный, Tkinter вернет ошибку TclError.

Обучение с трудоустройством

Профессия Python-разработчик / Skillbox

Профессия Python-разработчик / Skillbox

7 500 3 750 ₽/мес.
Факультет Python-разработки / GeekBrains

Факультет Python-разработки / GeekBrains

4 990 ₽/мес.
Факультет Аналитики Big Data / GeekBrains

Факультет Аналитики Big Data / GeekBrains

7 490 ₽/мес.
Профессия Data Scientist / Skillbox

Профессия Data Scientist / Skillbox

8 167 4 083 ₽/мес.

Тест на знание python

Что выведет этот код?
Что выведет этот код?
Что выведет этот код?
Что нужно вcтавить после "if", для вывода "x четное число"
Что выведет этот код?
Александр
Я создал этот блог в 2018 году, чтобы распространять полезные учебные материалы, документации и уроки на русском. На сайте опубликовано множество статей по основам python и библиотекам, уроков для начинающих и примеров написания программ.