Рубрики
Без рубрики

Разработка графического интерфейса Python с помощью Tkinter: Часть 3

Автор оригинала: Mateusz Dobrychlop.

Разработка графического интерфейса Python с помощью Tkinter: Часть 3

Это третья часть нашей многосерийной серии по разработке графических интерфейсов на Python с использованием Tkinter. Проверьте ссылки ниже для других частей этой серии:

  • Разработка графического интерфейса Python с помощью Tkinter
  • Разработка графического интерфейса Python с помощью Tkinter: Часть 2
  • Разработка графического интерфейса Python с помощью Tkinter: Часть 3

Вступление

Tkinter – это де-факто стандартный пакет для построения графических интерфейсов в Python. В Stackabuse first и second части учебника Tkinter мы узнали, как использовать основные строительные блоки GUI для создания простых интерфейсов.

В последней части нашего урока мы рассмотрим несколько ярлыков, которые предлагает Tkinter, чтобы мы могли легко предлагать сложные и очень полезные функции. Мы также узнаем о Python MegaWidgets – инструментарии, основанном на Tkinter, который еще быстрее ускоряет создание сложных интерфейсов.

Диалоговое окно файла

Предоставление пользователю возможности выбрать файл на своем компьютере, очевидно, является очень распространенной особенностью графических интерфейсов. Диалоговые окна файлов обычно довольно сложны – они объединяют по крайней мере несколько кнопок (например, Open , Cancel или Create New Folder ) и фрейм, который отображает структуру каталогов нашей среды. Основываясь на наших предыдущих уроках, вы можете предположить, что с помощью Tkinter очень трудно создать такую сложную функцию. Однако на самом деле это не так. Взгляните на следующий пример:

import tkinter
import tkinter.filedialog

root = tkinter.Tk()

def print_path():
    f = tkinter.filedialog.askopenfilename(
        parent=root, initialdir='C:/Tutorial',
        title='Choose file',
        filetypes=[('png images', '.png'),
                   ('gif images', '.gif')]
        )

    print(f)

b1 = tkinter.Button(root, text='Print path', command=print_path)
b1.pack(fill='x')

root.mainloop()

Выход:

Приведенный выше код-это все, что вам нужно для отображения приятного, полезного диалога File Dialog . В строке 2 мы импортируем содержимое класса filedialog . Затем, после создания нашего окна root в строке 4, мы определяем новую функцию в строке 6 (которая должна выполняться кнопкой, созданной в строке 17 и упакованной в строку 18).

Давайте взглянем на определение функции print_path () . В строке 7 мы выполняем функцию askopenfilename , которая принимает несколько аргументов. Первым аргументом, конечно, является родительский виджет диалогового окна (который в данном случае является нашим корневым окном). Затем в аргументе initialdir мы указываем местоположение, которое будет отображаться в нашем диалоговом окне файла сразу после его открытия. title управляет содержимым строки заголовка диалогового окна.

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

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

Метод askopenfilename() возвращает строку, которая является путем к файлу, выбранному пользователем. Если пользователь решает нажать Cancel , возвращается пустая строка. В строке 7 мы возвращаем путь к переменной f , а затем в строке 15 (которая выполняется только после закрытия диалогового окна файла) путь печатается в консоли.

Отображение Изображений С Помощью Tkinter

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

import tkinter
import tkinter.filedialog

root = tkinter.Tk()

def display_image():
    f = tkinter.filedialog.askopenfilename(
        parent=root, initialdir='C:/Tutorial',
        title='Choose file',
        filetypes=[('png images', '.png'),
                   ('gif images', '.gif')]
        )

    new_window = tkinter.Toplevel(root)

    image = tkinter.PhotoImage(file=f)
    l1 = tkinter.Label(new_window, image=image)
    l1.image = image
    l1.pack()

b1 = tkinter.Button(root, text='Display image', command=display_image)
b1.pack(fill='x')

root.mainloop()

Выход:

Давайте посмотрим, что изменилось внутри функции, выполняемой нашей кнопкой, теперь переименованной в display_image . Мы выводим диалоговое окно файла, используем те же критерии выбора файла, что и раньше, и снова сохраняем возвращенный путь в переменной f . Однако после получения пути к файлу мы не печатаем его в консоли. Мы создаем окно верхнего уровня в строке 14. Затем в строке 16 мы создаем экземпляр объекта класса PhotoImage , заставляя его читать файл .png , выбранный пользователем. Затем объект сохраняется в переменной image , которую мы можем передать в качестве аргумента для построения виджета Label в строке 17. В строке 18 мы обязательно сохраняем ссылку на объект image , чтобы он не был очищен сборщиком мусора Python . Затем в строке 19 мы упаковываем нашу метку (на этот раз отображающую изображение, а не текст) внутри new_window .

Выбор Цвета

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

import tkinter
import tkinter.colorchooser

root = tkinter.Tk()

def color_button():
    color = tkinter.colorchooser.askcolor(parent=root)
    print(color)
    b1.configure(bg=color[1])

b1 = tkinter.Button(root, text='Select Color', command=color_button)
b1.pack(fill='x')

root.mainloop()

Выход:

В строке 2 примера, показанного выше, мы импортируем класс с именем color chooser . Мы используем его метод askcolor() в строке 7. Этот метод, как и askopenfilename() , отвечает за открытие красивого, сложного диалога (в данном случае выбор цвета) и возвращает данные, зависящие от выбора пользователя. В этом случае после того, как пользователь выбирает цвет из палитры и принимает свой выбор, объект, возвращаемый переменной color , представляет собой кортеж, содержащий два элемента. Первый элемент-это кортеж, который хранит значения для красного, зеленого и синего каналов выбранного цвета. Второй элемент кортежа имеет тот же цвет, что и в формате hexadecimal . Мы можем видеть содержимое кортежей в нашей консоли, благодаря print() в строке 8.

После того как мы сохраним кортеж , возвращенный askcolor в переменной color , мы затем используем эту переменную в строке 9 для настройки кнопки b1 . Как вы уже знаете, аргумент bg отвечает за управление цветом фона кнопки. Передаем ему первый элемент кортежа color (представление цвета в шестнадцатеричном формате). В результате, после нажатия кнопки b1 пользователь может изменить свой цвет фона, используя хороший выбор цвета.

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

Прежде чем перейти от Tkinter к Python MegaWidgets, полезно упомянуть еще одну особенность модуля Tkinter, которая делает программирование GUI немного быстрее. Tkinter предлагает так называемые Окна сообщений, которые представляют собой набор простых, но широко используемых стандартных диалогов . Эти окна сообщений могут использоваться для отображения быстрого сообщения, предупреждения или когда нам нужно, чтобы наш пользователь принял простое решение “да/нет”. В следующем примере демонстрируются все окна сообщений, предлагаемые Tkinter:

import tkinter
import tkinter.messagebox

root = tkinter.Tk()

def display_and_print():
    tkinter.messagebox.showinfo("Info","Just so you know")
    tkinter.messagebox.showwarning("Warning","Better be careful")
    tkinter.messagebox.showerror("Error","Something went wrong")

    okcancel = tkinter.messagebox.askokcancel("What do you think?","Should we go ahead?")
    print(okcancel)

    yesno = tkinter.messagebox.askyesno("What do you think?","Please decide")
    print(yesno)

    retrycancel = tkinter.messagebox.askretrycancel("What do you think?","Should we try again?")
    print(retrycancel)

    answer = tkinter.messagebox.askquestion("What do you think?","What's your answer?")
    print(answer)

b1 = tkinter.Button(root, text='Display dialogs', command=display_and_print)
b1.pack(fill='x')

top.mainloop()

Выход:

На этот раз наша кнопка b1 выполняет функцию display_and_print() . Функция позволяет последовательно всплывать 7 окнам сообщений – каждое из них отображается после взаимодействия пользователя с предыдущим. диалоги, определенные в строках 11-21, – это диалоги, требующие от пользователя выбора одного из двух доступных вариантов, поэтому они возвращают значения на основе принятых решений и сохраняют их в соответствующих переменных. В каждом случае мы можем передать два аргумента при определении диалогов – первый всегда является заголовком диалога, а второй содержит содержание его основного сообщения.

Итак, начнем с самого верха. В строке 7 мы определяем простой диалог show info , который предназначен только для отображения нейтрального значка, сообщения и кнопки OK , которая закрывает его. В строках 8 и 9 мы имеем аналогичные, простые типы окон сообщений, но их значки указывают на то, что требуется предупреждение от пользователя ( showwarning ) или что произошла ошибка ( showerror ). Обратите внимание, что в каждом из трех случаев при появлении диалога воспроизводится другой звук.

Как я уже говорил, строки 11-21 содержат код, отвечающий за отображение диалогов для получения решения пользователя. askokcancel (строка 11) возвращает True если пользователь нажимает OK и False если он нажимает Cancel . askyesno (строка 14) возвращает True если пользователь нажимает Yes и False если пользователь нажимает No . askretrycancel (строка 17) возвращает True если пользователь нажимает Retry и False если пользователь нажимает Cancel . askquestion очень похож на askyesno , но возвращает 'yes' если пользователь нажимает Yes и 'no' если пользователь нажимает No .

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

Индикатор выполнения

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

import tkinter
import time
from tkinter import ttk

root = tkinter.Tk()

def start():
    for k in range(1, 11):
        progress_var.set(k)
        print("STEP", k)
        k += 1
        time.sleep(1)
        root.update_idletasks()

b1 = tkinter.Button(root, text="START", command=start)
b1.pack(side="left")

progress_var = tkinter.IntVar()

pb = ttk.Progressbar(root, orient="horizontal",
                     length=200, maximum=10,
                     mode="determinate",
                     var=progress_var)
pb.pack(side="left")

pb["value"] = 0

root.mainloop()

Выход:

В приведенном выше примере показана реализация Progress bar . Он является частью модуля tkinter.ttk , который обеспечивает доступ к набору тематических виджетов Tk, представленному в Tk 8.5 . Вот почему нам нужно дополнительно импортировать модуль ttk в строке 3.

Состояние нашего индикатора прогресса будет контролироваться временем – индикатор будет прогрессировать в десять шагов, выполняемых с интервалом в одну секунду. Для этого мы импортируем модуль time в строку 2.

Мы определяем наш Индикатор выполнения в строке 20. Мы определяем его родительский виджет ( root ), даем ему “горизонтальную” ориентацию и длину 200 пикселей. Затем мы определяем значение maximum , которое является значением переменной, назначенной индикатору выполнения с помощью аргумента var (в нашем случае переменной progress_var ), что означает, что индикатор выполнения заполнен полностью. Мы устанавливаем mode на “determinate”, что означает, что наш код будет перемещать длину индикатора в точно определенные точки на основе значения progress_var .

Целочисленная переменная progress_var , которая будет управлять прогрессом бара, определена в строке 18. В строке 26, используя задание типа словаря, мы устанавливаем начальное значение индикатора выполнения равным 0.

В строке 15 мы создаем кнопку , которая должна запустить часы, контролирующие ход нашего бара, выполнив функцию start () , определенную между строками 7 и 13. Там у нас есть простой цикл for , который будет перебирать значения от 1 до 10. С каждой итерацией значение progress_var обновляется и увеличивается на 1. Чтобы иметь возможность четко наблюдать за ходом выполнения, мы ждем одну секунду во время каждой итерации (строка 12). Затем мы используем метод корневого окна update_idletasks() в строке 13, чтобы позволить программе обновить внешний вид индикатора выполнения, даже если мы все еще выполняем цикл for (таким образом, технически мы все еще находимся в одной итерации mainloop () ).

Python Mega Widgets

Если вы широко используете Tkinter в своих проектах, я думаю, что это хорошая идея, чтобы рассмотреть возможность включения Python Mega Widgets в ваш код. Python MegaWidgets-это инструментарий, основанный на Tkinter, который предлагает набор megawidgets : сложных, функциональных и относительно эстетически приятных виджетов, сделанных из более простых виджетов Tkinter. Что замечательно в этом пакете, который вы можете скачать здесь , так это то, что общая философия определения и ориентации виджетов такая же, как и в случае с Tkinter, и вы можете смешивать обе библиотеки в своем коде. Давайте закончим наш урок, поцарапав поверхность этого мощного инструментария.

Виджет EntryField

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

import tkinter
import Pmw

root = tkinter.Tk()

def color_entry_label():
    color = entry_color.get()
    entry_number.configure(label_bg=color)

entry_color = Pmw.EntryField(root, labelpos="w",
                                 label_text="First name:",
                                 entry_bg="white",
                                 entry_width=15,
                                 validate="alphabetic")

entry_number = Pmw.EntryField(root, labelpos="w",
                                 label_text="Integer:",
                                 entry_bg="white",
                                 entry_width=15,
                                 validate="integer")

ok_button = tkinter.Button(root, text="OK", command=color_entry_label)

entry_color.pack(anchor="e")
entry_number.pack(anchor="e")
ok_button.pack(fill="x")

root.mainloop()

Выход:

На этот раз мы должны не только импортировать tkinter , но и наш недавно установленный Pmw пакет (строка 2). Как всегда, мы используем класс Tk для инициализации нашего корневого окна.

В строках 10-14 и 16-20 мы определяем два Pmw.EntryField виджеты. Поле ввода представляет собой функциональную смесь Tkinter Label и Entry с некоторым добавлением полезных функций. Первым аргументом для инициализации виджета является, конечно же, родительский виджет. label_text , entry_bg и entry_width управляют некоторыми самоочевидными аспектами внешнего вида виджета. Наиболее интересным аргументом в нашем примере, вероятно, является аргумент validate . Здесь мы можем решить, какие данные пользователь может поместить в поле.

В поле entry_color мы ожидаем строку букв, поэтому устанавливаем validate в “алфавитный”. В виджете entry_number мы ожидаем целое число, и именно на него мы устанавливаем значение аргумента validate . Таким образом, если мы попытаемся поместить число внутри первого и букву внутри второго, символы просто не появятся в виджетах, и будет воспроизводиться системный звук, сообщающий нам, что мы пытаемся сделать что-то не так. Кроме того, если виджет ожидает определенного типа данных и его содержимое находится в конфликте с этим условием в момент инициализации, поле EntryField будет выделено красным цветом.

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

Кнопка, определенная в строке 26, выполняет команду color_entry_label () , определенную между строками 6 и 8. Цель функции-нарисовать фон метки виджета entry_number в соответствии с содержимым виджета entry_color . В строке 7 метод get() используется для извлечения содержимого поля entry_color | Entry . Затем, естественно, метод configure() используется для изменения внешнего вида виджета entry_number . Обратите внимание, что для изменения характеристик виджетов Pmw, состоящих из нескольких более простых виджетов, мы должны указать, какой суб-виджет мы хотим настроить (в нашем случае это метка - именно поэтому мы настраиваем label_bg , а не, скажем, entryfield_bg ).

Виджет EntryField может быть визуально не очень впечатляющим, но даже этот простой пример иллюстрирует потенциал мега-виджетов – создание такого рода самоподтверждающейся части интерфейса более высокой сложности потребовало бы гораздо больше кода, если бы мы попытались достичь того же эффекта с помощью простого Tkinter. Я призываю вас изучить другие мощные мега-виджеты, описанные в документации инструментария .

Вывод

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