Добавляем графический интерфейс программе для учебных тестов
easy

Добавляем графический интерфейс программе для учебных тестов

Создаём полноценное и полезное десктопное приложение

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

Если вы ещё не пробовали разрабатывать на Python, но интересуетесь этим языком программирования, посмотрите наш мастрид, там много полезного.

Для работы нам будет нужен Python. Если у вас его ещё нет, почитайте это:

Что делаем

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

Прошлая версия прохождения тестов:

Добавляем графический интерфейс программе для учебных тестов

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

Но главное — мы добавим графический интерфейс. Тогда при запуске скрипта наша программа будет выглядеть так:

Добавляем графический интерфейс программе для учебных тестов

Что нужно вспомнить: классы и функции

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

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

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

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

Основы работы с PyQt5

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

Сначала устанавливаем библиотеку в папку нашего проекта при помощи такой команды:

pip install pyqt5

Вот как это сделать из командной строки:

Добавляем графический интерфейс программе для учебных тестов

А так — в среде разработчика:

Добавляем графический интерфейс программе для учебных тестов

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

from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton

Ещё для работы с PyQt5 понадобится встроенный модуль sys. Он нужен для доступа к некоторым возможностям при запуске Python-кода. Например, с его помощью передаётся путь к файлу нашего скрипта.

import sys

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

# функция для создания окна PyQt
def window():
   # создаём процесс основного приложения
   app = QApplication(sys.argv)
   # создаём переменную для окна программы
   win = QMainWindow()
   # задаём размеры и координаты окна на главном экране
   win.setGeometry(400, 200, 600, 300)
   # подписываем название окна
   win.setWindowTitle('Test PyQt')

Обратите внимание, что сначала нужно создать объект процесса и задать ему стандартные аргументы: app = QApplication(sys.argv). Если этого не сделать, ничего не заработает. Можно с помощью команды print вывести в консоли аргумент sys.argv и посмотреть, что в нём лежит:

Аргумент sys.argv: ['/Users/boombro190/Documents/Develop_Projects/CODE_School_Tests/debug.py']

На главном объекте-окне можно создавать другие объекты. Чтобы добавить текст и кнопку, понадобятся модули QLabel и QPushButton. В скобках указываем переменную главного окна как аргумент:

# создаём объект текстовой метки-лейбла внутри нашего окна
label = QLabel(win)
# задаём текст метки
label.setText('my first label')
# задаём позицию метки
label.move(200, 100)
# создаём объект кнопки внутри нашего окна
button1 = QPushButton(win)
# устанавливаем текст кнопки
button1.setText('Click Me!')
# задаём позицию кнопки
button1.move(15, 100)

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

# показываем окно на экране
win.show()
# добавляем корректный выход по нажатию на крестик
sys.exit(app.exec_())

Если вызвать нашу функцию, появится окно с названием, текстом и кнопкой:

Добавляем графический интерфейс программе для учебных тестов

Из чего состоит проект

Новая версия нашей программы будет состоять из трёх логических блоков:

  • импорт всех модулей и библиотек;
  • чтение документа Word и создания заданий в тесте;
  • работа с классами и методами, в которые добавим возможности графического интерфейса.

Сначала импортируем всё, чего не хватает:

from docx import Document
import re
import random
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QMessageBox, QCheckBox

Что мы добавили:

  • Библиотека python-docx нужна для чтения файла Word. Из неё мы возьмём один модуль Document, который даст возможность читать вордовские документы и сохранять из них текст.
  • Модуль re позволяет сгенерировать регулярное выражение. Это формула-шаблон, по которой наша программа найдёт нужные части теста в документе: текст задания, вопросы и ответы.
  • Random даст возможность перемешивать задания и варианты ответов внутри них. Тогда каждый раз при запуске они будут появляться в случайном порядке.
  • Sys нужен для работы библиотеки PyQt5.
  • Из библиотеки PyQt5 мы подключаем к проекту все модули, которые будем использовать. Нам нужно само приложение, модуль для создания класса, макет главного окна, текст, кнопки, всплывающие сообщения и чекбоксы.

Логика чтения документа Word и создания списка с ответами осталась такой же, как в предыдущей версии программы:

  1. Сначала читаем наш файл с тестами и сохраняем всё в одну большую строку.
  2. Потом создаём регулярное выражение в переменной pattern. По этому выражению-шаблону программа найдёт в строке все нужные части.
  3. Создаём список rough_tests, в который по очереди будут попадать текст задания, варианты ответов и правильные ответы из каждого задания.
  4. Добавляем ещё один список result_tests, в котором будут храниться блоки задания. Каждый блок будет хранить один вопрос, все варианты ответа к этому вопросу и правильные ответы. После создания перемешиваем этот список в случайном порядке.

Так выглядит код этого процесса:

# создаём файл документа Word
document = Document('test_fan.docx')
# берём все абзацы документа и добавляем в одну строку, разделяя переносами на новые строки
text = "\n".join(para.text for para in document.paragraphs)
# создаём регулярное выражение, которое ищет все части текста между словами
# 'Задание', 'Варианты ответов' и 'Правильные ответы'
pattern = re.compile(
    r'Задание.+?(?=Варианты ответов)|Варианты ответов.+?(?=Правильные ответы)|Правильные ответы.+?(?=Задание)',
    re.DOTALL)
# создаём список, где идут вопросы, потом варианты ответов, потом правильные ответы
rough_tests = pattern.findall(text)
# создаём список из блоков по 3 элемента: вопросы, потом варианты ответов, потом правильные ответы
result_tests = [rough_tests[i:i + 3] for i in range(0, len(rough_tests), 3)]
# перемешиваем блоки
random.shuffle(result_tests)

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

В общем виде программа будет работать так:

  1. При создании класса сработают два метода: создание главного окна программы и перезагрузка тестов.
  2. Метод создания главного окна подготовит места в визуальном макете, куда потом загрузятся нужные элементы.
  3. Перезагрузка тестов запустит метод загрузки вопроса.
  4. Метод загрузки вопроса отправит в метод создания главного окна всю информацию о задании.
  5. Как только пользователь выберет нужные ответы в окне программы и нажмёт кнопку «Отправить ответ», запустится метод проверки ответов. Он отчитается о результате, запишет статистику и снова запустит метод загрузки вопроса.
  6. Так будет продолжаться, пока в тесте не закончатся задания. После этого программа запустит последний метод — вывода результатов. Затем приложение спросит пользователя, хочет ли он пройти тест заново. Если хочет — всё начнётся с первого пункта.

Пишем функции с графическим интерфейсом

Сначала создадим класс. Для использования PyQt5 он должен наследоваться от стандартного класса QWidge и сохранить его возможности:

# создаём класс теста
# при создании объявляем только два начальных метода для настройки интерфейса и сброса теста
class TestCode(QWidget):
   def __init__(self):
       super().__init__()
       self.init_ui()
       self.reset_quiz()

Внутри класса мы добавили конструктор init: он принимает аргументы и методы, которые будут созданы при создании экземпляра класса. Но наш класс — не просто новая структура, а наследник уже существующего класса QWidget. Это значит, что у него уже есть все методы QWidget и нам не нужно дополнительно их прописывать. Эти методы родительского класса нужно вызвать командой super().__init__().

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

Создаём главное окно программы

Метод init_ui() создаёт главное окно, добавляет текст задания и кнопку для сдачи ответа. Как и для всех методов класса, в скобках после него нужно написать слово self, чтобы показать, что этот метод относится к классу. Все переменные и объекты внутри метода тоже начинаются со слова self, потому что тоже принадлежат одному классу.

Дальше мы возьмём вертикальный макет QVBoxLayout и будем располагать все части вопроса один под другим, в том порядке, каком они появляются в коде: текст задания, варианты ответов, кнопка сдачи.

# подготавливаем интерфейс теста: создаём главное окно и добавляем на него виджеты
def init_ui(self):
   # создаём макет, где все виджеты будут располагаться вертикально, один под другим
   self.layout = QVBoxLayout()

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

Теперь создадим пустой список для вариантов ответов и кнопку.

# создаём метку-текст: в неё будет загружаться вопрос в методе load question
self.question_label = QLabel(self)
# функция добавления виджетов в макет
self.layout.addWidget(self.question_label)
# создаём пустой список для вариантов ответов
self.answer_checkboxes = []
# создаём кнопку для ответа на вопрос
self.submit_button = QPushButton('Отправить ответ', self)

Кнопку свяжем с функцией проверки ответов, которую напишем позже.

# связываем эту кнопку с запуском проверки ответов
self.submit_button.clicked.connect(self.check_answer)

Теперь можно создать макет главного окна, задать его название, размеры и начальную позицию на экране при запуске:

# создаём макет главного окна
self.setLayout(self.layout)
# даём название окну
self.setWindowTitle('Тесты с CODE')
# устанавливаем размеры и позицию на экране при запуске
self.setGeometry(400, 200, 600, 300)

Добавляем метод перезапуска тестов

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

# метод сброса теста к начальному состоянию
# внутри - метод загрузки каждого вопроса load_question
def reset_quiz(self):
   # счётчик для проверки, есть ли неотвеченные вопросы
   self.current_test_index = 0
   # счётчик правильных ответов
   self.right_answers = 0
   # счётчик всех ответов
   self.all_questions = 0

Потом заново перемешиваем все задания и запускаем следующую функцию-метод load_question() для загрузки вопроса:

# перемешиваем вопросы
random.shuffle(result_tests)
# загружаем вопрос
self.load_question()

👉 Обратите внимание: нам понадобилось вызвать всего два метода при создании класса, а они сами вызвали остальные методы, которые им нужны.

Загружаем вопрос

Это самый длинный и сложный метод в программе. Мы объявляем функцию и сравниваем общее количество вопросов с номером текущего вопроса (он на единицу меньше, так как нумерация начинается с нуля). Если номер равен или превышает общее количество вопросов, это значит, что мы спросили всё, что было в тесте, и больше вопросов нет, тест можно останавливать. Если нет — продолжаем тест.

# метод загрузки следующего вопроса
# запускается изнутри метода reset_quiz
def load_question(self):
   # проверяем счётчик оставшихся вопросов
   if self.current_test_index < len(result_tests):

Первую часть метода целиком берём из предыдущей версии нашей программы. Вот как в ней всё работает:

  1. Берём задание из списка result_tests.
  2. Задание выглядит как список с тремя элементами. Мы берём второй элемент block[1], в нём хранятся варианты ответов. Убираем оттуда ненужные слова и разделяем на отдельные строки — по одной на каждый вариант.
  3. После этого берём строку с правильными вариантами ответов — это элемент block[2]. Извлекаем из него номера ответов и создаём из них множество целых чисел. Теперь правильные ответы легко будет проверить.
  4. Варианты ответов мы тоже перемешаем, поэтому нам понадобится хитрая манипуляция, чтобы не забыть, какой ответ правильный. В результате у нас будет новое множество правильных ответов после перемешивания new_correct_answers.

На языке Python это записывается так:

# если в списке result_tests ещё остались блоки с заданиями, загружаем следующий блок
block = result_tests[self.current_test_index]
# создаём список вариантов ответов из строки
variants = block[1].replace('Варианты ответов:', '').replace('\n', '').split(';')
# создаём множество правильных ответов для проверки
old_answers = set(map(int, block[2].split(":")[1].strip().split(", ")))
# нумеруем варианты ответов...
indexed_variants = list(enumerate(variants, start=1))
# и перемешиваем их
random.shuffle(indexed_variants)
# делаем новый список без номеров из перемешанных ответов
self.new_variants = [var for _, var in indexed_variants]
# определяем новые правильные ответы: для этого проверяем, какой ответ на каком месте стоял раньше
self.new_correct_answers = {new_index for new_index, (old_index, _)
                           in enumerate(indexed_variants, start=1)
                           if old_index in old_answers}

На этом месте мы подключаем графический интерфейс. Сначала передаём текст задания в переменную question_label, которая уже создана в методе init_ui() и ждёт текст, чтобы вывести его на экран.

После этого нужно учесть, что метод загрузки ответов будет вызываться за один тест несколько раз. Чтобы каждый раз всё работало как с чистого листа, в методе мы удаляем старые чекбоксы с вариантами ответов и кнопку отправки ответов, чтобы она не уплыла наверх, когда мы удалим чекбоксы. Для этого используем встроенные в PyQt5 функции removeWidget() и deleteLater():

# загружаем в метку-лейбл текст вопроса из блока задания
self.question_label.setText(block[0])
# удаляем старые чекбоксы
for checkbox in self.answer_checkboxes:
   self.layout.removeWidget(checkbox)
   checkbox.deleteLater()
# создаём пустой список чекбоксов
self.answer_checkboxes = []
# временно удаляем кнопку "Отправить ответ", чтобы она не появилась наверху
if self.submit_button in [self.layout.itemAt(i).widget() for i in range(self.layout.count())]:
   self.layout.removeWidget(self.submit_button)

Для удаления кнопки мы перебираем все элементы макета. Если среди них есть объект submit_button, удаляем его.

Создаём чекбоксы с вариантами ответов для текущего задания и кнопку проверки. Теперь она точно будет внизу:

# создаём новые чекбоксы
for i, variant in enumerate(self.new_variants):
   checkbox = QCheckBox(f"{i + 1}. {variant}", self)
   self.layout.addWidget(checkbox)
   self.answer_checkboxes.append(checkbox)
# добавляем кнопку "Отправить ответ" внизу
self.layout.addWidget(self.submit_button)

Если первое условие метода показало, что новых заданий нет, выводим результат всего теста. Для этого вызываем функцию show_results():

# если неотвеченных заданий нет, показываем результат теста
else:
   self.show_results()

Проверяем ответы

Эта часть работает так:

  1. Создаём множество user_answers_set из номеров чекбоксов, которые отметил пользователь.
  2. Сравниваем этот множество со множеством правильных ответов new_correct_answers, которое мы вычислили в функции load_question.
  3. Если множества совпали, ответ верный. Увеличиваем счётчик правильных ответов и выводим всплывающее сообщение: «Верно!»
  4. Если множества не совпали, ответ ошибочный. Увеличиваем счётчик всех ответов и номер текущего ответа, а на экран выводим сообщение: «Неверно!»
  5. В конце снова запускаем функцию загрузки следующего вопроса load_question. Если счётчик current_test_index сравнялся с длиной списка, вместо очередного задания функция выдаст результаты всего теста.

Код метода проверки ответов:

# функция проверки ответов
def check_answer(self):
   # добавляем во множество номера чекбоксов, который отметил пользователь
   user_answers_set = {i + 1 for i, checkbox in enumerate(self.answer_checkboxes) if checkbox.isChecked()}
   # сравниваем множество ответов пользователя и множество правильных ответов
   if user_answers_set == self.new_correct_answers:
       # если правильно, увеличиваем счётчик правильных ответов и запускаем сообщение
       self.right_answers += 1
       QMessageBox.information(self, 'Результат', "Верно!")
   else:
       # если неправильно, запускаем сообщение
       QMessageBox.information(self, 'Результат', "Неверно!")
   # увеличиваем счётчики всех ответов, всех блоков и загружаем следующий вопрос
   self.all_questions += 1
   self.current_test_index += 1
   self.load_question()

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

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

Появляется всплывающее сообщение:

reply = QMessageBox.question(self, 'Тест завершён',
                            f'Вы ответили правильно на {self.right_answers} вопросов из {self.all_questions}.'
                            f'\nХотите попробовать ещё раз?',
                            # даём два варианта ответа и по умолчанию выбираем QMessageBox.No,
                            # который будет запущен по кнопке Esc или Enter
                            QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

Внутри сообщения пользователь увидит счётчики ответов right_answers и all_questions. Ещё в сообщении будет две кнопки для ответа на вопрос о повторном прохождении теста: да или нет, — поэтому при желании тест всегда можно пройти заново.

Дальше в зависимости от выбора запускаем тест снова или закрываем программу:

# запускаем повторный тест или закрываем программу в зависимости от ответа
if reply == QMessageBox.Yes:
   self.reset_quiz()
else:
   self.close()

Всё готово, тесты можно запускать:

Добавляем графический интерфейс программе для учебных тестов

Что можно сделать ещё

У нас есть полноценное работающее десктопное приложение, но улучшить его тоже можно:

  • запустить проект на сервере;
  • подключить фронтенд и сделать ещё более красивый интерфейс;
  • добавить возможность работы с заданиями с выбором только одного ответа (сейчас в чекбоксы можно поставить несколько);
  • выводить вместе с текстом вопроса изображения;
  • создать исполняемый файл для MacOS и Windows.

Займёмся этим в следующий раз.

from docx import Document
import re
import random
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QMessageBox, QCheckBox

# создаём файл документа Word
document = Document('test.docx')
# берём все абзацы документа и добавляем в одну строку, разделяя переносами на новые строки
text = "\n".join(para.text for para in document.paragraphs)
# создаём регулярное выражение, которое ищет все части текста между словами
# 'Задание', 'Варианты ответов' и 'Правильные ответы'
pattern = re.compile(
   r'Задание.+?(?=Варианты ответов)|Варианты ответов.+?(?=Правильные ответы)|Правильные ответы.+?(?=Задание)',
   re.DOTALL)
# создаём список, где идут вопросы, потом варианты ответов, потом правильные ответы
rough_tests = pattern.findall(text)
# создаём список из блоков по 3 элемента: вопросы, потом варианты ответов, потом правильные ответы
result_tests = [rough_tests[i:i + 3] for i in range(0, len(rough_tests), 3)]
# перемешиваем блоки
random.shuffle(result_tests)


# создаём класс теста. при создании объявляем только два
# начальных метода для настройки интерфейса и сброса теста
class TestCode(QWidget):
   def __init__(self):
       super().__init__()
       self.init_ui()
       self.reset_quiz()

   # подготавливаем интерфейс теста: создаём главное окно и добавляем на него виджеты
   def init_ui(self):
       # создаём макет, где все виджеты будут располагаться вертикально, один под другим
       self.layout = QVBoxLayout()
       # создаём метку-текст: в неё будет загружаться вопрос в методе load question
       self.question_label = QLabel(self)
       # функция добавления виджетов в макет
       self.layout.addWidget(self.question_label)
       # создаём пустой список для вариантов ответов
       self.answer_checkboxes = []
       # создаём кнопку для ответа на вопрос
       self.submit_button = QPushButton('Отправить ответ', self)
       # связываем эту кнопку с запуском проверки ответов
       self.submit_button.clicked.connect(self.check_answer)
       # создаём макет главного окна
       self.setLayout(self.layout)
       # даём название окну
       self.setWindowTitle('Тесты с CODE')
       # устанавливаем размеры и позицию на экране при запуске
       self.setGeometry(400, 200, 600, 300)

   # метод сброса теста к начальному состоянию
   # внутри - метод загрузки каждого вопроса load_question
   def reset_quiz(self):
       # счётчик для проверки, есть ли неотвеченные вопросы
       self.current_test_index = 0
       # счётчик правильных ответов
       self.right_answers = 0
       # счётчик всех ответов
       self.all_questions = 0
       # перемешиваем вопросы
       random.shuffle(result_tests)
       # загружаем вопрос
       self.load_question()

   # метод загрузки следующего вопроса
   # запускается изнутри метода reset_quiz
   def load_question(self):
       # проверяем счётчик оставшихся вопросов
       if self.current_test_index < len(result_tests):
           # если в списке result_tests ещё остались блоки с заданиями, загружаем следующий блок
           block = result_tests[self.current_test_index]
           # создаём список вариантов ответов из строки
           variants = block[1].replace('Варианты ответов:', '').replace('\n', '').split(';')
           # создаём множество правильных ответов для проверки
           old_answers = set(map(int, block[2].split(":")[1].strip().split(", ")))
           # нумеруем варианты ответов...
           indexed_variants = list(enumerate(variants, start=1))
           # и перемешиваем их
           random.shuffle(indexed_variants)
           # делаем новый список без номеров из перемешанных ответов
           self.new_variants = [var for _, var in indexed_variants]
           # определяем новые правильные ответы: для этого проверяем, какой ответ на каком месте стоял раньше
           self.new_correct_answers = {new_index for new_index, (old_index, _)
                                       in enumerate(indexed_variants, start=1)
                                       if old_index in old_answers}
           # загружаем в метку-лейбл текст вопроса из блока задания
           self.question_label.setText(block[0])
           # удаляем старые чекбоксы
           for checkbox in self.answer_checkboxes:
               self.layout.removeWidget(checkbox)
               checkbox.deleteLater()
           # создаём пустой список чекбоксов
           self.answer_checkboxes = []
           # временно удаляем кнопку "Отправить ответ", чтобы она не появилась наверху
           if self.submit_button in [self.layout.itemAt(i).widget() for i in range(self.layout.count())]:
               self.layout.removeWidget(self.submit_button)
           # создаём новые чекбоксы
           for i, variant in enumerate(self.new_variants):
               checkbox = QCheckBox(f"{i + 1}. {variant}", self)
               self.layout.addWidget(checkbox)
               self.answer_checkboxes.append(checkbox)
           # добавляем кнопку "Отправить ответ" внизу
           self.layout.addWidget(self.submit_button)
       # если неотвеченных заданий нет, показываем результат теста
       else:
           self.show_results()

   # функция проверки ответов
   def check_answer(self):
       # добавляем во множество номера чекбоксов, который отметил пользователь
       user_answers_set = {i + 1 for i, checkbox in enumerate(self.answer_checkboxes) if checkbox.isChecked()}
       # сравниваем множество ответов пользователя и множество правильных ответов
       if user_answers_set == self.new_correct_answers:
           # если правильно, увеличиваем счётчик правильных ответов и запускаем сообщение
           self.right_answers += 1
           QMessageBox.information(self, 'Результат', "Верно!")
       else:
           # если неправильно, запускаем сообщение
           QMessageBox.information(self, 'Результат', "Неверно!")
       # увеличиваем счётчики всех ответов, всех блоков и загружаем следующий вопрос
       self.all_questions += 1
       self.current_test_index += 1
       self.load_question()

   # функция для вывода результатов после теста
   def show_results(self):
       # cообщение выводит результаты теста и спрашивает, хочет ли пользователь пройти тест ещё раз
       reply = QMessageBox.question(self, 'Тест завершён',
                                    f'Вы ответили правильно на {self.right_answers} вопросов из {self.all_questions}.'
                                    f'\nХотите попробовать ещё раз?',
                                    # даём два варианта ответа и по умолчанию выбираем QMessageBox.No,
                                    # который будет запущен по кнопке Esc или Enter
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
       # запускаем повторный тест или закрываем программу в зависимости от ответа
       if reply == QMessageBox.Yes:
           self.reset_quiz()
       else:
           self.close()


# основная функция запуска тестов
def main():
   # создаём основной класс программы и передаём все нужные аргументы для запуска
   app = QApplication(sys.argv)
   # создаём класс теста
   quiz_app = TestCode()
   # выводим инициализированный тест на экран
   quiz_app.show()
   # добавляем возможность закрытия
   sys.exit(app.exec_())


# запускаем основную функцию
main()

Редактор:

Инна Долога

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Соцсети:

Юлия Зубарева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Вам может быть интересно
easy