С пользовательским Tkinter и OpenCV

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

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

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

В этом проекте я использовал OpenCV (cv2) и QRcode для штрих-кода, tkinter и пользовательский tkinter для графического интерфейса, а также модуль os для поиска папки загрузок пользователя. Используемые модули могут быть установлены следующими строками:

pip install opencv-python
pip install qrcode
pip install tkinter
pip install customtkinter

После их установки мы можем импортировать их. Из Tkinter мы будем импортировать только PhotoImage для значка и filedialog для открытия диалогового окна файла при декодировании QR-кодов. Мы импортируем пользовательский Tkinter для графического интерфейса, cv2 и QR-код для обработки и генерации QR-кода:

from tkinter import PhotoImage, filedialog
import customtkinter
import qrcode
import cv2
import os

Теперь, когда у нас есть библиотеки, первое, что мы хотим сделать в любом проекте, включая внешний интерфейс, — это получить пустое окно для отображения на нашем экране. Итак, сначала мы начнем с определения нашего класса App, используя customtkinter.CTk в качестве базового класса. CTk — это пользовательский tkinter эквивалент Tk, который является классом нашего основного тела приложения (окна) и будет корневым окном нашего приложения.

Во-первых, чтобы добавить заголовок, мы используем атрибут title для себя (корень нашего приложения).

После добавления заголовка в наше приложение, чтобы убрать иконку с пути (эта часть необязательна, вы не хотите с ней возиться), мы сначала используем PhotoImage для загрузки изображения, а затем используем атрибут iconbitmap, чтобы установить иконку для наше приложение (вы можете использовать любую понравившуюся иконку с расширением файла «ico»).

Наконец, чтобы установить размеры окна нашего приложения, мы используем атрибут geometry для их установки. Вы задаете размеры в виде строки (300x300).

Для этого приложения я использовал размеры 300 на 300, поскольку они мне показались подходящими размерами.

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.title("QR Code Generator")
        icon = PhotoImage("icon.ico")
        self.iconbitmap(icon)

        height = 300
        width = 300
        self.geometry(f"{width}x{height}")

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

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

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

Мы вызовем CTkFrame, чтобы добавить кадр в наше приложение. Мы установим self (корневой в нашем случае) как его master (родительский контейнер), а затем мы будем использовать атрибут pack, чтобы отобразить его на нашем экране. Однако, прежде чем мы закончим его, мы добавим 20 единиц отступов со всех четырех сторон, чтобы он выглядел немного менее тесным, используя padx и pady , и мы заставим его заполнять «оба» пути и расширяться. Наш класс и приложение должны выглядеть следующим образом.

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()
        
        self.title("QR Code Generator")
        icon = PhotoImage("icon.ico")
        self.iconbitmap(icon)

        height = 300
        width = 300
        self.geometry(f"{width}x{height}")

        frame = customtkinter.CTkFrame(master=self)
        frame.pack(pady=20,padx=20, fill="both", expand=True)

Теперь, когда у нас есть контейнер, мы добавим метку (текстовый элемент), используя CTkLabel. Он будет отображать текст, говорящий “QR Code\nGenerator & Decoder” (\n для новой строки), он будет использовать шрифт «Berlin Sans FB» с размером шрифта 18.

После его вызова мы еще раз воспользуемся атрибутом pack, чтобы он отображался в нашем приложении. Еще раз, мы собираемся дать ему отступы в 10 единиц как по горизонтали, так и по вертикали, используя padx и pady. Теперь наше приложение должно выглядеть примерно так.

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.title("QR Code Generator")
        icon = PhotoImage("icon.ico")
        self.iconbitmap(icon)

        height = 300
        width = 300
        self.geometry(f"{width}x{height}")

        frame = customtkinter.CTkFrame(master=self)
        frame.pack(pady=20,padx=20, fill="both", expand=True)

        label = customtkinter.CTkLabel(master=frame, text="QR Code\nGenerator & Decoder", font=("Berlin Sans FB", 18))
        label.pack(pady=10, padx=10)

Примечание. В пользовательском Tkinter или tkinter вы должны иметь возможность использовать любой шрифт, установленный на вашем компьютере. Для Windows, если вы хотите узнать, какие шрифты вы установили на свой компьютер, вы можете перейти в панель поиска Windows и ввести «шрифт» и проверить его в настройках шрифта.

Теперь, когда мы знаем, как добавить виджет, первое, что мы хотим сделать, это добавить виджет ввода, используя CTkEntry, а затем добавить две кнопки, используя CTkButton, одну для создания кода qr и одну для декодирования кодов qr.

Для нашего поля ввода мы собираемся определить frame в качестве его родителя (главного), и его width будет равно 200. Там будет текст-заполнитель, который говорит “Enter Text Here”, и для центрирования текста внутри поля ввода мы будем использовать justify="center" .

Наконец, мы хотим, чтобы он использовал шрифт «Consolas» с размером шрифта 16. Однако, прежде чем мы отобразим его, будет 5 единиц верхнего и нижнего заполнения наряду с 5 единицами внутреннего заполнения, которые мы добавим в каждом направлении, используя ipadx и ipady. .

Для наших кнопок мы будем использовать CTkButton. Для первого его родительский контейнер снова будет frame, он будет отображать текст “Generate Code” и использовать шрифт «Berlin Sans FB» с размером шрифта 16. Мы будем pack с отступами x и y 10 и внутренняя прокладка 5 во всех направлениях.

Для второй кнопки мы скопируем и вставим первую с другим именем переменной и текстом «Decode QR Code”. К этому моменту наш код и приложение должны выглядеть примерно так.

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        #Previous code

        frame = customtkinter.CTkFrame(master=self)
        frame.pack(pady=20,padx=20, fill="both", expand=True)

        label = customtkinter.CTkLabel(master=frame, text="QR Code\nGenerator & Decoder", font=("Berlin Sans FB", 18))
        label.pack(pady=10, padx=10)

        url_input = customtkinter.CTkEntry(master=frame, placeholder_text="Enter Text Here",justify="center",width=200, font=("Consolas", 16))
        url_input.pack(ipady=5,ipadx=5,pady=5)

        btn_gen = customtkinter.CTkButton(master=frame, text="Generate Code", font=("Berlin Sans FB", 16))
        btn_gen.pack(ipady=5,ipadx=5,pady=10,padx=10)
        
        btn_dec = customtkinter.CTkButton(master=frame, text="Decode QR Code", font=("Berlin Sans FB", 16))
        btn_dec.pack(ipady=5,ipadx=5,pady=10,padx=10)

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

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

Внутри функции мы сначала определим корень для всплывающего окна, используя CTkTopLevel. Мы добавляем к нему заголовок “For you!” (поскольку это сообщение для пользователя, я подумал, что оно подойдет :D), фиксируем его размер до 300 на 180. Мы делаем это, устанавливая minsize и maxsize равными другому перед добавлением значок так же, как мы делали это в главном окне приложения (используя iconbitmap). Код должен выглядеть так.

def pop_up(msg:str):
        win = customtkinter.CTkToplevel()
        win.wm_title("For you!")
        win.maxsize(300,180)
        win.minsize(300,180)
        icon = PhotoImage("code.ico")
        win.iconbitmap(icon)

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

btn_gen = customtkinter.CTkButton(master=frame, text="Generate Code", font=("Berlin Sans FB", 16), command=lambda:pop_up("dummy string that won't be displayed"))
btn_gen.pack(ipady=5,ipadx=5,pady=10,padx=10)

Следующее всплывающее окно должно появиться после того, как мы нажмем кнопку.

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

Во-первых, мы добавим рамку с 20 единицами отступа в каждом направлении, метку, которая будет отображать входной аргумент, который использует «Berlin Sans FB» в качестве шрифта с размером шрифта 16 и имеет 10 единиц заполнения в каждом направлении, и наконец, кнопка, которая закроет всплывающее окно после нажатия. Мы достигаем этого, вызывая win.destroy, который закроет (уничтожит) сам себя после вызова.

Наконец, дайте нашей кнопке внутренние отступы 5 в каждом направлении, внешние отступы 10 в каждом направлении, отображаемый текст “OK”, выбор шрифта «Berlin Sans FB» с размером шрифта 16. После того, как мы сделали все, код и всплывающее окно должно выглядеть, как показано ниже.

def pop_up(msg:str):
        win = customtkinter.CTkToplevel()
        win.wm_title("For you!")
        win.maxsize(300,180)
        win.minsize(300,180)
        icon = PhotoImage("code.ico")
        win.iconbitmap(icon)

        frame = customtkinter.CTkFrame(master=win)
        frame.pack(pady=20, padx=20,fill="both", expand=True)

        label = customtkinter.CTkLabel(master=frame, text=msg, font=("Berlin Sans FB", 16))
        label.pack(pady=10, padx=10)

        btn = customtkinter.CTkButton(master=frame, text="OK", font=("Berlin Sans FB", 16),command=win.destroy)
        btn.pack(ipady=5,ipadx=5,pady=10,padx=10)

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

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

Для генерации QR-кода мы начнем с определения функции с именем generate_code. Он будет иметь единственный аргумент строкового типа с именем url. С помощью qrcode.make мы превратим его в QR-код. Прежде чем сохранить его, нам сначала нужно найти пользовательские загрузки (эта часть необязательна, но имеет смысл сохранить ее в загрузках), используя os.getenv(‘USERPROFILE’) и добавив “\\Downloads” в конец.

После этого мы сохраняем наш код, используя code.save. Как только все будет сделано, появится всплывающее окно с сообщением «Код находится в C://user/downloads/folder». Код должен выглядеть следующим образом.

def generate_code(url:str):
    code = qrcode.make(url)
    downloads = f"{os.getenv('USERPROFILE')}\\Downloads"
    code.save(downloads + '/code.png')
    pop_up("The QR code is at >>\n" + downloads)

Однако, прежде чем мы сможем его использовать, нам нужно изменить команду нашей кнопки с pop_up на generate_code и получить строку из поля ввода. Чтобы получить строку из поля ввода, мы можем просто использовать entry.get, который возвращает строку внутри поля ввода и передает ее в качестве аргумента нашей функции.

btn_gen = customtkinter.CTkButton(master=frame, text="Generate Code", font=("Berlin Sans FB", 16), command=lambda:generate_code(url_input.get()))
btn_gen.pack(ipady=5,ipadx=5,pady=10,padx=10)

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

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

Во-первых, нам нужно иметь возможность выбрать файл, содержащий qr-код для декодирования. Для этого мы будем использовать filedialog из tkinter с типами файлов ((“image”, “.jpg”), (“image”, “.jpeg”), (“image”, “.png”)). Как только мы сможем выбрать файл для декодирования, мы будем использовать cv2.QRCodeDetector для его декодирования. Однако, прежде чем мы сможем это сделать, нам нужно использовать cv2.imread, чтобы загрузить его как изображение в наш детектор. Как только все будет сделано, всплывающее окно отобразит текст внутри кода.

def decode():
    qr_code = filedialog.askopenfilename(initialdir="/", title="Select File", filetypes=(("image", ".jpg"), ("image", ".jpeg"), ("image", ".png")))
    code_detector = cv2.QRCodeDetector()
    code_str, _, _ = code_detector.detectAndDecode(cv2.imread(qr_code))
    pop_up(code_str)

После добавления этой функции в качестве команды нашей кнопки декодирования наше приложение теперь может декодировать QR-коды.

И, наконец, у нас есть полнофункциональное приложение. Если вы хотите получить полный код, его можно найти по адресу: