Как создать более сложные графические интерфейсы и виджеты на Python?

Вопрос или проблема

Мне сложно создать графический интерфейс пользователя (GUI) на Python (используя Tkinter), так как, насколько я знаю, в нём нет таких вещей, как переключатели. И я не нашёл хороших ресурсов по этой теме. (TKdocs довольно запутанный и трудно понимаемый) Я прочитал книгу “Современный Tkinter”, и из неё я узнал довольно много и могу использовать основные концепции Tkinter. Но некоторые вещи устарели. Многие найденные мною материалы по Python, на самом деле, такие. Например, “Учебник Python” и “Библия Python” не охватывают Match/Case. Я узнал о них только после того, как спросил здесь. Я также использую видео от Bro Codes по Python, в котором кажется есть информация о match case.

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

Я слышал, что PyQt может это сделать. Но я тоже не знаю хороших ресурсов по этому поводу.

Я пытался создать программу викторины, но хотя я выяснил, как редактировать radiobuttonы с использованием textvariables, я не могу проверить, какую кнопку я выбрал. И затем решить, правильный ли это ответ. “Современный Tkinter” не углублялся в детали виджетов. В целом, программирование GUI на Python вызывает разочарование. Но это весело.

Моя проблема в том, что я не хочу создавать самый простой графический интерфейс. Я хочу делать с ним интересные вещи. Например, иметь виджеты карточек или хотя бы переключатель. В моей программе викторины есть кнопки “Далее” и “Назад”, которые переключаются между вопросами. Чем я горжусь. Это супер просто. Я не смог понять, как уничтожать виджеты. Поэтому мне пришлось сделать несколько обходных путей. И я получил общее представление о match/case.

В дополнение к комбобоксу, который показывает, на каком вопросе вы находитесь. (я ещё не выяснил, как его заставить менять вопрос) Я хотел бы хранить вопросы/ответы в другом файле (чтобы я мог переключаться между наборами вопросов и ответов через главное меню). Но “Современный Tkinter” не описал, как переключить страницу на другую. Наилучшее, что я могу придумать, это либо поэкспериментировать с виджетами notebook, либо попробовать заставить работать меню (но это также не слишком хорошо объяснено, и почему-то меню не работают, когда я пытаюсь их использовать, просто открывается отдельное пустое окно вместо панели с опциями.

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

Вот код:

from tkinter import *
from tkinter import ttk
import random
from time import *

root = Tk()
root.title("[Введите заголовок]")
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
frame = Frame(root)
frame.grid(column=0, row=0)
s = ttk.Style()
s.configure('a.TFrame', background=None, borderwidth=1,
            relief=None)
quest = 0


class Questions:
    a = """Каков результат 2 + 2?              """
    b = """Каков результат 3 умножить на 15?         """
    c = """Каков результат 21 разделить на 3?"""
    d = """Каков результат 2 - 12              """


def assignprompt():
    global quest
    match quest:
        case 1:
            prompt.set(Questions.a)
            root.update()
        case 2:
            prompt.set(Questions.b)
            root.update()
        case 3:
            prompt.set(Questions.c)
            root.update()
        case 4:
            prompt.set(Questions.d)
            root.update()


def assignchoice():
    global quest
    match quest:
        case 1:
            num.set(1)
            A.set("6")
            B.set("43")
            C.set("3")
            D.set("4")
        case 2:
            num.set(2)
            A.set("56")
            B.set("45")
            C.set("72")
            D.set("32")
        case 3:
            num.set(3)
            A.set("7")
            B.set("64")
            C.set("6")
            D.set("12")
        case 4:
            num.set(4)
            A.set("10")
            B.set("3")
            C.set("-10")
            D.set("-2")
        case 5:
            num.set(5)
            A.set("1")
            B.set("4")
            C.set("5")
            D.set("9")
        case 6:
            num.set(6)
            A.set("-12")
            B.set("43")
            C.set("-1")
            D.set("2")
        case 7:
            num.set(7)
            A.set("10")
            B.set("6")
            C.set("69")
            D.set("12")
        case 8:
            num.set(8)
            A.set("10")
            B.set("3")
            C.set("303")
            D.set("2")
        case 9:
            num.set(9)
            A.set("57")
            B.set("64")
            C.set("23")
            D.set("14")
        case 10:
            num.set(10)
            A.set("150")
            B.set("35")
            C.set("150")
            D.set("-25")


A = StringVar()
B = StringVar()
C = StringVar()
D = StringVar()
ttk.Radiobutton(frame, textvariable=A, value=1).grid(column=0, row=1, sticky=W)
ttk.Radiobutton(frame,textvariable=B, value=2).grid(column=0, row=2, sticky=W)
ttk.Radiobutton(frame, textvariable=C, value=3).grid(column=0, row=3, sticky=W)
ttk.Radiobutton(frame, textvariable=D, value=4).grid(column=0, row=4, sticky=W)



def assign():
    global quest
    if quest > 10:
        quest = 0
    elif quest < 1:
        quest = 10
    assignprompt()
    assignchoice()


def previous():
    global quest
    quest -= 1

    assign()


def nextq():
    global quest
    quest += 1
    assign()


num = IntVar()
prompt = StringVar()
check = StringVar()
Label(frame,
      textvariable=prompt).grid(column=4, row=0, sticky=E)
Label(frame,
      textvariable=check).grid(column=4, row=0, sticky=E)
Button(frame, text="Назад",
       command=previous).grid(column=1, row=0, sticky=W)
Button(frame, text="Далее",
       command=nextq).grid(column=3, row=0, sticky=W)
ttk.Combobox(frame, height=2, width=3, textvariable=num,
             values=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
             state="readonly").grid(column=0, row=0)
root.mainloop()

Это в процессе разработки. Я думаю, что использование переменной quest для переключения вопросов, вероятно, неэффективно и проблематично. Но я не понял, как уничтожать radiobuttons при переключении вопросов. (Я действительно смог заставить переключаться вопросы и создавать новые виджеты, я просто не знал, как уничтожать старые виджеты. Поэтому изменение текущих виджетов — это то, с чем я столкнулся).

Я также пытался сделать так, чтобы радиокнопки выбирались из диапазона случайных чисел, но я не знал, как избежать повторений и удостовериться, что хотя бы один из них является правильным ответом. Поэтому я просто ввёл заданные ответы вручную. И также мне нужно было убедиться, что ответы достаточно похожи, чтобы не было очевидно, какой из них правильный, поэтому я попытался выбрать из списка. Затем снова столкнулся с проблемой повторов и необходимости одной правильной ответы.

Я бы также хотел иметь возможность случайным образом назначать вопросы из пула. (Может быть, у меня будет словарь с вопросом в качестве ключа и ответом в качестве значения? Или может быть нет, потому что тогда я не смог бы использовать сам вопрос, да?)

Другие проблемы связаны с размещением виджетов. Кнопка "Далее" не останется на месте, если метка в той же колонке изменит ширину, возможно, я мог бы использовать place() для этого. Я думаю, что это недостаток менеджера grid(), если нет встроенного исправления.

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

У меня была проблема с проверкой, какая радиокнопка была выбрана. (например, я не мог просто вызвать get() на переменной и проверить, какое значение оно возвращает. Затем сказать: "О, они выбрали вариант 1, вариант 1 - это 2, и вопрос 2 - '2 + 2', Неверно".

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

Ответ или решение

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

Использование PyQt для создания сложных GUI

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

Ресурсы для изучения PyQt

  1. Документация PyQt - Официальная документация является отличным ресурсом для изучения различных компонентов и механизмов.
  2. Книга "Rapid GUI Programming with Python and Qt" - Эта книга помогает понять, как быстро разрабатывать приложения с использованием PyQt.
  3. Онлайн-курсы - Платформы, такие как Udemy или Coursera, предлагают курсы по PyQt.

Углубление в Tkinter

Если вы решите остаться с Tkinter, то вам следует использовать следующее:

1. Собственные классы для виджетов

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

class CustomRadiobutton(ttk.Radiobutton):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

2. Управление размещением виджетов

Используйте метод pack() или place(), если столкнулись с проблемами с grid(). Например, в некоторых случаях pack() может обеспечить более предсказуемое размещение.

3. Управление жизненным циклом виджетов

Чтобы удалить старые виджеты перед созданием новых, используйте метод destroy():

for widget in frame.winfo_children():
    widget.destroy()

4. Хранение вопросов и проверки ответов

Вместо статического определения вопросов в коде, используйте словари или JSON-файлы для хранения вопросов и ответов. Это позволяет легко добавлять новые вопросы или изменять существующие:

questions = {
    "What is 2 + 2?": ["3", "4", "5", "6"],
    "What is the capital of France?": ["Berlin", "Madrid", "Paris", "Rome"]
}

Используйте метод random.choice(), чтобы случайным образом выбирать вопросы.

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

Чтобы связать изменение значения в Combobox с определенной функцией, используйте метод bind():

def on_combobox_change(event):
    selected_question = combobox.get()
    # обновите вопрос в интерфейсе

combobox.bind("<<ComboboxSelected>>", on_combobox_change)

Программные рекомендации

Разработайте свой вопрос-ответный процесс, используя структурированный подход:

  • Создайте класс, который управляет состоянием вопросов и ответов.
  • Реализуйте логику избегания повторяющихся вопросов, храня answered questions в списке.
  • Убедитесь, что интерфейс адаптивен, чтобы избежать проблем с размещением при изменении текста меток и кнопок.

Заключение

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

Если у вас есть дополнительные вопросы или вам нужна помощь по конкретным аспектам вашего проекта, пожалуйста, дайте знать!

Оцените материал
Добавить комментарий

Капча загружается...