Академический Документы
Профессиональный Документы
Культура Документы
Второе издание
Джошуа М Уиллман
Начало работы с PyQt: Практический подход к программированию графических интерфейсов с PyQt6
Распространяется в книжной торговле по всему миру издательством Springer Science+Business Media New
York, 1 New York Plaza, New York, NY 10004. Телефон 1-800-SPRINGER, факс (201) 348-4505, электронная
почта orders-ny@springer-sbm.com или посетите сайт www.springeronline.com. Apress Media, LLC является
калифорнийским ООО, а единственным участником (владельцем) является Springer Science + Business Media
Finance Inc (SSBM Finance Inc). SSBM Finance Inc является корпорацией штата Делавэр корпорация.
Для получения информации о переводах, пожалуйста, пишите по адресу booktranslations@springernature.com;
для получения прав на перепечатку, права на издание в мягкой обложке или аудио, пожалуйста, пишите по
адресу bookpermissions@springernature.com.
Издания Apress можно приобрести оптом для академического, корпоративного или рекламного использования.
Лицензии на большинство изданий также доступны. За дополнительной информацией обращайтесь к нашей
странице “Оптовые продажи печатных изданий и электронных книг” на сайте веб-странице по адресу
http://www.apress.com/bulk-sales.
Любой исходный код или другой дополнительный материал, на который автор ссылается в этой книге,
доступен для читателям на GitHub через страницу продукта книги, расположенную по адресу https://github.
com/Apress/Beginning-PyQt--second-edition. Для получения более подробной информации посетите сайт http://
www.apress.com/source-code.
VI ОГЛАВЛЕНИЕ
Глава 3: Добавление дополнительной функциональности с помощью виджетов.......23
Обработчики событий, сигналы и слоты................................................................................... 23
Виджет QPushButton................................................................................................................. 24
Объяснение использования QPushButton............................................................................ 25
Виджет QLineEdit...................................................................................................................... 28
Объяснение использования QLineEdit.................................................................................. 28
Виджет QCheckBox.................................................................................................................... 31
Объяснение использования QCheckBox................................................................................ 32
Диалоговое окно QMessageBox.................................................................................................. 34
Окна и диалоги..................................................................................................................... 35
Объяснение использования QMessageBox............................................................................ 35
Проект 3.1 - Графический интерфейс входа в систему и диалог регистрации............................. 41
Проектирование графического интерфейса входа в систему и диалогового окна регистрации.. 42
Объяснение создания графического интерфейса входа в систему....................................... 43
Пояснения к созданию главного окна.................................................................................. 52
Объяснение создания диалогового окна регистрации......................................................... 53
Резюме..................................................................................................................................... 57
ОГЛАВЛЕНИЕ VII
Глава 5: Меню, панели инструментов и многое другое..................................................96
Общая практика создания меню................................................................................................................ 97
Создание простой панели меню.................................................................................................................. 97
Пояснения к созданию панели меню.................................................................................................... 98
Использование значков и класса QIcon..................................................................................................... 102
Пояснения к использованию значков................................................................................................. 102
Установка центрального виджета....................................................................................................... 106
Встроенные классы диалогов в PyQt........................................................................................................ 106
Класс QFileDialog................................................................................................................................... 106
Класс QInputDialog................................................................................................................................ 107
Класс QFontDialog.................................................................................................................................. 108
Класс QColorDialog................................................................................................................................. 109
О QMessageBox...................................................................................................................................... 110
Проект 5.1 - Графический интерфейс блокнота с насыщенным текстом................................................. 111
Проектирование графического интерфейса блокнота Rich Text Notepad.......................................... 112
Пояснения к графическому интерфейсу блокнота Rich Text Notepad................................................. 113
Расширение возможностей главного окна............................................................................................... 122
Пояснение к расширению возможностей........................................................................................... 123
Проект 5.2 - Простой графический интерфейс редактора фотографий................................................... 131
Проектирование графического интерфейса редактора фотографий................................................ 132
Пояснения к графическому интерфейсу редактора фотографий...................................................... 132
Резюме........................................................................................................................................................ 146
VIII ОГЛАВЛЕНИЕ
Глава 7: Обработка событий в PyQt...............................................................................184
Обработка событий в PyQt......................................................................................................................... 184
Использование сигналов и слотов....................................................................................................... 185
Использование обработчиков событий для обработки событий...................................................... 185
Разница между сигналами, слотами и обработчиками событий...................................................... 186
Обработка ключевых событий.................................................................................................................. 186
Объяснение обработки ключевых событий....................................................................................... 187
Обработка событий мыши........................................................................................................................ 188
Объяснение обработки событий мыши.............................................................................................. 189
Создание пользовательских сигналов...................................................................................................... 193
Пояснения к созданию пользовательских сигналов.......................................................................... 193
Резюме........................................................................................................................................................ 196
ОГЛАВЛЕНИЕ IX
Глава 10: Представление данных в PyQt......................................................................245
Быстрая работа с данными в PyQt............................................................................................................ 245
Класс QListWidget........................................................................................................................................ 246
Пояснения к использованию QListWidget........................................................................................... 247
Перетаскивание в PyQt.............................................................................................................................. 250
Пояснения к перетаскиванию.............................................................................................................. 251
Класс QTableWidget..................................................................................................................................... 253
Пояснения к использованию QTableWidget........................................................................................ 254
Класс QTreeWidget....................................................................................................................................... 262
Пояснения к использованию QTreeWidget.......................................................................................... 263
Резюме........................................................................................................................................................ 266
X ОГЛАВЛЕНИЕ
Глава 13: Работа с Qt Quick.............................................................................................318
Описания QtQuick и QML............................................................................................................................. 319
Элементы в QtQuick.............................................................................................................................. 320
Введение в язык и синтаксис QML........................................................................................................ 321
Создание и запуск QML-компонентов........................................................................................................ 324
Создание и загрузка QML-компонентов.............................................................................................. 325
Создание многократно используемых компонентов........................................................................ 330
Обработка макетов в QML.................................................................................................................... 332
Создание и загрузка QML-окон............................................................................................................. 336
Использование трансформаций для анимации объектов...................................................................... 342
Пояснения к простым преобразованиям........................................................................................... 342
Пояснения к использованию преобразований для анимации объектов......................................... 344
Резюме........................................................................................................................................................ 347
ОГЛАВЛЕНИЕ XI
Глава 16: Дополнительные проекты............................................................................396
Проект 16.1 - Графический интерфейс просмотра каталогов................................................................... 397
Пояснения к графическому интерфейсу просмотра каталогов.......................................................... 398
Проект 16.2 - Графический интерфейс камеры......................................................................................... 400
Пояснения к графическому интерфейсу камеры............................................................................... 402
Проект 16.3 - П Project 16.7 – Tri-state ростой графический интерфейс часов.......................................... 407
Пояснения к графическому интерфейсу часов................................................................................... 407
Проект 16.4 - Графический интерфейс календаря.................................................................................... 410
Пояснения к графическому интерфейсу календаря........................................................................... 410
Проект 16.5 - Графический интерфейс "Виселица"................................................................................... 414
Пояснения к графическому интерфейсу "Виселица".......................................................................... 415
Проект 16.6 - Графический интерфейс веб-браузера................................................................................ 423
Пояснения к графическому интерфейсу веб-браузера...................................................................... 425
Проект 16.7 - Трехсоставной QComboBox................................................................................................... 434
Пояснения к трехсоставному QComboBox........................................................................................... 434
Резюме ....................................................................................................................................................... 437
XII ОГЛАВЛЕНИЕ
ОГЛАВЛЕНИЕ XIII
Об авторе
Джошуа Уиллман - инженер-программист с более чем 12-летним опытом
разработки приложений преимущественно на Python и C++. Его карьера позволила
ему принять участие в многих различных областях, от робототехники, машинного
обучения и компьютерного зрения до разработки пользовательского интерфейса,
разработки игр, и многое другое. Его первым опытом работы с PyQt было создание
интерфейса для упрощения процесса маркировки наборов данных для машинного
обучения. С тех пор он просто влюбился!
В последние годы его страсть к программированию
и всему визуальным эффектам позволила ему
принять участие в многочисленных проектах.
Среди них разработка образовательных курсов по
мобильной робототехнике и компьютерному зрению
с использованием Arduino и Raspberry Pi, создание
приложений с графическим интерфейсом, а также
работа в качестве одиночного разработчика инди-
игр. В настоящее время он работает инженером по
робототехнике, техническим писателем и создателем
контента (в свободное время изучает веб-разработку,
чтобы создать свою собственную платформу
redhuli.io). Когда он не работает, ему нравится возиться с проектами по робототехнике
и проводить время со своей замечательной женой и дочерью.
Он также является автором двух книг, выпущенных издательством Apress:
• Beginning PyQt: Практический подход к программированию графических
интерфейсов (1-е издание).
• Современный PyQt: Создание приложений с графическим интерфейсом для
управления проектами, компьютерное зрение и анализ данных
XIV
О технических рецензентах
Викас Кумар имеет более семи лет совокупного опыта
работы в авионике, аэрокосмической, автомобильной и в
сфере здравоохранения в научно-исследовательской де-
ятельности и разработке программного обеспечения. Он
программировал и разрабатывал настольные приложения
с использованием C++, Qt, PyQt/PySide, Python, Java и SQL
с самого начала. Имеет степень бакалавра технологий в об-
ласти компьютерных наук и инженерии от Биджу Patnaik
University of Technology, Одиша, Индия.
Он работал с различными индийскими оборонными
заказчиками для разработки тестового пакета для авионики для тестирования ком-
понентов различных военных самолетов. Сотрудничал с компанией Airbus в каче-
стве клиента для разработки различного программного обеспечения, отвечающего
за структурный и вычислительный анализ различных коммерческих самолетов. Со-
трудничал с компанией Mercedes-Benz для разработки программного обеспечения,
отвечающего за анализ данных, моделирование и тестирование различных коммер-
ческих самолетов, анализ данных, моделирование и валидацию высоковольтных
электрических батарей, используемых в электромобилях Mercedes-Benz. Его техни-
ческие навыки включают C++, Qt, PyQt/PySide, Python, Java, MySQL и разработку на-
стольных приложений в Linux и Windows. В настоящее время он работает старшим
инженером-программистом в компании GE Healthcare India, занимаясь разрабаткой
программного обеспечения для МРТ-сканеров.
Саумитра Джагдале является основателем компании
Open Cloudware и Посол глобального искусственного интел-
лекта, уделяющий особое внимание современным тенденци-
ям в области технологий. Он является признанным техни-
ческим автором для различных известных медиа-компаний,
таких как OpenSystems Media, CNX Software, AspenCore,
Electronics-Lab и IoT Tech Trends. Кроме того, он является
старшим инженером - облачных сервисов и программно-
го обеспечения в компании L&T Infotech с опытом работы
в области CRM и ERP-приложений. Будучи разработчиком
Python с открытым исходным кодом, он также возглавляет
сообщество TensorFlow Community Индия для продвижения
методологии глубокого обучения в сообществе.
XV
Благодарности
Я безмерно благодарен Дивии Моди, Селестину Сурешу Джону и замечательному
коллективу издательства «Apress» за предоставленную мне возможность написать
второе издание этой книги. Моя глубочайшая признательность Дивии за то, что она
была рядом на каждом шагу.
Особая благодарность Андреа Касадеи, чьи вопросы очень помогли улучшить это
издание.
Огромная благодарность сообществам Python, PyQt и Qt. Я бы также очень
поблагодарил Фила Томпсона, создателя PyQt.
Я очень благодарен Ричарду Броноски за то, что он дал мне шанс, когда я больше
всего в нем нуждался.
Ашиш Наик, спасибо тебе за поддержку, которую ты оказывал моей семье и мне
с самого начала.
Спасибо моей маме, Валори, и моим сестрам, Тише и Джазмин, за поддержку,
которую вы всегда оказывали мне.
Я глубоко благодарен моей жене, Эвелин Йе, чье постоянное терпение по
отношению ко мне делает эти книги возможными.
Калани, ты продолжаешь быть моей мотивацией и вдохновением.
Еще раз спасибо читателям. Я искренне надеюсь, что идеи, найденные в этой книге
подстегнут ваше творчество и принесут вам какую-то пользу.
XVI
Введение
С новыми версиями PyQt появляются новые инструменты для игры. Последнее
издание направлено на изучение некоторых из этих идей, при этом ориентируясь
на новичков. Вы узнаете, как использовать язык программирования Python вместе
с набором инструментов PyQt6 для создания графических пользовательских
интерфейсов (GUI).
Важнее всего просто начать. Кодирование графического интерфейса
пользователя можно рассматривать как сочетание навыков программирования
и графического дизайна. Осознание понимания потребностей пользователя
также имеет решающее значение для улучшения удобства использования и
графического вида. Программирование GUI часто сводится к выбору подходящего
компонента, называемого в PyQt виджетами, для выполнения задачи, а затем
применить необходимые навыки программирования, чтобы заставить их
работать.
Одна из целей состоит в том, чтобы сбалансировать теорию, лежащую в основе
хороших методов проектирования, с более практическими занятиями, примерами
кодирования в стиле “учись как хочешь”. Новые концепции и классы PyQt вводятся
в каждой главе, а последующие главы иногда опираются на предыдущие.
XVII
Как организована эта книга
Последнее издание книги “Знакомство с PyQt” начинается со знакомства с
основными идеями разработки графических интерфейсов пользователя. Глава 1
поможет вам установить и понять, как использовать последнюю версию PyQt.
Главы 2 и 3 учат, как добавлять виджеты в ваши приложения, тем самым
добавляя все больше и больше функциональности в ваши проекты. В обеих главах
представлены различные виджеты, такие как QLabel, QCheckBox и QLineEdit, а также
приводятся примеры и идеи их использования.Глава 3 также познакомит вас с
механизмом сигналов и слотов PyQt для обработки событий.
Глава 4 посвящена менеджерам компоновки для расположения виджетов.
После изучения различных виджетов и макетов, Глава 5 проведет вас через
примеры, которые помогут вам создать классический графический интерфейс с
меню и панелями инструментов.
В Главе 6 представлены таблицы стилей для изменения внешнего вида ваших
приложений.
В Главе 7 рассматривается обработка событий, происходящих в графическом
интерфейсе, например, нажатие пользователем кнопки мыши. Вы также узнаете, как
создавать собственные сигналы и как повторно реализовать обработчики событий.
Поскольку Qt также включает свой собственный графический интерфейс
пользователя, помогающий создавать графические интерфейсы, мы рассмотрим, как
использовать Qt Designer в Главе 8.
Отсюда мы начнем изучать более продвинутые концепции.
Глава 9 познакомит вас с использованием буфера обмена для копирования и
вставки информации между приложениями.
В Главе 10 показано, как работать с данными, используя классы удобства PyQt,
основанные на элементах. Вы также узнаете, как добавить базовую функциональность
перетаскивания к виджетам.
Глава 11 знакомит вас с рисованием, графикой и анимацией.
Настройка очень важна в PyQt. Глава 12 показывает, как создавать и использовать
свои собственные виджеты.
В Главе 13 рассказывается о Qt Quick для создания текучих и динамичных
приложений.
В Главе 14 показано, как использовать для создания пользовательских интерфейсов,
работающих с данными с помощью SQL баз данных и архитектуры PyQt Model/View.
В Главе 15 обсуждается многопоточное программирование, позволяющее
избежать «зависания» ваших приложений.
Глава 16 содержит дополнительные примеры проектов, которые помогут вам
получить дополнительную практику и понимание создания приложений с помощью
PyQt.
Приложение содержит дополнительную информацию о различных модулях
и классах PyQt. В приложении также можно найти дополнительный пример
кодирования.
XVIII
Понимание структуры и кода глав
Код в последнем издании, как правило, разбит на более мелкие части (если
только программа не представляет собой короткий пример). Это делает код более
легким для усвоения и понимания, а также избавляет пользователей от дампов
кода (копирования и вставки целых разделов). Вы всегда можете обратиться к
репозиторию GitHub (ссылка находится в разделе “Ссылки на исходный код”), чтобы
увидеть примеры кода целиком.
Кроме того, следуя примеру, обратите внимание на заголовки листингов над
каждым фрагментом кода. Они дают подсказки по каждому разделу, а также помогают
понять, какой пример кода вы просматриваете. Например, в Главе 2 представлены
метки. Код, объясняющий, как использовать метки, разбит на две части, листинг 1 и
листинг 2. Заголовок первого листинга выглядит следующим образом:
Листинг 1. Настройка главного окна для демонстрации использования виджетов
QLabel
# labels.py
Если фрагмент кода не содержит номера листинга, значит, этот код предоставляет
дополнительную информацию, но не находится ни в одном из файлов.
Кроме того, при необходимости в тексте указываются важные различия между
PyQt5 и PyQt6.
Еще одним важным замечанием является то, что PyQt разработан как
кроссплатформенный. Однако мир не совершенен, и иногда разработчикам даются
дополнительные пояснения для того, чтобы их код работал на Windows, macOS или
Linux (Ubuntu). Обязательно посмотрите на комментарии или примечания, чтобы
получить ясность при запуске приложения.
Наконец, по мере чтения ключевые слова выделяются жирным шрифтом.
Имена файлов, имена модулей и классов Python и PyQt, а также фрагменты кода,
упоминаемые в тексте, отображаются другим шрифтом, например, QPushButton.
XIX
Ссылки на исходный код
Исходный код для Beginning PyQt: практический подход к
программированию GUI с помощью PyQt6 можно найти на GitHub по адресу
https://github.com/Apress/Beginning-PyQt--second-edition
Отзывы читателей
Ваши отзывы, вопросы и идеи всегда приветствуются. Если у вас есть какие-либо
вопросы об этой книге, PyQt версии 5 или 6, разработке графического интерфейса
или вы просто хотите оставить комментарий, вы всегда можете найти меня по адресу
redhuli.comments@gmail.com.
XX
ГЛАВА 1
Фреймворк PyQt
Инструментарий PyQt - это набор привязок Python для кроссплатформенного
инструментария виджетов и приложений Qt. Что это значит?
Во-первых, Qt используется для разработки пользовательских интерфейсов и
других приложений и разрабатывается компанией The Qt Company. Эта платформа
важна тем, что она может работать на многочисленных программных и аппаратных
системах, таких как Windows, macOS, Linux, Android или встроенные системы,
практически не изменяя базовый код и сохраняя возможности и скорость
системы, на которой она работает. В общем, вы сможете создавать удивительные
кроссплатформенные приложения, не беспокоясь о платформе пользователя.
Во-вторых, все это означает, что PyQt сочетает в себе все преимущества
кроссплатформенного набора виджетов Qt C++ и Python, мощного и простого,
кроссплатформенного интерпретируемого языка. Стоит отметить, что хотя PyQt
имеет свою собственную документацию, документация Qt, как правило, более
полная. Если вы никогда раньше не использовали C++, это может показаться сложной
ГЛАВА 1 НАЧАЛО РАБОТЫ С PYQT 1
задачей. Мы обсудим эту тему немного подробнее в первом приложении этой главы.
Для получения дополнительной информации о Qt загляните на www.qt.io.
Более подробную информацию о PyQt можно найти на сайте
www.riverbankcomputing.com/news.
Совет. Для тех читателей, которые не хотят удалять текущую версию Python
и хотят управлять несколькими версиями Python в своей системе, обратите
внимание на инструмент управления версиями Python, pyenv.
Установка PyQt6
Поскольку PyQt не входит в комплект поставки Python, следующим шагом будет
использование pip для установки пакета PyQt6 из Python Package Index (PyPI). Для
создания связки между Python и C++ используется инструмент SIP binding generator.
При загрузке PyQt6 из PyPI автоматически загружается и модуль sip.
Чтобы установить PyQt6, введите в командную оболочку следующую команду:
$ pip3 install PyQt6
Если вы используете Windows, вам, вероятно, придется изменить pip3 на pip.
Чтобы убедиться, что PyQt загружен правильно, откройте интерпретатор Python 3,
введя python3 (python для Windows) в командной строке. Затем введите следующую
команду:
>>> import PyQt6
class EmptyWindow(QWidget):
def __init__(self):
"""Конструктор для класса "Пустое окно"""
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка приложения."""
self.setGeometry(200, 100, 400, 300)
self.setWindowTitle("Пустое окно в PyQt")
self.show() # Отображение окна на экране
Ваше начальное окно должно выглядеть так же, как на Рисунке 1-1, в зависимости
от вашей операционной системы.
Проходя по коду, мы сначала начнем с импорта модулей sys и PyQt6, которые нам
нужны для создания окна. Модуль sys может использоваться в PyQt для передачи
аргументов командной строки нашим приложениям и для их закрытия.
Модуль QtWidgets предоставляет классы виджетов, которые понадобятся для
создания графических интерфейсов в стиле рабочего стола. Из модуля QtWidgets
мы импортируем два класса: QApplication и QWidget. Вам нужно создать только один
экземпляр класса QApplication, независимо от того, сколько окон или диалоговых окон
существует в приложении. QApplication отвечает за управление главным циклом
событий приложения, а также за инициализацию и завершение работы виджетов.
Главный цикл событий - это место, где происходит управление взаимодействием
пользователя с окном графического интерфейса, например, нажатие на кнопку.
Взгляните на
app = QApplication(sys.argv)
QApplication принимает в качестве аргумента sys.argv. Вы также можете передать
пустой список, если вы знаете, что ваша программа не будет принимать никаких
аргументов командной строки, используя
app = QApplication([])
Совет. При создании окон в PyQt вы обычно создаете основной класс, который
наследуется от QMainWindow, QWidget или QDialog. Подробнее о каждом из
этих классов и о том, когда их использовать для создания окон и диалоговых
окон, вы узнаете в последующих главах.
Нам нужно вызвать метод show() для объекта окна, чтобы вывести его на экран.
Он находится внутри функции initializeUI() в нашем классе EmptyWindow. Вы можете
видеть, как app.exec() передается в качестве аргумента в sys.exit() в последней строке
Листинга 1-1. Метод exec() запускает цикл событий приложения и будет находиться
в нем до тех пор, пока вы не выйдете из приложения. Функция sys.exit() обеспечивает
чистый выход.
Шаги по созданию окна лучше проиллюстрированы в Листинге 1-2 с помощью
процедурного программирования - парадигмы программирования, в которой
компьютер выполняет набор последовательных команд для выполнения задачи.
Листинг 1-2. Минимальный код, необходимый для создания пустого окна в PyQt
без ООП
# procedural.py
# 1. Импортируйте необходимые модули
import sys # использовать sys для приема аргументов командной строки
from PyQt6.QtWidgets import QApplication, QWidget
Изменение окна
Класс EmptyWindow в Листинге 1-1 содержит метод initializeUI(), который создает
окно на основе заданных нами параметров. Функция initializeUI() воспроизводится в
следующем фрагменте кода:
Резюме
В этой главе мы рассмотрели, как подготовиться к созданию графических
интерфейсов с помощью PyQt6. Создание удобных графических интерфейсов
очень важно, и есть несколько понятий, о которых следует помнить при разработке
приложений, таких как последовательность и ясность, чтобы помочь пользователям
понять назначение и возможности вашего приложения. Наконец, мы рассмотрели
основные классы и методы, необходимые для создания и модификации простого
главного окна.
В следующей главе вы узнаете больше о разработке графического интерфейса
пользователя. Вы узнаете, как добавлять текст и изображения в графические
интерфейсы с помощью виджета QLabel, а также изучите один из методов
расположения виджетов в окнах.
Создание простого
графического
интерфейса
И снова здравствуйте! Графические интерфейсы предназначены для выполнения
конкретных задач, таких как написание и редактирование документов или
воспроизведение видео. Создание любого пользовательского интерфейса может
показаться сложной задачей, поскольку необходимо учитывать всевозможные
виджеты. Виджеты - это кнопки, флажки, ползунки и другие компоненты, с помощью
которых пользователи взаимодействуют с графическим интерфейсом.
Для того чтобы помочь вам узнать о различных видах виджетов и понять, когда вы
можете захотеть их использовать, в каждой главе мы будем использовать различные
виджеты и применять их в одном или нескольких проектах GUI. Каждый раз, когда
будет представлен новый виджет, вы также будете применять этот компонент в
небольших практических приложениях, прежде чем использовать их в более крупных
графических интерфейсах. Для некоторых больших приложений мы также обсудим
процесс проектирования и расположения виджетов в окне приложения.
В этой главе вы
• Начнете изучать виджеты в PyQt и узнаете, как использовать их в своих
графических интерфейсах
• Рассмотрите процесс проектирования простого приложения с графическим
интерфейсом пользователя
• Построите базовые графические интерфейсы, используя в основном виджет
QLabel
• Узнайте, как организовать виджеты в графическом интерфейсе с помощью
метода move().
Давайте начнем с изучения очень фундаментального виджета.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setGeometry(100, 100, 250, 250)
self.setWindowTitle("Пример QLabel")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
def initializeUI(self):
"""Настройки графического интерфейса приложения."""
self.setGeometry(50, 50, 250, 400)
self.setWindowTitle("2.1 - User Profile GUI")
self.setUpMainWindow()
self.show()
# Run program
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
developer_label = QLabel(self)
developer_label.setText("Python Разработчик")
developer_label.move(15, 310)
dev_dates_label = QLabel(self)
dev_dates_label.setText("Март 2011 - настоящее время")
dev_dates_label.setFont(QFont("Arial", 10))
dev_dates_label.move(15, 330)
driver_label = QLabel(self)
driver_label.setText("Водитель доставки пиццы")
driver_label.move(15, 350)
driver_dates_label = QLabel(self)
driver_dates_label.setText("Aug 2015 - Dec 2017")
driver_dates_label.setFont(QFont("Arial", 10))
driver_dates_label.move(15, 370)
Добавление
дополнительной
функциональности с
помощью виджетов
Что хорошего в пользовательском интерфейсе, если он не интерактивен?
Эта глава посвящена изучению того, как использовать виджеты для создания
отзывчивых пользовательских интерфейсов, которые реагируют на взаимодействие с
пользователем, обрабатывают различные события и передают важную информацию
обратно пользователю. Мы рассмотрим несколько распространенных виджетов
и увидим, как использовать их для проектирования и создания приложений с
графическим интерфейсом.
В этой главе вы
• Познакомитесь с обработкой событий и механизмом сигналов и слотов Qt
• Создадите графические интерфейсы, используя новые классы виджетов,
включая QPushButton, QLineEdit, QCheckBox и QMessageBox
• Узнайте о полезных методах выравнивания текста и настройки размеров
виджетов.
• Узнайте больше об окнах и диалоговых окнах и посмотрите, как создавать
классы, наследующие от QDialog
• Создайте приложение, которое научит работать с несколькими окнами.
Прежде чем перейти к коду, давайте узнаем немного об обработке событий в PyQt.
Виджет QPushButton
Виджет QPushButton можно использовать для выполнения действий и выбора.
Когда вы нажимаете на виджет QPushButton, он посылает сигнал, который может
быть подключен к функции. Хотя вы обычно встречаете кнопки с текстом: OK, Next,
Cancel, Close, Yes или No, вы также можете создавать свои собственные кнопки с
описательным текстом или значками.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Set up the application's GUI."""
self.setGeometry(100, 100, 250, 150)
self.setWindowTitle("QPushButton Example")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Совет. Если вам нужно несколько строк для ввода текста пользователем,
используйте виджет QTextEdit.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMaximumSize(310, 130)
self.setWindowTitle("Пример QLineEdit")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
self.name_edit = QLineEdit(self)
self.name_edit.resize(210, 20)
self.name_edit.move(70, 50)
Два объекта QLabel - это просто примеры создания виджетов. Вы можете создавать
виджеты и располагать их в своем графическом интерфейсе без необходимости
присваивать их переменной.
Объект QLineEdit, name_edit, является примером того, как изменить размер
виджета с помощью метода resize(). Вам потребуется указать желаемые значения
высоты и ширины виджета.
Объекты clear_button и accept_button связаны со слотами clearText() и acceptText(),
созданными в Листинге 3-6.
Листинг 3-6. Код для слотов clearText() и acceptText()
# line_edits.py
def clearText(self):
"""Очистить поле ввода QLineEdit."""
self.name_edit.clear()
def acceptText(self):
"""Принять ввод пользователя в виджете QLineEdit
и закрыть программу."""
print(self.name_edit.text())
self.close()
30 ГЛАВА 3 ДОБАВЛЕНИЕ ФУНКЦИОНАЛЬНОСТИ С ПОМОЩЬЮ ВИДЖЕТОВ
Когда нажимается кнопка clear_button, она издает сигнал, который подключается
к слоту clearText(), и виджет name_edit реагирует на этот сигнал и очищает свой
текущий текст. Если пользователь нажимает кнопку accept_button, текст в name_edit
считывается с помощью геттера text() и выводится в оболочку компьютера. После
этого приложение закрывается.
Давайте рассмотрим еще один часто встречающийся виджет в настольных
приложениях.
Виджет QCheckBox
Виджет QCheckBox представляет собой выбираемую кнопку, которая обычно
имеет два состояния: включено и выключено.
Это делает их идеальными для представления функций в вашем графическом
интерфейсе, которые могут быть включены или выключены, или для выбора из
списка вариантов, как в опросе.
Приложение на Рисунке 3-3 показывает базовый графический интерфейс анкеты.
Пользователю разрешается выбрать все флажки, которые к нему относятся, и каждый
раз, когда пользователь нажимает на флажок, мы вызываем метод, чтобы показать,
как определить текущее состояние виджета.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setGeometry(100, 100, 250, 150)
self.setWindowTitle("Пример QCheckBox")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
# Установите флажки
morning_cb = QCheckBox("Утро [8 утра-14 дня]", self)
morning_cb.move(40, 60)
#morning_cb.toggle() # Удалите комментарий, чтобы начать проверять
morning_cb.toggled.connect(self.printSelected)
Рисунок 3-4. Вывод в терминал при установке или снятии флажков с различных
checkbox-ов
Давайте рассмотрим еще один очень важный класс для создания интерактивных
и удобных графических интерфейсов.
Окна и диалоги
Приложения обычно состоят из одного главного окна. Окно используется для
визуального отделения приложений друг от друга и обычно состоит из меню, панели
инструментов и других видов виджетов, которые часто могут выступать в качестве
основного интерфейса GUI-приложения. Окна в Qt обычно считаются виджетами,
которые появляются на экране и не имеют родительского виджета.
Диалоговое окно, или просто диалог, всплывает и отображает опции или
информацию, пока пользователь работает в главном окне. Большинство видов
диалоговых окон имеют родительское окно, которое используется для определения
положения диалога относительно его владельца. Это также означает, что между
окном и диалоговым окном происходит обмен данными, и диалоговые окна могут
использоваться для обновления главного окна.
Существует два вида диалоговых окон. Модальные диалоги блокируют
взаимодействие пользователя с остальной частью программы до тех пор, пока
диалоговое окно не будет закрыто. Бесмодальные диалоги позволяют пользователю
взаимодействовать как с диалоговым окном, так и с остальной частью приложения.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setGeometry(100, 100, 340, 140)
self.setWindowTitle("Пример QMessageBox")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Чтобы создать окно, показанное на Рисунке 3-5, вам потребуется создать несколько
объектов QLabel, виджет QLineEdit для ввода пользователем имени автора и объект
QPushButton, который издает сигнал при нажатии и ищет текст в текстовом файле.
Все это реализовано в Листинге 3-11.
search_label = QLabel(
"Поиск автора в индексе:", self)
search_label.move(20, 40)
self.author_edit = QLineEdit(self)
self.author_edit.move(70, 70)
self.author_edit.resize(240, 24)
self.author_edit.setPlaceholderText(
"Введите имя как: Имя Фамилия")
try:
with open(file, "r") as f:
authors = [line.rstrip("\n") for line in f]
if answer == QMessageBox.StandardButton.No:
print("Закрытие приложения.")
self.close()
except FileNotFoundError as error:
QMessageBox.warning(self, "Ошибка",
f"""<p>Файл не найден.</p>
<p>Error: {error}</p>
Закрытие приложения.""",
QMessageBox.StandardButton.Ok)
self.close()
Рисунок 3-10. Главное окно приложения, которое появляется при успешном входе
пользователя в систему. Изображение зимородка взято с сайта https://pixabay.com.
В следующем разделе обсуждаются различные окна, виджеты и их функциональные
возможности в этом приложении.
class LoginWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setFixedSize(360, 220)
self.setWindowTitle("3.1 – Login GUI")
self.setUpWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = LoginWindow()
sys.exit(app.exec())
self.username_edit = QLineEdit(self)
self.username_edit.resize(250, 24)
self.username_edit.move(90, 50)
self.password_edit = QLineEdit(self)
self.password_edit.setEchoMode(QLineEdit.EchoMode.Password)
self.password_edit.resize(250, 24)
self.password_edit.move(90, 82)
try:
with open(file, "r") as f:
for line in f:
user_info = line.split(" ")
username_info = user_info[0]
password_info = user_info[1].strip("\n")
users[username_info] = password_info
def openApplicationWindow(self):
"""Открываем макет главного окна после входа пользователя в систему."""
self.main_window = MainWindow()
self.main_window.show()
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(640, 426)
self.setWindowTitle('3.1 - Главное окно')
self.setUpMainWindow()
def setUpMainWindow(self):
"""Создайте и расположите виджеты в главном окне."""
image = "images/background_kingfisher.jpg"
try:
with open(image):
main_label = QLabel(self)
pixmap = QPixmap(image)
main_label.setPixmap(pixmap)
main_label.move(0, 0)
except FileNotFoundError as error:
print(f"Изображение не найдено.\nError: {error}")
class NewUserDialog(QDialog):
def __init__(self):
super().__init__()
self.setModal(True)
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setFixedSize(360, 320)
self.setWindowTitle("3.1 - Графический интерфейс регистрации")
self.setUpWindow()
try:
with open(user_image):
user_label = QLabel(self)
pixmap = QPixmap(user_image)
user_label.setPixmap(pixmap)
user_label.move(150, 60)
except FileNotFoundError as error:
print(f "Изображение не найдено. Ошибка: {error}")
self.name_edit = QLineEdit(self)
self.name_edit.resize(250, 24)
self.name_edit.move(90, 140)
full_name_edit = QLineEdit(self)
full_name_edit.resize(250, 24)
full_name_edit.move(90, 170)
self.new_pswd_edit = QLineEdit(self)
self.new_pswd_edit.setEchoMode(
QLineEdit.EchoMode.Password)
self.new_pswd_edit.resize(250, 24)
self.new_pswd_edit.move(90, 200)
self.confirm_edit = QLineEdit(self)
self.confirm_edit.setEchoMode(
QLineEdit.EchoMode.Password)
self.confirm_edit.resize(250, 24)
self.confirm_edit.move(90, 230)
Эти виджеты используются для сбора имени пользователя, полного имени, пароля
и дополнительного виджета QLineEdit для проверки правильности ввода пароля.
Кнопка для подтверждения данных настроена в Листинге 3-26.
Листинг 3-26. Добавление кнопки регистрации в метод setUpWindow() диалогового
окна регистрации
# registration.py
# Создаем кнопку регистрации QPushButton
sign_up_button = QPushButton("Регистрация", self)
sign_up_button.resize(320, 32)
sign_up_button.move(20, 270)
sign_up_button.clicked.connect(self.confirmSignUp)
Резюме
В этой главе вы получили опыт создания графических интерфейсов с
использованием различных виджетов - QPushButton, QLineEdit, QCheckBox и
QMessageBox. Используя различные виджеты, вы также смогли узнать о других
важных концепциях для создания графических интерфейсов в PyQt, а именно:
обработка событий, взаимодействие между виджетами с помощью сигналов и слотов,
разница между окнами и диалогами, и как создавать приложения с несколькими
окнами. Все эти понятия в этой главе закладывают основу для создания больших,
более отзывчивых графических интерфейсов.
Однако есть еще несколько концепций, которые необходимо изучить для
создания GUI-приложений. В следующей главе вы узнаете еще об одной из этих
фундаментальных тем - управления макетами.
Изучение управления
макетом
По мере роста приложений, организация и управление расположением и
размерами всех виджетов в пользовательском интерфейсе может стать сложной
задачей. Хорошей новостью является то, что PyQt делает процесс организации
виджетов относительно простым благодаря встроенным менеджерам компоновки,
или классам, которые обрабатывают большинство тонкостей организации виджетов
в графических интерфейсах.
В этой главе вы
• узнаете о классах менеджера компоновки PyQt и примените их в
многочисленных графических интерфейсах пользователя
• рассмотрите, какой менеджер компоновки лучше всего подходит для вашего
приложения
• узнаете, как создавать сложные макеты с вложенными макетами
• изучите несколько различных методов управления виджетами в менеджерах
компоновки, таких как добавление пробелов или работа с политикой
размеров
• используйте множество новых классов, включая QComboBox, QSpinBox,
QDoubleSpinBox, QButtonGroup, QTextEdit, QDateEdit и другие, чтобы расширить
свой инструментарий и знания о создании невероятных графических
интерфейсов.
• расширите свой опыт работы с ранее изученными классами виджетов при
создании практических приложений
Давайте узнаем больше о менеджерах компоновки и о том, как использовать их в
PyQt.
Абсолютное позиционирование
Одним из методов расположения виджетов в интерфейсе является абсолютное
позиционирование, которое подразумевает указание размеров и положения
дочернего виджета внутри родительского. Эта идея была представлена и использована
в Главах 2 и 3 с помощью метода QWidget move(). Это было сделано для того, чтобы вы
могли получить фундаментальное понимание того, как использовать пространство в
окне, а также для того, чтобы вы могли воочию убедиться в полезности использования
менеджеров компоновки.
Итак, почему вы хотите использовать абсолютное позиционирование? Абсолютное
позиционирование может быть наиболее полезным для установки положения и
размеров виджетов, которые находятся внутри других виджетов, или, возможно, для
изменения расположения окна на рабочем столе.
Если вы решите использовать абсолютное позиционирование, следует помнить о
нескольких недостатках. Во-первых, изменение размера главного окна не приведет
к изменению размера или положения виджетов в нем. Во-вторых, различия
между операционными системами, такие как шрифты и размеры шрифтов, могут
кардинально изменить внешний вид и расположение виджетов в интерфейсе на
разных платформах. Наконец, использование абсолютного позиционирования
требует больших затрат времени разработчика, поскольку ему придется вычислять
точный размер и положение каждого виджета, одновременно решая такие проблемы,
как изменение размеров окна, добавление или удаление виджетов в графическом
интерфейсе.
В следующем разделе вы рассмотрите свой первый менеджер компоновки для
расположения виджетов по горизонтали или вертикали.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumWidth(500)
self.setFixedHeight(60)
self.setWindowTitle("Пример QHBoxLayout")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
class MainWindow(QWidget):
def __init__(self):
64 ГЛАВА 4 ИЗУЧЕНИЕ УПРАВЛЕНИЯ МАКЕТОМ
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(350, 200)
self.setWindowTitle("Пример QVBoxLayout")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Класс QButtonGroup
Часто у вас может быть несколько флажков или кнопок, которые необходимо
сгруппировать вместе, чтобы облегчить управление ими. К счастью, в PyQt есть
класс QButtonGroup, который помогает управлять связанными кнопками, делая их
взаимоисключающими. Это может быть полезно, если вы хотите, чтобы одновременно
# vertical_box.py
ratings = ["Удовлетворен", "Средне", "Не удовлетворен"]
ratings_group = QButtonGroup(self)
ratings_group.buttonClicked.connect(
self.checkboxClicked)
self.confirm_button = QPushButton("Подтвердить")
self.confirm_button.setEnabled(False)
self.confirm_button.clicked.connect(self.close)
main_v_box = QVBoxLayout()
main_v_box.addWidget(header_label)
main_v_box.addWidget(question_label)
for cb in range(len(ratings)):
rating_cb = QCheckBox(ratings[cb])
ratings_group.addButton(rating_cb)
main_v_box.addWidget(rating_cb)
main_v_box.addWidget(self.confirm_button)
self.setLayout(main_v_box)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Set up the application's GUI."""
self.setMinimumSize(400, 160)
self.setWindowTitle("Nested Layout Example")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
food_combo1 = QComboBox()
food_combo1.addItems(food_list)
food_combo2 = QComboBox()
food_combo2.addItems(food_list)
self.price_sb2 = QSpinBox()
self.price_sb2.setRange(0, 100)
self.price_sb2.setPrefix("₽")
self.price_sb2.valueChanged.connect(
self.calculateTotal)
item2_h_box = QHBoxLayout()
item2_h_box.addWidget(food_combo2)
item2_h_box.addWidget(self.price_sb2)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(500, 300)
self.setWindowTitle("QGridLayout пример")
self.setUpMainWindow()
self.loadWidgetValuesFromFile()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Виджет QTextEdit
Когда пользователю требуется область для ввода или редактирования более одной
строки текста за раз, класс QTextEdit хорошо подходит для изменения обычного
или насыщенного текста и включает встроенные функции редактирования, такие
74 ГЛАВА 4 ИЗУЧЕНИЕ УПРАВЛЕНИЯ МАКЕТОМ
как копирование, вставка и вырезание. Виджет может работать с символами или
абзацами текста. Параграфы - это просто длинные строки, которые заворачиваются
в виджет и заканчиваются символом новой строки. QTextEdit также полезен для
отображения списков, изображений и таблиц или для предоставления интерфейса
для отображения текста с помощью HTML.
Мы можем начать создание виджетов в левой части главного окна в Листинге 4-12,
начиная с ярлыка(label) заголовка, объектов QTextEdit и соответствующих им ярлыков.
Листинг 4-12. Метод setUpMainWindow() для использования QGridLayout, Часть 1
# grid.py
def setUpMainWindow(self):
"""Создать и расположить виджеты в главном окне."""
header_label = QLabel("Простой ежедневник")
header_label.setFont(QFont("Arial", 20))
header_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
notes_label = QLabel("•Заметки")
notes_label.setFont(QFont("Arial", 14))
self.notes_tedit = QTextEdit()
todo_label = QLabel("•Делать")
todo_label.setFont(QFont("Arial", 14))
Значения для виджетов QTextEdit и QLineEdit хранятся в Python dict, что упрощает
процесс сохранения данных в формате JSON. Чтобы вернуть простой текст для
today_tedit и notes_tedit, используйте метод QTextEdit toPlainText(). Если вам нужно
вернуть насыщенный текст, используйте toHtml().
Когда виджеты добавляются в макеты, они добавляются как объекты QLayoutItem.
Метод QGridLayout itemAtPosition() используется для получения элементов по
заданным значениям строки и столбца. Перебирая индексы строк от 2 до 8, мы
можем узнать, какие виджеты QCheckBox отмечены с помощью функции isChecked().
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(500, 400)
self.setWindowTitle("QFormLayout пример")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
При создании полей редактирования имейте в виду, что вам не нужно будет
создавать виджет QLabel, который обычно предшествует им. С помощью QFormLayout
можно добавить два виджета подряд. Пока мы просто установим виджеты
редактирования текста в Листинге 4-18.
self.first_name_edit = QLineEdit()
self.first_name_edit.setPlaceholderText("Имя")
self.first_name_edit.textEdited.connect(
self.clearText)
self.last_name_edit = QLineEdit()
self.last_name_edit.setPlaceholderText("Фамилия")
self.last_name_edit.textEdited.connect(self.clearText)
self.phone_edit = QLineEdit()
self.phone_edit.setInputMask("(999) 999-9999;_")
self.phone_edit.textEdited.connect(self.clearText)
self.birthdate_edit = QDateEdit()
self.birthdate_edit.setDisplayFormat("yyyy/MM/dd")
self.birthdate_edit.setMaximumDate(
QDate.currentDate())
self.birthdate_edit.setCalendarPopup(True)
self.birthdate_edit.setDate(QDate.currentDate())
extra_info_tedit = QTextEdit()
self.feedback_label = QLabel()
submit_button = QPushButton("ОТПРАВИТЬ")
submit_button.setMaximumWidth(140)
submit_button.clicked.connect(
self.checkFormInformation)
main_form.addRow(header_label)
main_form.addRow("Имя", name_h_box)
main_form.addRow("Пол", gender_combo)
main_form.addRow("Дата рождения", self.birthdate_edit)
main_form.addRow("Телефон", self.phone_edit)
main_form.addRow("Email", self.email_edit)
main_form.addRow(QLabel("Комментарии или сообщения"))
main_form.addRow(extra_info_tedit)
main_form.addRow(submit_h_box)
def checkFormInformation(self):
"""Демонстрирует несколько случаев для проверки пользовательского
ввода."""
if self.first_name_edit.text() == "" or \
self.last_name_edit.text() == "":
self.feedback_label.setText(
"[INFO] Пропущенны имена.")
elif self.phone_edit.hasAcceptableInput() == False:
self.feedback_label.setText(
"[INFO] Неправильно введен номер телефона.")
elif self.email_edit.hasAcceptableInput() == False:
self.feedback_label.setText(
"[INFO] Email введен неверно.")
Рисунок 4-7. Пример нескольких страниц в одном окне. Изображение кошки взято
с сайта https://pixabay.com.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setFixedSize(300, 340)
self.setWindowTitle("Пример QStackedLayout")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
age_sb = QSpinBox()
age_sb.setRange(0, 30)
pg3_form.addRow("Возраст:", age_sb)
weight_dsb = QDoubleSpinBox()
weight_dsb.setRange(0.0, 30.0)
pg3_form.addRow("Вес (кг):", weight_dsb)
pg3_container = QWidget()
pg3_container.setLayout(pg3_form)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(300, 200)
self.setWindowTitle("Пример расположения")
main_v_box = QVBoxLayout()
main_v_box.addWidget(label)
main_v_box.addSpacing(20)
main_v_box.addWidget(line_edit)
main_v_box.addStretch()
main_v_box.addWidget(button)
self.setLayout(main_v_box)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Резюме
Уделив время изучению управления компоновкой, вы сэкономите время и усилия
при создании собственных приложений с графическим интерфейсом. В этой главе
мы рассмотрели множество способов организации виджетов в интерфейсе PyQt. В
зависимости от требований вашего приложения, существует множество вариантов,
включая абсолютное позиционирование и любой из встроенных менеджеров
компоновки Qt.
Боксовые макеты, QHBoxLayout и QVBoxLayout, используются для горизонтального
или вертикального расположения виджетов. QFormLayout отлично подходит для
построения форм. QGridLayout полезен для стратегической организации и определения
размеров виджетов в макете, напоминающем сетку. Наконец, QStackedLayout можно
использовать, когда вам нужно управлять пространством в окне, укладывая виджеты
друг на друга. Каждый класс имеет свой особый случай использования, но настоящая
сила заключается в том, насколько удобно их комбинировать для создания сложных
компоновок.
Преимущества использования менеджера компоновки включают контроль над
положением дочерних виджетов, возможность устанавливать размеры по умолчанию
для виджетов, простоту использования для изменения размеров виджетов и
упрощение обновления содержимого окна или родительского виджета, когда что-то
меняется, например, скрытие, отображение или удаление дочернего виджета. Кроме
того, вы можете создавать и компоновать свой интерфейс графически с помощью
Qt Designer. Мы рассмотрим, как это сделать в Главе 8.
В Главе 5 мы рассмотрим, как создавать главные окна, включающие панели меню,
док-виджеты и многое другое.
Меню, панели
инструментов и многое
другое
Настольные приложения предоставляют средства не только для организации
виджетов, но и для организации и представления интерактивных функций и
инструментов пользователям. Меню - это список команд, которые может выполнять
компьютерная программа, представленный в более удобном и организованном виде.
Многочисленные устройства и системы включают меню, помогающие пользователю
ориентироваться и выбирать различные опции и задачи, а хорошо организованное
меню сделает программу более удобной в использовании.
В этой главе вы
• Узнаете, как создавать главные окна, которые наследуются от QMainWindow
• Создавать меню, подменю и проверяемые пункты меню, используя классы
PyQt, такие как QMenuBar и QAction
• Узнайте, как устанавливать и изменять иконки виджетов и главного окна
• Узнайте о встроенных классах диалоговых окон PyQt, включая QFileDialog,
QInputDialog, QColorDialog и другие.
• Настраивать и использовать строку состояния приложения для
предоставления обратной связи
• Используйте класс QDockWidget для создания съемных виджетов, которые
могут отображать общие инструменты и операции.
• Создавайте практические приложения, обучающие дополнительным
навыкам, таким как манипулирование изображениями с помощью классов
QPixmap и QTransform и настройка приложений для печати изображений с
помощью класса QPrinter.
Эта глава создаст основу для полностью функционирующих программ, которые
можно использовать сразу или как отправную точку для ваших собственных
программ.
Давайте начнем с рассмотрения общих приемов, используемых при создании
меню.
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(450, 350)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
def createActions(self):
"""Создание действий в меню приложения."""
# Создание действий для меню "Файл
self.quit_act = QAction("&Выход")
self.quit_act.setShortcut("Ctrl+Q")
100 ГЛАВА 5 МЕНЮ, ПАНЕЛИ ИНСТРУМЕНТОВ И МНОГОЕ ДРУГОЕ
self.quit_act.triggered.connect(self.close)
def createMenu(self):
"""Создайте строку меню приложения."""
self.menuBar().setNativeMenuBar(False)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Рисунок 5-4. Оригинальный значок приложения в левом верхнем углу окна (слева)
может быть установлен на новый значок (справа) с помощью метода setWindowIcon().
Главное окно, созданное в Листинге 5-4, состоит из QLabel, которая предоставляет
инструкции пользователю, и QPushButton для выбора одного из изображений
фруктов, содержащихся в списке изображений.
self.images = [
"images/1_apple.png", "images/2_pineapple.png",
"images/3_watermelon.png", "images/4_banana.png"]
self.icon_button = QPushButton()
self.icon_button.setIcon(
QIcon(random.choice(self.images)))
self.icon_button.setIconSize(QSize(60, 60))
self.icon_button.clicked.connect(
self.changeButtonIcon)
Класс QFileDialog
Класс QFileDialog можно использовать для открытия и выбора файлов или
каталогов на вашем компьютере. Этот класс диалоговых окон полезен для открытия,
сохранения и именования файлов.
Чтобы открыть локальный файл, используется метод QFileDialog getOpenFileName().
Пример этого показан в следующем фрагменте кода:
file_name, ok = QFileDialog.getSaveFileName(self,
"Save File", "/Users/user_name/Desktop/",
"Text Files (*.txt)")
file_name, ok = QFileDialog.getOpenFileName(self,
"Open File", "/Users/user_name/Desktop/",
"Image Files (*.png *.jpg *.bmp)",
options = QFileDialog.Option.DontUseNativeDialog)
Класс QInputDialog
QInputDialog - это собственный диалог в PyQt, который может быть использован
для получения ввода от пользователя. Вводом является одно значение, которое
может быть строкой, числом или элементом из списка.
Чтобы создать базовый диалог ввода и получить текст от пользователя:
Класс QFontDialog
QFontDialog предоставляет диалоговое окно, которое позволяет пользователю
выбирать и манипулировать различными типами шрифтов. Чтобы создать
диалоговое окно шрифта и выбрать шрифт, используйте метод getFont():
font, ok = QFontDialog.getFont()
Ключевое слово font - это конкретный шрифт, возвращаемый из getFont(), а ok -
это булева переменная для проверки того, выбрал ли пользователь шрифт и нажал
ли кнопку OK. Диалоговое окно шрифта на macOS показано на рисунке 5-6.
Чтобы изменить шрифт, если был выбран новый, используйте метод setCurrentFont()
и измените его на новый шрифт.
Класс QColorDialog
Класс QColorDialog создает диалоговое окно для выбора цветов, как показано на
Рисунке 5-7. Выбор цветов может быть полезен для изменения цвета текста, цвета
фона окна и многих других задач.
if color.isValid():
self.text_field.setTextColor(color)
О QMessageBox
Во многих приложениях в меню часто можно найти пункт "О программе". Щелчок
на этом пункте открывает диалоговое окно, в котором отображается информация
о приложении, такая как логотип программы, название, номер последней версии и
другая юридическая информация.
Класс QMessageBox, который мы рассматривали в Главе 3, также предоставляет
метод about() для создания диалогового окна для отображения заголовка и текста.
Это создает диалоговое окно, показанное на рисунке 5-8. Если вы никогда раньше
не использовали HTML, то <p> и </p> вокруг текста - это HTML-теги, которые
создают абзацы. Мы рассмотрим использование HTML с PyQt в главе 6.
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(400, 500)
self.setWindowTitle("5.1 - Rich Text Notepad GUI")
self.setUpMainWindow()
self.createActions()
self.createMenu()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
self.open_act = QAction(
QIcon("images/open_file.png"), "Открыть")
self.open_act.setShortcut("Ctrl+O")
self.open_act.triggered.connect(self.openFile)
self.save_act = QAction(
QIcon("images/save_file.png"), "Сохранить")
self.save_act.setShortcut("Ctrl+S")
self.save_act.triggered.connect(self.saveToFile)
self.quit_act = QAction(
QIcon("images/exit.png"), "Выход")
self.quit_act.setShortcut("Ctrl+Q")
self.quit_act.triggered.connect(self.close)
self.redo_act = QAction(
QIcon("images/redo.png"), "Повторить")
self.redo_act.setShortcut("Ctrl+Shift+Z")
self.redo_act.triggered.connect(self.text_edit.redo)
self.copy_act = QAction(
QIcon("images/copy.png"), "Копировать")
self.copy_act.setShortcut("Ctrl+C")
self.copy_act.triggered.connect(self.text_edit.copy)
self.paste_act = QAction(
QIcon("images/paste.png"), "Вставить")
self.paste_act.setShortcut("Ctrl+V")
self.paste_act.triggered.connect(self.text_edit.paste)
self.find_act = QAction(
QIcon("images/find.png"), "Найти все")
self.find_act.setShortcut("Ctrl+F")
self.find_act.triggered.connect(self.searchText)
116 ГЛАВА 5 МЕНЮ, ПАНЕЛИ ИНСТРУМЕНТОВ И МНОГОЕ ДРУГОЕ
# Создание действий для меню "Инструменты
self.font_act = QAction(
QIcon("images/font.png"), "Шрифт")
self.font_act.setShortcut("Ctrl+T")
self.font_act.triggered.connect(self.chooseFont)
self.color_act = QAction(
QIcon("images/color.png"), "Цвет")
self.color_act.setShortcut("Ctrl+Shift+C")
self.color_act.triggered.connect(self.chooseFontColor)
self.highlight_act = QAction(
QIcon("images/highlight.png"), "Выделить")
self.highlight_act.setShortcut("Ctrl+Shift+H")
self.highlight_act.triggered.connect(
self.chooseFontBackgroundColor)
В QTextEdit уже есть предопределенные слоты, такие как cut(), copy() и paste(),
которые создают стандартные функции редактирования текста. Для большинства
действий в меню Правка их сигналы подключаются к этим специальным слотам, а
не создают новые, за исключением find_act.
Действия, созданные в меню Инструменты, вызывают слоты, открывающие
диалоги. Эти действия используются для изменения внешнего вида текста. Действие
about_act используется для отображения диалога О программе.
Когда действия созданы, следующим шагом будет создание соответствующих
пунктов меню в Листинге 5-11.
Листинг 5-11. Метод createMenu() для графического интерфейса Rich Text Notepad
# richtext_notepad.py
def createMenu(self):
"""Создаем строку меню приложения."""
self.menuBar().setNativeMenuBar(False)
Рисунок 5-11. Графический интерфейс Блокнота и его меню - Файл (слева), Правка
(посередине) и Инструменты (справа)
Существует ряд слотов, которые вызываются при нажатии на пункт меню.
Некоторые из них открывают диалоговое окно и возвращают какой-либо ввод от
пользователя, например, новый файл, цвет текста или фона, или ключевое слово для
поиска текста.
В Листинге 5-12 начинается конструирование слотов для меню Файл.
def openFile(self):
"""Открыть текстовый или html файл и отобразить его содержимое
в поле редактирования текста."""
file_name, _ = QFileDialog.getOpenFileName(
self, "Открыть файл", "",
"Файлы HTML (*.html);;Текстовые файлы (*.txt)")
if file_name:
with open(file_name, "r") as f:
notepad_text = f.read()
self.text_edit.setText(notepad_text)
if file_name.endswith(".txt"):
notepad_text = self.text_edit.toPlainText()
with open(file_name, "w") as f:
f.write(notepad_text)
elif file_name.endswith(".html"):
notepad_richtext = self.text_edit.toHtml()
with open(file_name, "w") as f:
f.write(notepad_richtext)
else:
QMessageBox.information(
self, "Не сохранено", "Текст не сохранен.",
QMessageBox.StandardButton.Ok)
Пользователь может выбрать одно из двух расширений файла: .html или .txt.
Если выбран .txt, то для преобразования текста в обычный текст используется
функция QTextEdit.toPlainText(). Затем текст записывается в файл. В противном случае
используется метод QTextEdit toHtml() для сохранения форматированного текста в
файл.
Единственный пункт меню Правка, который не вызывает встроенный слот
QTextEdit, - это опция Найти всё, созданная в Листинге 5-14.
Листинг 5-14. Слот searchText() для меню Edit
# richtext_notepad.py
def searchText(self):
"""Поиск текста."""
# Отображает диалог ввода, чтобы спросить пользователя о тексте для поиска
find_text, ok = QInputDialog.getText(
self, "Текст для поиска", "Найти:")
if ok:
extra_selections = []
# Установите курсор в начало
self.text_edit.moveCursor(
QTextCursor.MoveOperation.Start)
color = QColor(Qt.GlobalColor.gray)
def removeHighlights(self):
"""Сбросить дополнительные выделения после редактирования текста."""
self.text_edit.setExtraSelections([])
opt = QFontDialog.FontDialogOption.DontUseNativeDialog
font, ok = QFontDialog.getFont(current, self,
options=opt)
if ok:
self.text_edit.setCurrentFont(font)
def chooseFontBackgroundColor(self):
"""Выберите цвет для фона текста."""
color = QColorDialog.getColor()
if color.isValid():
self.text_edit.setTextBackgroundColor(color)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
ГЛАВА 5 МЕНЮ, ПАНЕЛИ ИНСТРУМЕНТОВ И МНОГОЕ ДРУГОЕ 123
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(450, 350)
self.setWindowTitle("Добавление дополнительных возможностей окна")
self.setUpMainWindow()
self.createDockWidget()
self.createActions()
self.createMenu()
self.createToolBar()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Пункты меню также могут быть созданы таким образом, чтобы они действовали как
переключатели, их можно было включать и выключать. Чтобы создать проверяемый
Класс QToolBar
Когда пользователь выполняет ряд рутинных задач, необходимость многократно
открывать меню для выбора действия может стать утомительной. К счастью,
Класс QDockWidget
Класс QDockWidget используется для создания съемных или плавающих
палитр инструментов или панелей виджетов. Док-виджеты - это вторичные окна,
которые обеспечивают дополнительную функциональность для окон графического
интерфейса.
Чтобы создать объект док-виджета, создайте экземпляр QDockWidget и установите
заголовок виджета с помощью метода setWindowTitle(), как показано в Листинге 5-22.
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setFixedSize(650, 650)
self.setWindowTitle("5.2 - графический интерфейс фоторедактора")
self.setUpMainWindow()
self.createToolsDockWidget()
self.createActions()
self.createMenu()
self.createToolBar()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setAttribute(
Qt.ApplicationAttribute.AA_DontShowIconsInMenus, True)
window = MainWindow()
sys.exit(app.exec())
self.image_label = QLabel()
self.image_label.setAlignment(
Qt.AlignmentFlag.AlignCenter)
self.setCentralWidget(self.image_label)
self.save_act = QAction(
QIcon("images/save_file.png"), "Сохранить")
self.save_act.setShortcut("Ctrl+S")
self.save_act.setStatusTip("Сохранить изображение")
self.save_act.triggered.connect(self.saveImage)
self.print_act = QAction(
QIcon("images/print.png"), "Печать")
self.print_act.setShortcut("Ctrl+P")
self.print_act.setStatusTip("Печать изображения")
self.print_act.triggered.connect(self.printImage)
self.print_act.setEnabled(False)
self.quit_act = QAction(
QIcon("images/exit.png"), "Выход")
self.quit_act.setShortcut("Ctrl+Q")
self.quit_act.setStatusTip("Выход из программы")
self.quit_act.triggered.connect(self.close)
Действия для меню Правка созданы в Листинге 5-26. Эти действия также
появляются на панели инструментов приложения в Листинге 5-27.
self.clear_act = QAction(
QIcon("images/clear.png"), "Очистить изображение")
self.clear_act.setShortcut("Ctrl+D")
self.clear_act.setStatusTip("Очистить текущее изображение")
self.clear_act.triggered.connect(self.clearImage)
if image_file:
self.image = QPixmap(image_file)
self.image_label.setPixmap(
self.image.scaled(self.image_label.size(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
else:
QMessageBox.information(self, "Нет изображения",
"Изображение не выбрано",
QMessageBox.StandardButton.Ok)
self.print_act.setEnabled(True)
def saveImage(self):
"""Отображает QFileDialog для выбора местоположения изображения и
сохранить изображение."""
image_file, _ = QFileDialog.getSaveFileName(
self, "Сохранить изображение", "",
"JPG Файлы (*.jpeg *.jpg );;PNG Файлы (*.png);;\
растровые файлы (*.bmp);;GIF файлы (*.gif)")
140 ГЛАВА 5 МЕНЮ, ПАНЕЛИ ИНСТРУМЕНТОВ И МНОГОЕ ДРУГОЕ
if image_file and self.image.isNull() == False:
self.image.save(image_file)
else:
QMessageBox.information(self, "Не сохранено",
"Изображение не сохранено",
QMessageBox.StandardButton.Ok)
def rotateImage90(self):
"""Поверните изображение на 90° по часовой стрелке."""
if self.image.isNull() == False:
transform90 = QTransform().rotate(90)
pixmap = QPixmap(self.image)
mode = Qt.TransformationMode.SmoothTransformation
rotated = pixmap.transformed(transform90,
mode = mode)
142 ГЛАВА 5 МЕНЮ, ПАНЕЛИ ИНСТРУМЕНТОВ И МНОГОЕ ДРУГОЕ
self.image_label.setPixmap(
rotated.scaled(self.image_label.size(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
self.image = QPixmap(rotated)
self.image_label.repaint() # Перекрашивание метки
def rotateImage180(self):
"""Поверните изображение на 180° по часовой стрелке."""
if self.image.isNull() == False:
transform180 = QTransform().rotate(180)
pixmap = QPixmap(self.image)
mode = Qt.TransformationMode.SmoothTransformation
rotated = pixmap.transformed(transform180,
mode = mode)
self.image_label.setPixmap(
rotated.scaled(self.image_label.size(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
# Для того, чтобы не позволить вращать
# изображение, установите повернутое изображение как self.image
self.image = QPixmap(rotated)
self.image_label.repaint() # Перекрасить метку
self.image_label.setPixmap(
flipped.scaled(self.image_label.size(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
self.image = QPixmap(flipped)
self.image_label.repaint()
def flipImageVertical(self):
"""Зеркально отразите изображение по вертикальной оси."""
if self.image.isNull() == False:
flip_v = QTransform().scale(1, -1)
pixmap = QPixmap(self.image)
flipped = pixmap.transformed(flip_v)
self.image_label.setPixmap(
flipped.scaled(self.image_label.size(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
self.image = QPixmap(flipped)
self.image_label.repaint()
self.image_label.setPixmap(
resized.scaled(self.image_label.size(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
self.image = QPixmap(resized)
self.image_label.repaint()
Последний шаг, о котором нужно позаботиться, это создание метода для печати.
Класс QPrinter
Фоторедактор включает метод для печати изображений. Класс QPrinter
используется для создания страницы для печати документов. Для страницы можно
задать ряд параметров, включая ее ориентацию и размер бумаги.
В данном примере мы хотим использовать QPrinter для печати изображения на
метке image_label. Для этого нам также потребуется использовать класс QPainter,
чтобы указать, что рисовать на странице, которую мы хотим напечатать, то есть на
изображении. Все это рассматривается в Листинге 5-34.
Листинг 5-34. Создание слота для печати изображений в графическом интерфейсе
редактора фотографий
# photo_editor.py
def printImage(self):
"""Печатаем изображение и используем QPrinter для выбора
родной системный формат для диалогового окна принтера."""
printer = QPrinter()
# Конфигурируем принтер
print_dialog = QPrintDialog(printer)
if print_dialog.exec() == QDialog.DialogCode.Accepted:
# Используйте QPainter для вывода PDF файла
painter = QPainter()
painter.begin(printer)
# Создайте объект QRect для хранения текущего видового экрана художника,
ГЛАВА 5 МЕНЮ, ПАНЕЛИ ИНСТРУМЕНТОВ И МНОГОЕ ДРУГОЕ 145
# которым является метка_изображения
rect = QRect(painter.viewport())
Резюме
В этой главе вы смогли убедиться в преимуществах использования класса
QMainWindow для построения главного окна вашего приложения. QMainWindow
предоставляет функциональные возможности и взаимодействия, необходимые
для интеграции панелей инструментов, меню, док-виджетов и строк состояния в
ваши графические интерфейсы. Еще многое предстоит узнать об использовании
меню, например, о создании контекстных меню и отображении виджетов в строке
состояния. По мере изучения этой книги вы продолжите изучать и применять многие
из этих концепций на практических примерах.
Строка меню может состоять из нескольких меню, каждое из которых может
быть разбито на несколько команд. Каждая из этих команд сама по себе также может
Стилизация графических
интерфейсов
В графических интерфейсах, которые вы создавали до сих пор, основное внимание
уделялось функциональности и меньше - внешнему виду и настройке. Создание
интерактивного, последовательного и профессионально выглядящего графического
интерфейса может быть достигнуто не только с помощью виджетов и менеджеров
компоновки, но и путем изменения внешнего вида и поведения каждого объекта в
интерфейсе. Выбор правильного стиля, цветов, шрифтов и тонких форм обратной
связи может помочь создать последовательный, простой в навигации и удобный для
пользователя интерфейс.
В этой главе вы
• Узнаете о стилизации приложений PyQt
• Узнаете, как настроить внешний вид виджетов с помощью Qt Style Sheets и
HTML
• Использовать новые виджеты и классы PyQt, включая QRadioButton,
QGroupBox и QTabWidget
• Используйте контейнеры и виджеты с вкладками для организации и
управления группами виджетов
Давайте начнем с изучения того, что такое стили в PyQt. После этого вы узнаете,
как настраивать внешний вид окон и виджетов приложения.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(300, 100)
self.setWindowTitle("Пример HTML")
no_style_label = QLabel(
"""Не бойтесь совершенства.
- вы никогда его не достигнете.
- Сальвадор Дали""")
style_label = QLabel("""
<p><font color='#DB8D31' face='Times' size='+2'>
Не бойтесь совершенства -
вы никогда его не достигнете.</font></p>
<p align='right'>
<b> - <i>Сальвадор Дали</i></b></p>""")
v_box = QVBoxLayout()
v_box.addWidget(no_style_label)
v_box.addWidget(style_label)
self.setLayout(v_box)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Тег Описание
p Определяет абзац. Для изменения тега можно использовать такие
атрибуты, как выравнивание
font Используется для задания внешнего вида шрифта с помощью атрибутов
color, face и size.
b Определяет полужирный текст
i Определяет курсивный текст
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(200, 200)
self.setWindowTitle("Пример таблиц стилей")
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Код в Листинге 6-4 размещен перед вызовом метода show() в Листинге 6-3.
Созданный ярлык использует комбинацию HTML и CSS для изменения своего
внешнего вида.
style_sheet = """
QPushButton#Warning_Button{
background-color: #C92108;
border-radius: 5px;
padding: 6px;
color: #FFFFFF
}
QPushButton#Warning_Button:pressed{
background-color: #F4B519;
}
"""
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(230, 140)
self.setWindowTitle("Пример таблиц стилей 2")
v_box = QVBoxLayout()
v_box.addWidget(label)
v_box.addWidget(normal_button)
v_box.addWidget(warning_button)
self.setLayout(v_box)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(style_sheet) # Установить стиль приложения
window = MainWindow()
sys.exit(app.exec())
Класс QGroupBox
Контейнер QGroupBox представляет собой прямоугольную рамку, используемую
для группировки виджетов вместе. Групповой блок имеет рамку с заголовком в
верхней части. Заголовок также может быть проверяемым, так что дочерние виджеты
внутри группового блока могут быть включены или выключены при установке или
снятии флажка.
Объект группового блока может содержать любой виджет. Поскольку QGroupBox
автоматически не упорядочивает дочерние виджеты, вам также потребуется
применить менеджер компоновки.
Следующий блок кода является кратким примером использования QGroupBox:
Класс QTabWidget
Иногда вам может понадобиться организовать связанную информацию на
отдельных страницах, чтобы не создавать беспорядочный графический интерфейс.
Класс QTabWidget предоставляет панель вкладок с областью под каждой вкладкой
(называемой страницей) для представления информации и виджетов, связанных с
каждой вкладкой. Одновременно отображается только одна страница, и пользователь
может просмотреть другую страницу, щелкнув на вкладке или используя ярлык (если
он установлен для вкладки).
Существует несколько различных способов взаимодействия и отслеживания
различных вкладок. Например, если пользователь переключается на другую вкладку,
индекс текущей вкладки может быть возвращен при подаче сигнала currentChanged. Вы
также можете вернуть индекс текущей страницы с помощью функции currentIndex()
или виджет текущей страницы с помощью функции currentWidget(). Вкладка также
может быть включена или отключена с помощью метода setTabEnabled().
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(400, 300)
self.setWindowTitle("Пример контейнеров")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
gender_h_box = QHBoxLayout()
gender_h_box.addWidget(male_rb)
gender_h_box.addWidget(female_rb)
Имея базовое понимание таблиц стилей и несколько новых классов PyQt, пришло
время применить полученные знания для создания нового проекта GUI.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(700, 700)
self.setWindowTitle("6.1 - GUI заказа еды")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(style_sheet)
window = MainWindow()
sys.exit(app.exec())
self.tab_bar.addTab(self.pizza_tab, "Пицца")
self.tab_bar.addTab(self.wings_tab, "Крылышки")
items_box = QWidget()
items_box.setObjectName("Side")")
pizza_label = QLabel("Тип пиццы: ")
self.display_pizza_label = QLabel("")
toppings_label = QLabel("Начинки: ")
self.display_toppings_label = QLabel("")
extra_label = QLabel("Дополнительно: ")
self.display_wings_label = QLabel("")
description_box.setLayout(pizza_h_box)
crust_gbox.setLayout(gb_v_box)
toppings_gbox.setLayout(gb_v_box)
self.pizza_tab.setLayout(page1_v_box)
wings_gbox.setLayout(gb_v_box)
self.wings_tab.setLayout(page2_v_box)
Объект Свойства
background-color Устанавливает цвет фона для виджета
border Сокращение для установки цвета, стиля и ширины границы QLabel
{border: 2px groove grey}
border-color Задает цвет границы для всех сторон виджета
border-style Определяет узор для рисования границы виджета. Некоторые из
них: пунктир (dashed), точка (dotted), желобок (groove), вставка
(inset), край (outset) и сплошная (solid).
border-width Задает ширину границы для всех сторон виджета (в пикселях).
border-radius Устанавливает радиус углов виджета (в пикселях).
color Задает цвет, используемый для текста
font Задает вес, стиль, размер и семейство шрифта QLabel
{font: bold italic small 'Times'}
image Задает изображение, используемое в виджете.
Обязательно укажите url(path_to_file)
margin Задает дополнительное пространство вокруг виджета (в пикселях)
padding Задает дополнительное пространство внутри виджета (в пикселях)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setGeometry(100, 100, 300, 200)
self.setWindowTitle("Пример обработки событий")
info_label = QLabel(
"""<p align='center'>Нажмите <b>ESC</b> клавишу
чтобы закрыть окно.</p>""")
self.setCentralWidget(info_label)
self.show()
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setMinimumSize(400, 300)
self.setWindowTitle("Пример обработки событий")
self.setUpMainWindow()
self.show()
def setUpMainWindow(self):
self.image_label = QLabel()
self.image_label.setPixmap(QPixmap("images/back.png"))
self.image_label.setAlignment(
Qt.AlignmentFlag.AlignCenter)
self.info_label = QLabel("")
self.info_label.setAlignment(
Qt.AlignmentFlag.AlignCenter)
self.pos_label = QLabel("")
self.pos_label.setAlignment(
Qt.AlignmentFlag.AlignCenter)
main_h_box = QVBoxLayout()
main_h_box.addStretch()
main_h_box.addWidget(self.image_label)
main_h_box.addStretch()
main_h_box.addWidget(self.info_label)
main_h_box.addWidget(self.pos_label)
self.setLayout(main_h_box)
Рисунок 7-4. Цвет метки будет меняться при нажатии стрелок вверх и вниз
class SendSignal(QObject):
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройте графический интерфейс приложения."""
self.setGeometry(100, 100, 300, 200)
self.setWindowTitle("Создание пользовательских сигналов")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
def changeBackground(self):
"""Изменение фона виджета этикетки, когда
подается сигнал KeyPressEvent."""
if self.direction == "up" and \
self.index < len(self.colors_list) - 1:
self.index = self.index + 1
self.label.setStyleSheet(f"""background-color:
{self.colors_list[self.index]}""")
elif self.direction == "down" and self.index > 0:
self.index = self.index - 1
self.label.setStyleSheet(f"""background-color:
{self.colors_list[self.index]}""")
Резюме
Обработка событий является важным компонентом разработки графического
интерфейса пользователя. В PyQt это можно сделать либо с помощью сигналов и
слотов, либо с помощью классов событий и соответствующих им обработчиков
событий. В любом случае, вы часто можете столкнуться с расширением возможностей
класса виджета путем создания пользовательских сигналов с помощью pyqtSignal или
повторной реализации базовой функциональности, предоставляемой различными
методами обработчиков событий Qt.
В этой главе мы рассмотрели обе эти концепции, изменив поведение обработчиков
событий нажатия клавиш и мыши и создав пользовательский сигнал для изменения
внешнего вида метки.
В следующей главе мы рассмотрим использование приложения Qt Designer для
создания приложений PyQt и упрощение процесса расположения виджетов в окне
графического интерфейса.
196 ГЛАВА 7 ОБРАБОТКА СОБЫТИЙ В PYQT
ГЛАВА 8
Создание графических
интерфейсов с
помощью Qt Designer
Хотя создание графических пользовательских интерфейсов программным
способом дает вам больше контроля над процессом проектирования, некоторые
задачи разработки могут потребовать более быстрого подхода. К счастью, Qt
предоставляет отличный интерфейс для расположения виджетов и проектирования
основных окон, виджетов или диалогов. Графический инструмент разработки,
Qt Designer, наполнен виджетами и другими инструментами для создания
графических интерфейсов. С помощью интерфейса перетаскивания приложения вы
можете создавать и настраивать свои собственные приложения Qt или PyQt.
Виджеты и другие приложения, которые вы создаете с помощью Qt Designer,
могут взаимодействовать с другими программами Qt с помощью сигналов и слотов,
что упрощает назначение поведения виджетов. Это означает, что больше ресурсов
можно направить на кодирование функциональности и меньше на компоновку и
дизайн.
В этой главе вы
• Установите приложение Qt Designer
• Рассмотрите различные компоненты, составляющие интерфейс Qt Designer
• Проследите за созданием приложения в Qt Designer, попутно изучая, как
применять макеты, редактировать свойства объектов, подключать сигналы и
слоты и генерировать код Python
• Узнайте о новых классах PyQt, таких как класс QFrame для группировки
виджетов.
Установка Qt Designer
На данный момент существует два подхода к установке последней версии
Qt Designer, и они могут различаться в зависимости от того, сколько памяти вы
готовы использовать.
Первый вариант - загрузить последнюю версию Qt Creator для Qt 6 с сайта
www.qt.io/download. Qt Designer поставляется в комплекте с Qt Creator, который
является официальной C++ IDE от Qt. Имейте в виду, что этот метод работает для
macOS, Windows и Linux, но также означает, что вам придется установить всю IDE
Qt Creator.
На веб-странице загрузки Qt вам нужно найти опцию загрузки Qt для создателей
с открытым исходным кодом. Оттуда прокрутите страницу в самый низ и найдите
кнопку с надписью Download the Qt Online Installer. Когда загрузка завершится,
вам нужно будет открыть программу установки Qt. Вам нужно будет создать
учетную запись Qt, а затем следовать подсказкам для установки Qt Creator. Следует
отметить, что если вы выберете выборочную установку, вы сможете вручную
выбрать необходимое программное обеспечение и сэкономить немного памяти.
После завершения установки выполните поиск на своем компьютере, чтобы найти
Qt Designer.
Другой способ установки Qt Designer - через PySide6. Сначала откройте окно
оболочки и введите следующую команду для установки PySide6:
$ pip3 install PySide6
В Windows используйте pip вместо pip3.
Затем выполните поиск Qt Designer на вашем компьютере и откройте приложение.
После открытия Qt Designer вы увидите графический пользовательский интерфейс
для создания собственных графических интерфейсов, как показано на рисунке 8-1.
Рисунок 8-8. Браузер ресурсов для работы с такими ресурсами, как изображения
и значки
Если вам нужно добавить ресурсы, сначала необходимо создать новый файл
ресурсов. Для этого нажмите на карандаш в левом верхнем углу док-виджета Браузер
ресурсов. Откроется диалог редактирования ресурсов, подобный показанному на
Рисунке 8-9.
grid = QGridLayout()
grid.addWidget(button, 0, 0)
Рисунок 8-13. Открытие контекстного меню для выбора макетов для контейнеров
и окон
Макет верхнего уровня формы можно установить, щелкнув правой кнопкой мыши
на самой форме в главном окне и найдя макет, который вы хотите использовать. Для
GUI клавиатуры щелкните правой кнопкой мыши и выберите Lay Out Vertically.
Ваш графический интерфейс должен выглядеть, как показано на Рисунке 8-14. Если
виджеты выровнены неправильно, вы также можете открыть контекстное меню,
выбрать Break Layout и переставить виджеты. Опция Simplify Grid Layout также
может помочь вам расположить элементы в макете сетки.
class Ui_Keypad(object):
def setupUi(self, Keypad):
Keypad.setObjectName("Keypad")
Keypad.resize(302, 406)
Видно, что хотя мы изменили только цвет палитры, это изменение обрабатывается
для всех трех состояний автоматически. Класс QBrush используется для применения
цветов и узоров к виджетам. Метод setPalette() применяет палитру к классу Keypad.
Вертикальный макет для класса Keypad показан в Листинге 8-3.
Листинг 8-3. Создание менеджера компоновки главного окна
# keypad_gui.py
self.verticalLayout = QtWidgets.QVBoxLayout(Keypad)
self.verticalLayout.setObjectName("verticalLayout")
В этом большом блоке кода много повторений. Это потому, что во всех четырех
строках внесены одни и те же изменения. Рассмотрев один экземпляр, вы сможете
понять остальные три.
Каждый из четырех виджетов редактирования строки имеет политики размеров,
которые позволяют им растягиваться, если окно изменяет размеры по вертикали
и горизонтали, используя QSizePolicy.Policy.Expanding. Изменения, внесенные в
размер шрифта и выравнивание, также отображаются. Затем виджеты QLineEdit
располагаются в horizontalLayout контейнера фрейма. Наконец, объект рамки
добавляется в verticalLayout главного окна.
В Листинге 8-7 показан нижний контейнер фрейма и устанавливается его политика
размера и атрибуты стиля.
ГЛАВА 8 СОЗДАНИЕ ГРАФИЧЕСКИХ ИНТЕРФЕЙСОВ С ПОМОЩЬЮ QT DESIGNER 219
Листинг 8-7. Создание рамки для виджетов QPushButton в графическом
интерфейсе клавиатуры
# keypad_gui.py
self.frame_2 = QtWidgets.QFrame(Keypad)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(2)
sizePolicy.setHeightForWidth(
self.frame_2.sizePolicy().hasHeightForWidth())
self.frame_2.setSizePolicy(sizePolicy)
self.frame_2.setFrameShape(QtWidgets.QFrame.Shape.Box)
self.frame_2.setFrameShadow(
QtWidgets.QFrame.Shadow.Sunken)
self.frame_2.setLineWidth(2)
self.frame_2.setObjectName("frame_2")
self.gridLayout = QtWidgets.QGridLayout(self.frame_2)
self.gridLayout.setObjectName("gridLayout")
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.ui = Ui_Keypad()
self.ui.setupUi(self)
self.initializeUI()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
Keypad = MainWindow()
sys.exit(app.exec())
Затем мы соединяем сигналы и слоты для виджетов кнопок в Листинге 8-13. Когда
каждая кнопка нажимается, она посылает сигнал, который подключается к слоту
numberClicked(). Вместо того чтобы создавать отдельный метод для каждой кнопки,
функция lambda используется для повторного использования метода для сигналов.
lambda вызывает функцию numberClicked() и каждый раз передает ей новый параметр,
в данном случае конкретный текст каждой кнопки.
Листинг 8-13. Настройка сигналов для кнопок в графическом интерфейсе
клавиатуры
# keypad_main.py
# 4-значный код доступа
self.passcode = 8618
self.ui.button_hash.clicked.connect(
self.checkPasscode)
Рисунок 8-17. Создание меню и пунктов меню. Введите здесь заполнитель (слева
вверху). Дважды щелкните заполнитель и введите название меню (вверху справа).
Добавьте новый пункт меню (слева внизу). Новый пункт меню (справа внизу)
230 ГЛАВА 8 СОЗДАНИЕ ГРАФИЧЕСКИХ ИНТЕРФЕЙСОВ С ПОМОЩЬЮ QT DESIGNER
Отсюда вы можете добавить дополнительные меню, подменю или действия. Чтобы
добавить подменю, сначала создайте пункт меню. Затем нажмите на символ плюса
рядом с новым пунктом меню. Это добавит новое меню, которое ответвляется от
существующего пункта меню. Дважды щелкните на заполнителе Type Here и введите
текст для нового пункта. Обратитесь за помощью к Рисунку 8-18.
Резюме
Qt Designer, безусловно, является полезным инструментом для создания GUI-
приложений. Он предоставляет интерфейс перетаскивания, который облегчает
организацию виджетов; изменение параметров объектов; создание меню, панелей
инструментов и док-виджетов; добавление действий в меню; генерацию кода,
который можно использовать в Python, и многое другое. Qt Designer может сделать
процесс проектирования намного быстрее и проще.
Хотя в этой главе были рассмотрены некоторые основы использования
Qt Designer, существуют и другие возможности, например, создание собственных
пользовательских виджетов или построение диалоговых окон.
В следующих главах мы начнем рассматривать более специфические классы и темы
PyQt, которые можно использовать для дальнейшего улучшения пользовательских
интерфейсов. В следующей главе вы узнаете, как использовать класс QClipboard для
копирования и вставки данных между различными приложениями.
Работа с буфером
обмена
Одним из основных преимуществ графических интерфейсов является
возможность создавать программы, которые могут взаимодействовать с вашей
системой и другими приложениями. Эта концепция выходит за рамки открытия и
сохранения файлов или печати изображений.
Буфер обмена (clipboard) - это место в памяти компьютера, которое используется
для временного хранения данных, скопированных или вырезанных из приложения.
В буфере обмена могут храниться различные типы данных, включая текст,
изображения и GIF-файлы. Информация, хранящаяся в буфере обмена, может быть
вставлена в другие приложения, если только приложение знает, как работать с типом
данных, хранящихся в буфере обмена.
В этой главе вы узнаете
• Использовать классы QClipboard и QMimeData для перемещения данных между
приложениями
• Узнаете, как запустить несколько экземпляров приложения одновременно.
Чтобы начать, давайте узнаем о классе PyQt для взаимодействия с буфером обмена
вашего компьютера.
Класс QClipboard
Класс QClipboard делает доступным буфер обмена вашей системы, чтобы вы
могли копировать и вставлять данные, такие как текст, изображения и насыщенный
текст между приложениями. Виджеты Qt, которые можно использовать для работы с
текстовой информацией, такие как QLineEdit и QTextEdit, поддерживают использование
буфера обмена. Классы Model/View в Qt (о которых вы узнаете больше в Главе 10)
также поддерживают буфер обмена. Если вы хотите вставить изображение из буфера
обмена в приложение, обязательно используйте виджеты, поддерживающие графику,
такие как QLabel.
Включение буфера обмена в ваш проект довольно просто в PyQt. Чтобы получить
доступ к QClipboard приложения, сначала создайте экземпляр буфера обмена с
помощью следующей строки:
self.clipboard = QApplication.clipboard()
234 ГЛАВА 9 РАБОТА С БУФЕРОМ ОБМЕНА
Следующий блок кода показывает один из способов получения изображения,
которое было скопировано в буфер обмена, и применения его к метке:
Этот процесс работает только для изображений, поэтому если вы хотите вставить
текст или насыщенный текст, вам нужно будет использовать setText() для QLabel.
Другой способ получения данных - использовать класс QMimeData и описать, какие
именно данные перемещаются. Эта тема будет рассмотрена в разделе "Пояснения к
использованию QClipboard".
События, происходящие между вашей системой и приложением, созданным
с помощью PyQt, обрабатываются QApplication. Экземпляр буфера обмена дает
вам возможность отправлять или получать данные в вашем приложении. Однако
буфер обмена может одновременно хранить только один объект. Поэтому, если вы
скопируете изображение в буфер обмена, а затем скопируете текст, будет доступен
только текст, а изображение будет удалено.
В этом разделе вы создадите простой графический интерфейс, как на Рисунке 9-1,
который показывает, как получать текст из других приложений и затем вставлять
его в окно PyQt.
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(500, 300)
self.setWindowTitle("Пример буфера обмена")
self.setUpMainWindow()
self.createClipboardDock()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
dock_v_box = QVBoxLayout()
dock_v_box.addWidget(self.clipboard_tedit)
dock_v_box.addWidget(paste_button)
def pasteText(self):
"""Вставка текста из буфера обмена при нажатии на кнопку."""
self.central_tedit.paste()
self.central_tedit.repaint()
class StickyNote(QMainWindow):
# Переменные класса, общие для всех экземпляров
note_id = 1
notes = []
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(250, 250)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = StickyNote()
sys.exit(app.exec())
Меню Цвет позволяет пользователю выбрать цвет фона для каждой заметки.
Если пользователь хочет вставить текст из буфера обмена в виджет, он может
воспользоваться либо пунктом меню Вставить, либо горячей клавишей Ctrl+V.
Метод createClipboard() создает объект буфера обмена, а слот copyToClipboard()
в Листинге 9-9 срабатывает при изменении данных в буфере обмена по сигналу
dataChanged.
Листинг 9-9. Код для настройки буфера обмена в графическом интерфейсе Sticky
Notes
# sticky_notes.py
def createClipboard(self):
"""Устанавливаем буфер обмена."""
self.clipboard = QApplication.clipboard()
self.clipboard.dataChanged.connect(
self.copyToClipboard)
self.mime_data = self.clipboard.mimeData()
def copyToClipboard(self):
"""Получить содержимое системного буфера обмена."""
self.mime_data = self.clipboard.mimeData()
def newNote(self):
"""Создаем новый экземпляр класса StickyNote."""
StickyNote().show()
self.note_id += 1
def pasteToClipboard(self):
"""Получает содержимое системного буфера обмена и
вставить в заметку."""
if self.mime_data.hasText():
self.central_tedit.paste()
Резюме
Класс QClipboard позволяет GUI-приложениям получать и отправлять данные из
системного буфера обмена. Класс QMimeData обрабатывает различные типы данных
как для буфера обмена, так и для систем перетаскивания, обеспечивая корректную
работу с данными.
Многие виджеты PyQt, предназначенные для редактирования текста, уже
содержат возможность взаимодействия с буфером обмена, поэтому часто включать
код для работы с буфером обмена в свою программу не потребуется.
В следующей главе вы познакомитесь с классами Qt для работы с данными. Вы
также узнаете, как применять в графических интерфейсах функцию drag-and-drop
(перетаскивание), чтобы можно было передавать данные между виджетами и
другими программами.
Представление данных
в PyQt
Как разработчик графических интерфейсов, вы, вероятно, в какой-то момент
обнаружите, что ищете способ представления данных, текстовых или визуальных,
в своем интерфейсе. Прежде чем приступать к этой работе, необходимо помнить
о пользователе и цели приложения. Во многих случаях методы представления
графических данных уже соответствуют различным стандартам. Многие
пользователи уже имеют представление о том, как должны отображаться данные в
таблицах или как добавлять и удалять элементы в списке. К счастью, Qt уже учла это
и создала несколько классов, которые позволяют быстрее и проще решать задачу
представления данных.
В этой главе вы
• Начнете думать о том, как работать с данными в PyQt
• Создадите графический интерфейс для каждого из классов удобства на
основе элементов. - QListWidget, QTableWidget и QTreeWidget.
• Добавите в графический интерфейс функций перетаскивания
• Создадите контекстные меню для отображения ярлыков.
Начнем с того, что выясним, на что способны удобные классы.
Класс QListWidget
Класс QListWidget создает виджет, отображающий один столбец элементов, что
упрощает добавление и удаление элементов. Элементы могут быть добавлены либо
при создании виджета в коде, либо вставлены позже через графический интерфейс.
Элементы во всех трех удобных классах создаются с помощью специальных
классов. Класс QListWidgetItem используется совместно с QListWidget в качестве
элемента, который может быть использован со списком. На рис. 10-1 показан
графический интерфейс пользователя, который мы собираемся создать для этого
примера.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(400, 200)
self.setWindowTitle("Пример QListWidget")
self.setUpMainWindow()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
# Создание макетов
right_v_box = QVBoxLayout()
right_v_box.addWidget(add_button)
right_v_box.addWidget(insert_button)
right_v_box.addWidget(remove_button)
right_v_box.addWidget(clear_button)
main_h_box = QHBoxLayout()
main_h_box.addWidget(self.list_widget)
main_h_box.addLayout(right_v_box)
self.setLayout(main_h_box)
def removeOneItem(self):
"""Удаление одного элемента из виджета списка."""
row = self.list_widget.currentRow()
item = self.list_widget.takeItem(row)
del item
Перетаскивание в PyQt
Механизм drag-and-drop (перетаскивание) позволяет пользователю выполнять
задачи в графическом интерфейсе, выбирая элементы, например, пиктограммы
или изображения, и перемещая их в другое окно или на другой объект. PyQt также
позволяет легко включить это поведение в приложение. Для того чтобы виджеты
обладали базовой функциональностью перетаскивания, достаточно установить
значения методов setAcceptDrops() и setDragEnabled() в True.
В PyQt включив функцию перетаскивания, можно перемещать элементы из одного
объекта текстового редактора, списка или таблицы в другой. QMimeData также
может быть использован для управления тем, какие данные можно перемещать,
перетаскивать или опускать.
На рис. 10-3 показан графический интерфейс, который вы будете создавать в этом
разделе. Элементы в окне можно перетаскивать туда-сюда между двумя экземплярами
QListWidget. Обязательно загрузите папку images из репозитория GitHub для этого
проекта.
Пояснения к перетаскиванию
Начнем со сценария basic_window.py и модифицируем класс MainWindow в
Листинге 10-5. В этом примере мы продолжим использовать классы QListWidget и
QListWidgetItem для демонстрации перетаскивания.
Листинг 10-5. Создание класса MainWindow для примера с перетаскиванием
# drag_drop.py
# Импорт необходимых модулей
import sys, os
from PyQt6.QtWidgets import (QApplication, QWidget, QLabel,
QListWidget, QListWidgetItem, QGridLayout)
from PyQt6.QtCore import QSize
from PyQt6.QtGui import QIcon
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(500, 300)
self.setWindowTitle("Пример перетаскивания")
self.setUpMainWindow()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Класс QTableWidget
Класс QTableWidget предоставляет средства для отображения и организации
данных в табличной форме, представляя информацию в виде строк и столбцов.
Использование таблиц позволяет упорядочить данные в более удобном для чтения
формате. Пример таблиц PyQt показан на рис. 10-4.
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(1000, 500)
self.setWindowTitle(
"Электронная таблица - пример QTableWidget")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Контекстное меню обычно создается с помощью QMenu. При этом можно либо
использовать существующие действия, созданные в строке меню или на панели
инструментов, либо создать новые. Пример контекстного меню данного приложения
показан на рис. 10-5.
def addRowBelow(self):
current_row = self.table_widget.currentRow()
self.table_widget.insertRow(current_row + 1)
def addColumnBefore(self):
current_col = self.table_widget.currentColumn()
self.table_widget.insertColumn(current_col)
def addColumnAfter(self):
current_col = self.table_widget.currentColumn()
self.table_widget.insertColumn(current_col + 1)
ГЛАВА 10 ПРЕДСТАВЛЕНИЕ ДАННЫХ В PYQT 261
def deleteRow(self):
current_row = self.table_widget.currentRow()
self.table_widget.removeRow(current_row)
def deleteColumn(self):
current_col = self.table_widget.currentColumn()
self.table_widget.removeColumn(current_col)
def clearTable(self):
self.table_widget.clear()
Класс QTreeWidget
Класс QTreeWidget имеет общие черты с QListWidget и QTableWidget. С одной
стороны, элементы данных могут отображаться в виде списка, аналогичного
QListWidget. С другой стороны, QTreeWidget также может отображать несколько
столбцов данных, но не в табличном формате.
Отличительной особенностью QTreeWidget является то, что класс может визуально
представлять взаимосвязи между данными в виде древовидной структуры. При этом
элемент дерева может быть родителем других элементов.
Графический интерфейс, который мы создадим в этом разделе, показан на рис. 10-6.
class MainWindow(QWidget):
def __init__(self):
""" Конструктор для класса "Пустое окно" """
super().__init__()
self.initializeUI()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Графика и анимация в
PyQt
Разрабатывая графические интерфейсы, вы обнаружите, что можете проявить
свою творческую и художественную сторону, рисуя и анимируя виджеты. Поскольку
графика является очень важной частью графических приложений, в этой главе
мы рассмотрим только темы, связанные с двумерной графикой, включая линии,
формы, анимацию и рисование. Если вы заинтересованы в создании графических
интерфейсов, работающих с 3D-изображениями, то в Qt также имеется поддержка
различных графических API, включая OpenGL и Vulkan. Если вас действительно
интересует графика в Qt6, то посмотрите на https://doc.qt.io/qt-6/topics-graphics.html.
В этой главе вы
• Познакомитесь с QPainter и другими классами, используемыми для рисования
в Qt
• Создадите подсказки с помощью QToolTip
• Воспользуетесь QPropertyAnimation для анимации виджетов
• Анимируете объекты с помощью QPropertyAnimation и pyqtProperty
• Узнаете, как использовать QGraphicsView для построения графической сцены
Давайте ныачнем с одного из самых важных классов в PyQt.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настраиваем графический интерфейс приложения."""
self.setFixedSize(600, 600)
self.setWindowTitle("Основы QPainter")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
self.drawPoints(painter)
self.drawDiffLines(painter)
self.drawText(painter)
brush = QBrush(Qt.darkMagenta,
Qt.BrushStyle.Dense5Pattern)
painter.setBrush(brush)
pen.setWidth(3)
painter.setPen(pen)
painter.drawPoint(10, 15)
painter.setPen(pen)
painter.setBrush(brush)
painter.drawRect(120, 220, 80, 80)
red_pen = QPen(QColor(self.red), 5)
green_brush = QBrush(QColor(self.green))
painter.setPen(red_pen)
painter.setBrush(green_brush)
painter.drawRect(20, 320, 80, 80)
painter.setPen(pen)
painter.setBrush(brush)
painter.drawPolygon(points)
brush.setStyle(Qt.BrushStyle.Dense5Pattern)
painter.setBrush(brush)
painter.drawRoundedRect(rect_2, 5, 20)
brush.setStyle(Qt.BrushStyle.BDiagPattern)
painter.setBrush(brush)
painter.drawRoundedRect(rect_3, 15, 15)
tail_path = QPainterPath()
tail_path.moveTo(30, 420)
tail_path.cubicTo(30, 420, 65, 500, 30, 560)
tail_path.lineTo(163, 540)
tail_path.cubicTo(125, 360, 110, 440, 30, 420)
tail_path.closeSubpath()
painter.setPen(pen)
painter.setBrush(brush)
painter.drawPath(tail_path)
painter.setPen(pen)
painter.setBrush(brush)
painter.drawEllipse(QPoint(int(center_x),
int(center_y)), radius_x, radius_y)
Рисование градиентов
Градиенты можно использовать вместе с QBrush для заливки внутренней части
фигур. В PyQt существует три различных типа стилей градиента: линейный,
радиальный и конический. В данном примере мы будем использовать класс
QLinearGradient для интерполяции цветов между двумя начальными и конечными
точками. Результат можно увидеть на рис. 11-5.
gradient.setColorAt(0.0, Qt.GlobalColor.blue)
gradient.setColorAt(0.5, Qt.GlobalColor.yellow)
gradient.setColorAt(1.0, Qt.GlobalColor.cyan)
painter.setPen(pen)
painter.setBrush(QBrush(gradient))
painter.drawRect(420, 420, 160, 160)
# Инициализация переменных
self.antialiasing_status = False
self.eraser_selected = False
self.last_mouse_pos = QPoint()
self.drawing = False
self.pen_color = Qt.GlobalColor.black
self.pen_width = 2
В Листинге 11-14, если пользователь нажимает левую кнопку мыши, когда курсор
находится в окне, мы устанавливаем значение drawing равным True и сохраняем
текущее значение мыши в last_mouse_pos. Нам также потребуется проверить, когда
мышь была отпущена, чтобы остановить рисование, и обновить значения для draw
и eraser_selected.
Листинг 11-14. Реализация функций mousePressEvent() и mouseReleaseEvent() в
классе Canvas
# painter.py
def mousePressEvent(self, event):
"""Обработка нажатия кнопки мыши."""
if event.button() == Qt.MouseButton.LeftButton:
self.last_mouse_pos = event.pos()
self.drawing = True
self.mouse_track_label.setVisible(True)
sb_text = f"""<p>Координаты мыши: ({mouse_pos.x()},
{mouse_pos.y()})</p>"""
self.mouse_track_label.setText(sb_text)
self.parent.status_bar.addWidget(
self.mouse_track_label)
def saveFile(self):
"""Сохраняет файл изображения .png текущей области пиксмапа."""
file_format = "png"
default_name = os.path.curdir + "/untitled." + \
file_format
file_name, _ = QFileDialog.getSaveFileName(
self, "Сохранить как",
default_name, "PNG Формат (*.png)")
if file_name:
self.pixmap.save(file_name, file_format)
target_rect = QRect()
target_rect = event.rect()
painter.drawPixmap(target_rect,
self.pixmap, target_rect)
painter.end()
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(900, 600)
self.setWindowTitle("11.1 – Painter GUI")
# Установите стиль шрифта, используемый всеми подсказками
QToolTip.setFont(QFont("Helvetica", 12))
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setAttribute(
Qt.ApplicationAttribute.AA_DontShowIconsInMenus, True)
window = MainWindow()
sys.exit(app.exec())
def createMenu(self):
"""Создаем строку меню приложения."""
self.menuBar().setNativeMenuBar(False)
marker_act = QAction(QIcon("icons/marker.png"),
"Маркер", tool_bar)
marker_act.setToolTip("Это <b>Маркер</b>.")
marker_act.triggered.connect(
lambda: self.canvas.selectDrawingTool("marker"))
eraser_act = QAction(QIcon("icons/eraser.png"),
"Ластик", tool_bar)
eraser_act.setToolTip(
"Используйте <b>Ластик</b>, чтобы все исчезло.")
eraser_act.triggered.connect(
lambda: self.canvas.selectDrawingTool("eraser"))
color_act = QAction(QIcon("icons/colors.png"),
"Цвета", tool_bar)
color_act.setToolTip(
"Выберите <b>Цвет</b> в диалоговом окне Цвет.")
color_act.triggered.connect(
lambda: self.canvas.selectDrawingTool("color"))
tool_bar.addAction(pencil_act)
tool_bar.addAction(marker_act)
tool_bar.addAction(eraser_act)
tool_bar.addAction(color_act)
item_pixmap = QPixmap(image_path)
resize_item = item_pixmap.scaledToWidth(150)
self.item = QGraphicsPixmapItem(resize_item)
292 ГЛАВА 11 ГРАФИКА И АНИМАЦИЯ В PYQT
def _set_position(self, position):
self.item.setPos(position)
def __init__(self):
super().__init__()
self.initializeView()
def initializeView(self):
"""Инициализация графического представления и отображение
его содержимое на экран."""
self.setMaximumSize(700, 450)
self.setWindowTitle("Пример анимации")
self.createObjects()
self.createScene()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = AnimationScene()
sys.exit(app.exec())
self.car_anim.setStartValue(QPointF(-50, 350))
self.car_anim.setKeyValueAt(0.3, QPointF(150, 350))
self.car_anim.setKeyValueAt(0.6, QPointF(170, 350))
self.car_anim.setEndValue(QPointF(750, 350))
self.tree_anim.setStartValue(QPointF(750, 150))
self.tree_anim.setKeyValueAt(0.3, QPointF(170, 150))
self.tree_anim.setKeyValueAt(0.6, QPointF(150, 150))
self.tree_anim.setEndValue(QPointF(-150, 150))
background = QPixmap("images/highway.jpg")
bg_rectf = QRectF(background.rect())
painter.drawPixmap(scene_rect, background, bg_rectf)
Наконец, сцене можно придать фон с помощью QBrush. Если вы хотите использовать
фоновое изображение, то вам необходимо реализовать метод drawBackground()
класса QGraphicView, как показано в Листинге 11-27.
class AnimatedCheckbox(QCheckBox):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setFixedSize(240, 120)
self.setWindowTitle("Анимированные виджеты")
self.setUpMainWindow()
self.show()
def setUpMainWindow(self):
"""Создание и расположение виджетов в главном окне."""
self.update_cb = AnimatedCheckbox("Нормальный")
self.update_cb.stateChanged.connect(self.stopFlashing)
self.status_button = QPushButton("Статус изменен")
self.status_button.clicked.connect(
self.startAnimations)
main_v_box = QVBoxLayout()
main_v_box.addWidget(self.update_cb)
main_v_box.addWidget(self.status_button)
self.setLayout(main_v_box)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
298 ГЛАВА 11 ГРАФИКА И АНИМАЦИЯ В PYQT
Объект update_cb вначале отображает Normal. Его сигнал stateChanged подключен
к слоту stopFlashing(), код которого мы приведем в Листинге 11-31.
Для status_button щелчок на кнопке запускает различные анимации. В
setUpMainWindow() инстанцируются два объекта анимации свойств и одна группа
для управления анимациями.
Объект cb_anim анимирует цвет update_cb, а button_anim - свойство geometry для
кнопки status_button. С помощью геометрии мы получим координаты x и y, а также
размер кнопки. Наконец, seq_group будет управлять анимациями и проигрывать их
по порядку.
Геометрия виджета может быть получена с помощью метода geometry().
Переменная start_geometry в Листинге 11-30 содержит начальные значения QPoint и
QSize кнопки status_button. (Геттер geometry() фактически возвращает объект QRect,
а прямоугольник можно разбить на значения положения и размера верхнего левого
угла).
Листинг 11-30. Код для слота startAnimations() в примере с анимирующими
виджетами
# animate_widgets.py
def startAnimations(self):
"""Воспроизведение анимации и обновление состояний виджетов."""
# Соберите начальные значения геометрии кнопки.
# start_geometry - объект QRect
start_geometry = self.status_button.geometry()
Резюме
Система графики и рисования в PyQt6 - это обширная тема, которая сама по
себе могла бы стать целой книгой. Класс QPainter важен для выполнения рисования
на виджетах и других устройствах рисования. QPainter работает вместе с классами
QPaintEngine и QPaintDevice, предоставляя инструменты, необходимые для создания
приложений двумерного рисования.
Мы рассмотрели некоторые функции класса QPainter для рисования линий,
примитивных и абстрактных фигур. Вместе с QPen, QBrush и QColor QPainter позволяет
создавать довольно красивые цифровые изображения. Чтобы материализовать
эту концепцию, мы создали простое приложение для рисования. Надеемся, что
вы усовершенствуете это приложение и добавите еще больше возможностей для
рисования.
Мы также рассмотрели, как создавать свойства для объектов, созданных на основе
класса QObject, и затем анимировать эти объекты в Qt Graphics View Framework.
Это не рассматривается в данной книге, но вы можете использовать графическое
представление для создания графического интерфейса с интерактивными
элементами. Мы также использовали QPropertyAnimation для анимирования свойств
виджетов.
В Главе 12 мы рассмотрим, как создавать пользовательские виджеты в PyQt.
Создание
пользовательских
виджетов
Хотя большинство задач разработки можно решить с помощью кнопок, виджетов
редактирования текста и других компонентов, предоставляемых PyQt, в какой-то
момент вы можете оказаться в ситуации, когда ни один виджет не обеспечивает
вас необходимыми инструментами или функциональностью. Вы даже можете
столкнуться с необходимостью использовать созданный вами виджет в других
графических интерфейсах, и тогда вам понадобится способ легко импортировать
созданный вами виджет в другие приложения. К счастью, PyQt позволяет
разработчикам создавать и импортировать собственные виджеты для решения
новых и непредвиденных задач.
В этой главе вы
• Узнаете о создании собственных пользовательских виджетов в PyQt
• Увидите, как применить созданный пользовательский виджет в небольшом
примере графического интерфейса
• Познакомитесь с четырьмя классами Qt для работы с изображениями
• Использовать новый виджет, QSlider, для выбора значений в ограниченном
диапазоне.
Давайте познакомимся с пользовательским виджетом, который мы построим в
следующих разделах.
Виджет QSlider
Класс QSlider предоставляет разработчику инструмент для выбора целочисленных
значений в ограниченном диапазоне. Ползунки предоставляют пользователям
удобное средство для быстрого выбора значений или изменения настроек всего лишь
простым движением ручки. По умолчанию ползунки располагаются вертикально
(задается флагом Qt.Orientation.Vertical), но это можно изменить, передав конструктору
флаг Qt.Orientation.Horizontal.
Следующий блок кода демонстрирует, как создать экземпляр QSlider, установить
максимальное значение диапазона ползунка и подключиться к сигналу valueChanged,
который выдается при изменении значения ползунка.
QSlider#Red:sub-page:horizontal{
background: qlineargradient(x1:1, y1:0, x2:0, y2:1,
stop: 0 #FF4242, stop: 1 #1C1C1C);
background: qlineargradient(x1:0, y1:1, x2:1, y2:1,
stop: 0 #1C1C1C, stop: 1 #FF0000);
border: 1px solid #4C4B4B;
height: 10px;
border-radius: 4px;
}
QSlider::add-page:horizontal {
background: #FFFFFF;
border: 1px solid #4C4B4B;
height: 10px;
border-radius: 4px;
}
QSlider::handle:horizontal {
background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
stop: 0 #EEEEEE, stop: 1 #CCCCCC);
border: 1px solid #4C4B4B;
width: 13px;
margin-top: -3px;
margin-bottom: -3px;
border-radius: 4px;
}
QSlider#Blue:sub-page:horizontal{
background: qlineargradient(x1:1, y1:0, x2:0, y2:1,
stop: 0 #FF4242, stop: 1 #1C1C1C);
background: qlineargradient(x1:0, y1:1, x2:1, y2:1,
stop: 0 #1C1C1C, stop: 1 #0000FF);
border: 1px solid #4C4B4B;
height: 10px;
border-radius: 4px;
}
"""
Класс RGBSlider наследует QWidget в Листинге 12-4. Для этого класса пользователь
может передавать в конструктор изображение и другие аргументы в качестве
параметров.
ГЛАВА 12 СОЗДАНИЕ ПОЛЬЗОВАТЕЛЬСКИХ ВИДЖЕТОВ 307
Листинг 12-4. Код для начала построения класса RGBSlider
# rgb_slider.py
class RGBSlider(QWidget):
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(225, 300)
self.setWindowTitle("12.1 - RGB Ползунок")
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(style_sheet)
window = RGBSlider()
sys.exit(app.exec())
self.cd_label = QLabel()
self.cd_label.setPixmap(
QPixmap.fromImage(self.color_display))
self.cd_label.setScaledContents(True)
self.red_spinbox = QSpinBox()
self.red_spinbox.setMaximum(255)
green_label = QLabel("Зелёный")
green_label.setFont(QFont("Helvetica", 14))
self.green_slider = QSlider(Qt.Orientation.Horizontal)
self.green_slider.setObjectName("Green")
self.green_slider.setMaximum(255)
blue_label = QLabel("Синий")
blue_label.setFont(QFont("Helvetica", 14))
self.blue_slider = QSlider(Qt.Orientation.Horizontal)
self.blue_slider.setObjectName("Blue")
self.blue_slider.setMaximum(255)
self.blue_spinbox = QSpinBox()
self.blue_spinbox.setMaximum(255)
hex_h_box = QHBoxLayout()
hex_h_box.addWidget(
hex_label, Qt.AlignmentFlag.AlignRight)
hex_h_box.addWidget(self.hex_values_label,
Qt.AlignmentFlag.AlignRight)
hex_container = QWidget()
hex_container.setLayout(hex_h_box)
Обновление цветов
Ползунки QSlider и QSpinBox могут выдавать сигнал valueChanged. Мы можем
связать ползунки и регуляторы так, чтобы их значения изменялись относительно друг
друга. Например, когда red_slider издает сигнал, он запускает слот updateRedSpinBox(),
который затем обновляет значение red_spinbox с помощью setValue(). Аналогичный
процесс происходит и для red_spinbox. Этот процесс также происходит для ползунков
и регуляторов, управляющих значениями синего и зеленого цветов.
Посмотрите на сигналы valueChanged в Листинге 12-8 для ползунка и
соответствующего ему регулятора, и вы заметите, что они запускают слоты, которые
обновляют друг друга.
Листинг 12-8. Код метода setUpMainWindow() в классе RGBSlider, часть 4
# rgb_slider.py
# Ползунки и регуляторы для каждого цвета должны
# отображать одинаковые значения и обновляться
# одновременно
self.red_slider.valueChanged.connect(
self.updateRedSpinBox)
self.red_spinbox.valueChanged.connect(
self.updateRedSlider)
self.green_slider.valueChanged.connect(
self.updateGreenSpinBox)
self.green_spinbox.valueChanged.connect(
self.updateGreenSlider)
self.blue_slider.valueChanged.connect(
self.updateBlueSpinBox)
self.blue_spinbox.valueChanged.connect(
self.updateBlueSlider)
main_v_box = QVBoxLayout()
main_v_box.addWidget(self.cd_label)
main_v_box.addWidget(rgb_widgets)
self.setLayout(main_v_box)
self.cd_label.setPixmap(QPixmap.fromImage(
self.color_display))
self.hex_values_label.setText(
f"{self.current_val.name()}")
self.updateRedSpinBox(red_val)
self.updateRedSlider(red_val)
self.updateGreenSpinBox(green_val)
self.updateGreenSlider(green_val)
self.updateBlueSpinBox(blue_val)
self.updateBlueSlider(blue_val)
Демонстрация RGB-ползунка
Одна из причин создания пользовательского виджета заключается в том, что его
можно использовать в других приложениях. Следующая программа представляет
собой краткий пример импорта и настройки RGB-ползунка, встроенного в
Проект 12.1. В данном примере в окне рядом с ползунком RGB отображается
изображение. Пользователи могут щелкать по точкам изображения и видеть, как в
реальном времени изменяются RGB и шестнадцатеричные значения.
Графический интерфейс этой короткой программы показан на рис. 12-2.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(225, 300)
self.setWindowTitle("12.2 - Пример пользовательского виджета")
# Загрузить изображение
image = QImage("images/duck_pic.png")
Резюме
Не все проблемы можно решить с помощью виджетов, предоставляемых Qt.
В ситуациях, когда требуется проявить изобретательность, PyQt очень удобен,
поскольку позволяет разработчикам создавать и настраивать собственные виджеты.
Это можно сделать различными способами, например, создать новый виджет на
основе уже существующих или создать совершенно новый виджет с нуля. После
этого новый виджет может быть легко импортирован в другие приложения.
В Главе 13 вы узнаете, как создавать современные графические интерфейсы с
помощью Qt Quick.
Работа с Qt Quick
Графические пользовательские интерфейсы сегодня можно встретить на мно
жестве устройств, включая настольные компьютеры, мобильные устройства
и небольшие сенсорные экраны, управляемые микроконтроллерами. Хотя
пользовательские интерфейсы разрабатываются в соответствии с функциональными
и техническими потребностями каждого устройства, требования этих платформ
побудили компанию Qt продолжить создание набора масштабируемых, отточенных,
динамичных и визуально ошеломляющих инструментов для построения
пользовательских интерфейсов.
В этой главе вы
• Получите обзор модулей QtQuick и QtQml, а также языка программирования QML
• Узнаете, как писать и запускать простые приложения с использованием QML и
PyQt
• Узнаете, как создавать компоненты QML и использовать их в других файлах .qml
• Использовать различные методы расположения элементов QtQuick с помощью
QML
• Экспериментировать с различными типами QML для создания приложений
• Использовать простые преобразования для анимации объектов
Цель этой главы - дать общее представление о QtQuick и языке Qt QML. Если ваша
цель - дальнейшее использование QtWidgets, то эта глава может не дать информации,
полезной на данном этапе разработки. Однако мы надеемся, что для начинающих
разработчиков PyQt эта глава может дать несколько полезных идей и представлений
о том, что могут предложить последние версии Qt и PyQt.
Если после завершения этой главы вы поймете, что QtQuick и QML вас
заинтересовали, или если у вас возникнут вопросы по ходу работы, то следующие
ссылки помогут вам получить дополнительную информацию и рекомендации:
• Краткий учебник по QML: https://doc.qt.io/qt-6/qml-tutorial.html
• Онлайновая книга по QML, предоставленная компанией The Qt Company:
www.qt.io/product/qt6/qml-book
• Информация по работе с PyQt и QML:
www.riverbankcomputing.com/static/Docs/PyQt6/qml.html
• Информация о Qt Quick в Qt 6: https://doc.qt.io/qt-6/qtquick-index.html
Прежде чем приступить к созданию кода, давайте рассмотрим некоторые общие
элементы и свойства, которые мы будем использовать при создании приложений
QtQuick.
Элементы в QtQuick
Элементы, называемые также типами, являются встроенными строительными
блоками, используемыми для создания графических интерфейсов в QtQuick. Термин
"элементы" включает в себя как визуальные, так и невизуальные типы. Визуальные
элементы имеют геометрию и могут быть расположены в графическом интерфейсе,
а невизуальные элементы обычно используются для управления визуальными
элементами.
Чтобы создать экземпляр типа в QML, достаточно вызвать элемент, за которым
следует пара скобок. Пример создания элемента Rectangle показан в следующей
строке:
Rectangle {...}
Типы объектов всегда начинаются с заглавной буквы. Каждый элемент также имеет
ряд определенных свойств, таких как width (ширина), height (высота), color (цвет)
и text (текст), которые используются для задания различных аспектов элемента.
Свойства также могут использоваться для позиционирования элементов, задания
геометрических преобразований и обработки изменений состояния. Эти свойства
задаются между скобками.
В табл. 13-1 описаны некоторые распространенные и интересные элементы,
320 ГЛАВА 13 РАБОТА С QT QUICK
встречающиеся в QtQuick. Полный список типов QML можно найти на сайте
https://doc.qt.io/qt-6/qmltypes.html.
Rectangle {
id: rect
width: 155; height: 80
color: "skyblue"
Text {
text: "Small Component"
322 ГЛАВА 13 РАБОТА С QT QUICK
x: 10; y: 30
font.pixelSize: 16
font.weight: Font.DemiBold
color: "black"
}
}
Система координат
Визуальный холст в QtQuick представляет собой двумерную поверхность для
расположения объектов. Левый верхний пиксель окна находится в точке (0, 0). Хотя
холст является двумерным, он также имеет z-размещение для упорядочивания
объектов при их наложении друг на друга.
Интересно, что дочерние элементы являются относительными по отношению к
своим родителям, то есть дочерний элемент наследует систему координат родителя
ГЛАВА 13 РАБОТА С QT QUICK 323
и располагается на основе его левого верхнего угла. Пример этого можно найти на
сайте https://doc.qt.io/qt-6/qtquickvisualcanvas-coordinates.html.
Существует множество способов организации элементов в окне графического
интерфейса, многие из которых будут рассмотрены в разделе "Обработка макетов в
QML". В данном примере мы можем вручную расположить объекты в окне, указав
значения x и y для объекта Text.
Item {
id: root
width: 340; height: 420
anchors.horizontalCenter: root.horizontalCenter
anchors.topMargin: 40
Image {
id: image
anchors.centerIn: container
source: "images/qtquick_text.png"
sourceSize.width: container.width
sourceSize.height: container.height
}
Для того чтобы сделать общий Python-скрипт, которому можно передавать QML-
файлы в качестве аргументов при запуске приложения, мы также воспользуемся
модулем Python argparse в Листинге 13-3 (Более подробную информацию об argparse
можно найти на сайте https://docs.python.org/3.9/howto/argparse.html).
Для начала импортируем несколько классов PyQt6 в новый Python-скрипт.
Поскольку мы не используем QtWidgets, нет необходимости импортировать
QApplication. Вместо этого для приложений, связанных с графическим интерфейсом
и не использующих виджеты, используется QGuiApplication.
def parseCommandLine():
"""Use argparse to parse the command line for specifying
a path to a QML file."""
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--file", type=str,
help="A path to a .qml file to be the source.",
required=True)
args = vars(parser.parse_args())
return args
class MainView(QQuickView):
def __init__(self):
""" Constructor for loading QML files """
super().__init__()
self.setSource(QUrl(args["file"]))
# Get the Status enum's value and check for an error
if self.status().name == "Error":
sys.exit(1)
else:
self.show()
if __name__ == "__main__":
args = parseCommandLine() # Return command line arguments
app = QGuiApplication(sys.argv)
view = MainView()
sys.exit(app.exec())
Rectangle {
id: root
width: 80; height: 80
color: "#1FC6DE" // Cyan-like color
border.color: "#000000"
border.width: 4
radius: 5
Text {
text: root.color
anchors.centerIn: root
}
Позиционер Описание
Столбец Размещает дочерние элементы в одном столбце
Строка Размещает дочерние элементы в одной строке
Сетка Позиционирует дочерние элементы в сетке
Поток Располагает дочерние элементы рядом друг с другом, причем дочер
ние элементы могут быть обернуты сверху вниз или слева направо
Rectangle {
width: 200; height: 300
color: "grey"
Column {
id: column
anchors.centerIn: parent
spacing: 6
// Add custom components to Column
ColorRect { }
ColorRect { }
ColorRect { color: "pink"}
}
}
Кроме того, если вы посмотрите на третий экземпляр ColorRect, то заметите,
что свойства компонентов все еще можно изменять при их инстанцировании.
Попробуйте переключить Column на Row или Flow в Листинге 13-5 и посмотрите на
разницу в графическом интерфейсе.
На рис. 13-5 приведен пример позиционера Grid.
Rectangle {
width: 200; height: 200
color: "grey"
Grid {
id: grid
rows: 2; columns: 2
anchors.centerIn: parent
spacing: 6
// Add custom components to Column
ColorRect { }
ColorRect { }
ColorRect { radius: 20 }
ColorRect { }
}
}
Несмотря на то, что в этих двух примерах для иллюстрации использования
позиционеров используются типы Rectangle, они более эффективны при расположении
кнопок, циферблатов и других элементов пользовательского интерфейса.
В следующем разделе вы приблизитесь к созданию полноценных классических
настольных приложений, узнав, как создавать строки меню и отображать диалоговые
окна в QtQuick.
Средства
Описание
управления
Action Описываются действия, которые могут быть назначены пунктам
меню и панелям инструментов
ApplicationWindow Окно с дополнительной функциональностью для добавления
строки меню, верхнего и нижнего колонтитулов
Button Кнопка, на которую может нажать пользователь для выполнения
действия
CheckBox Кнопка проверки, которую можно включать и выключать
ComboBox Представляет выпадающий список для выбора опций
Dial Круговой диск, вращая который можно выбрать значение
Dialog Всплывающее диалоговое окно со стандартными кнопками и
заголовком
DialogButtonBox Кнопочное поле, которое используется для задания кнопок в
диалоговом окне
Frame Обеспечивает визуальную рамку для организации других
элементов управления
GroupBox Обеспечивает визуальную рамку с заголовком для организации
других элементов управления
MenuBar Создает в окне строку меню
RadioButton Радиокнопки, которые являются автоисключающими и могут быть
включены или выключены
Slider Используется для выбора значения с помощью подвижной
рукоятки на направляющей
TabBar Создает панель вкладок для переключения между различными
представлениями
Tumbler Колесо, которое можно вращать для выбора значений
ApplicationWindow {
title: "QtQuick Image Viewer"
width: 800; height: 500
visible: true
Image {
id: image
anchors.fill: container
source: "images/open_image.png"
fillMode: Image.PreserveAspectFit
}
}
}
ApplicationWindow является корневым объектом приложения и будет загружен
в следующем разделе с помощью QQmlApplicationEngine. ApplicationWindow также
включает свойство title. Очень важно, чтобы при использовании QQmlApplicationEngine
вы не забыли включить строку visible: true. Если забыть об этом свойстве, то окно
останется скрытым, так как по умолчанию ApplicationWindow не видно.
Элемент Image используется в окне ApplicationWindow для отображения выбранного
изображения пользователю. Хотя изображение может быть непосредственно
помещено в окно, использование свойства anchors.margins элемента Rectangle
позволяет сделать тонкую границу вокруг изображения.
def parseCommandLine():
"""Use argparse to parse the command line for specifying
a path to a QML file."""
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--file", type=str,
help="A path to a .qml file to be the source.",
required=True)
args = vars(parser.parse_args())
return args
class MainView(QQmlApplicationEngine):
def __init__(self):
super().__init__()
# Order matters here; need to check if the object was
# created before loading the QML file
self.objectCreated.connect(self.checkIfObjectsCreated,
Qt.ConnectionType.QueuedConnection)
self.load(QUrl(args["file"]))
if __name__ == "__main__":
args = parseCommandLine() # Return command line arguments
app = QGuiApplication(sys.argv)
engine = MainView()
sys.exit(app.exec())
ApplicationWindow {
title: "Simple Transformations"
width: 300; height: 300
visible: true
MouseArea {
id: windowMouse
anchors.fill: parent
onClicked: {
// Reset the values of the ColorRect objects
rect1.rotation = 0
rect2.scale = 1.0
}
}
ColorRect {
id: rect1
x: 20; y: 20
antialiasing: true
signal clicked
onClicked:{
// Rotate the rect 20˚ when clicked
rotation += 20
}
onClicked:{
// Scale the rect when clicked
scale += .1
}
}
}
Далее мы создадим элемент MouseArea для обработки нажатий на окно
приложения. Поскольку в коде QML порядок имеет значение, создание MouseArea
перед другими элементами гарантирует, что окно также будет получать щелчки, а не
только элементы ColorRect.
Два объекта ColorRect располагаются в окне вручную. Для каждого из объектов
определяется новый сигнал - clicked. Затем мы можем использовать обработчик
сигнала onClicked в каждом из элементов ColorRect для поворота или масштабирования
элементов с помощью встроенных свойств Item.
Щелкните в любом месте окна, чтобы сбросить значения ColorRect.
Чтобы загрузить Листинг 13-9, выполните в оболочке следующую команду:
$ python3 qml_loader.py -f rotate_and_move.qml
Рассмотрим последний пример, демонстрирующий использование трансформаций
и некоторых других классов QtQuick для анимации элементов.
ApplicationWindow {
title: "Spin Wheel Transformations"
width: 500; height: 500
visible: true
Image {
id: pointer
source: "images/pointer.png"
Image {
id: spinwheel
anchors.centerIn: parent
source: "images/spin_wheel.png"
sourceSize.width: parent.width - 30
sourceSize.height: parent.height - 30
Резюме
С появлением Qt 6 все больше внимания уделяется созданию динамических
графических интерфейсов, которые являются кроссплатформенными,
масштабируемыми, простыми в обслуживании и призваны максимально
использовать возможности графического оборудования на любой платформе.
QtQuick, инструментарий, основанный на языке Qt QML, определяет широкий
спектр удивительных графических инструментов, которые отлично подходят для
настольных, мобильных и встраиваемых приложений.
О QtQuick и QML можно узнать гораздо больше. Цель этой главы - дать вам
основные сведения для начала работы с QtQuick, а затем направить вас в нужное
русло с помощью ссылок и другой полезной информации.
В этой главе мы рассмотрели, как создать базовое окно, добавить QML-элементы
и элементы управления в приложение и даже обсудили, как создавать многократно
используемые QML-компоненты, которые можно использовать в других
приложениях. Затем эти компоненты были использованы для демонстрации других
концепций QtQuick, таких как макеты и трансформации. Мы увидели, как загружать
QML-файлы с помощью QQuickView и QQmlApplicationEngine. Последнее приложение,
которое мы создали, продемонстрировало, как анимировать объекты, используя как
типы Behavior, так и JavaScript.
Откровенно говоря, для того чтобы охватить и описать все, что можно предложить
в QML, необходимо полное руководство по QtQuick. Так много содержания и так
много возможностей для использования QtQuick и QML при создании графических
интерфейсов.
В Главе 14 мы вернемся к использованию QtWidgets и узнаем, как начать создавать
приложения, взаимодействующие с базами данных SQL и управляющие ими.
Введение в работу с
базами данных
Данные являются основополагающим фактором, определяющим изменения
в современном бизнесе, коммуникациях, науке и даже в нашей личной жизни.
Информация, которую мы получаем в результате покупок в интернете, сообщений
в социальных сетях, запросов в поисковых системах и данных о местоположении,
собирается, управляется и анализируется и может использоваться по разным
причинам, в том числе для отслеживания потребительских моделей, обучения
алгоритмов искусственного интеллекта или даже для изучения географического
распределения определенных событий, например, заболеваний.
В этой главе мы
• Познакомимся с архитектурой Qt "Model/View" для создания графических
интерфейсов, работающих с данными
• Используем класс QTableView для создания приложений, работающих с
данными
• Посмотрим, как работать с CSV-файлами в PyQt
• Представим модуль QtSql для создания и управления реляционными базами
данных SQL.
Прежде чем начать, давайте немного поразмышляем о пользе данных.
Размышления о данных
Анализ данных, или процесс организации, модификации и моделирования
данных, является важным процессом, и в этой главе мы рассмотрим работу
со структурированными данными для разработки графических интерфейсов.
Данные могут храниться в различных форматах, включая текстовые, визуальные и
мультимедийные.
Для анализа данных необходимо организовать их в виде структур, которые можно
хранить и затем получать к ним доступ в электронном виде через компьютерную
систему. Иногда приходится работать с небольшим набором данных, состоящим из
одного-двух файлов. В других случаях может потребоваться доступ к определенным
фрагментам целой базы данных, содержащей конфиденциальную информацию.
База данных - это организованная коллекция из нескольких наборов данных.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setGeometry(100, 100, 450, 300)
self.setWindowTitle("Пример модели и представления")
self.setupMainWindow()
self.loadCSVFile()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Таблицы отлично подходят для организации и отображения различных типов
текстовых (а иногда и графических) данных, например, информации о сотрудниках
или инвентаризации.
Начнем с импорта классов, в том числе QTableView из модуля QtWidgets и классов
QStandardItemModel и QStandardItem из QtGui. QStandardItemModel предоставляет
модель на основе элементов, необходимую для работы с данными; QStandardItem
создает элементы, которые используются в модели.
table_view = QTableView()
table_view.setSelectionMode(
QAbstractItemView.SelectionMode.ExtendedSelection)
table_view.setModel(self.model)
main_v_box = QVBoxLayout()
main_v_box.addWidget(table_view)
self.setLayout(main_v_box)
Ключевые слова
Описание
SQLite
AUTOINCREMENT Автоматически генерирует уникальный номер при вставке новой
записи в таблицу
CREATE TABLE Создает новую таблицу в базе данных
DELETE Удаляет строку из таблицы
DROP TABLE Удаляет таблицу, которая уже существует в базе данных
FOREIGN KEY Ограничение, связывающее две таблицы между собой
FROM Указание таблицы, с которой необходимо взаимодействовать при
выборе или удалении данных
INTEGER Целочисленный тип данных со знаком
INSERT INTO Вставляет новые строки в таблицу
MAX() Функция, находящая максимальное значение указанного столбца
NOT NULL Ограничение, гарантирующее, что столбец не будет принимать
значения NULL
PRIMARY KEY Ограничение, однозначно идентифицирующее запись в таблице
REFERENCES Используется с FOREIGN KEY для указания другой таблицы,
имеющей связь с первой таблицей.
SELECT Выборка данных из базы данных
SET Определяет, какие столбцы и значения должны быть обновлены
UNIQUE Ограничение, обеспечивающее уникальность всех значений в
столбце
UPDATE Обновление существующих значений в строке
VALUES Определяет значения оператора INSERT INTO
VARCHAR Переменный символьный тип данных для строк
WHERE Фильтрует результаты запроса, чтобы включить в него только
записи, удовлетворяющие определенным условиям
class CreateEmployeeData:
"""Создаем пример базы данных для проекта.
Класс демонстрирует, как подключаться к базе данных, создавать
запросы, создавать таблицы и записи в этих таблицах."""
# Создаем соединение с базой данных. Если файл db
# не существует, будет создан новый файл db.
# Использовать драйвер SQLite версии 3
database = QSqlDatabase.addDatabase("QSQLITE")
database.setDatabaseName("files/accounts.db")
if not database.open():
print("Невозможно открыть файл источника данных.")
sys.exit(1) # Код ошибки 1 - означает ошибку
country_query.prepare(
"INSERT INTO countries (country) VALUES (?)")
if __name__ == "__main__":
CreateEmployeeData()
sys.exit(0)
class QueryExamples:
def __init__(self):
super().__init__()
self.createConnection()
self.exampleQueries()
if __name__ == "__main__":
QueryExamples()
sys.exit(0)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
ГЛАВА 14 ВВЕДЕНИЕ В РАБОТУ С БАЗАМИ ДАННЫХ 369
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(1000, 500)
self.setWindowTitle("Модель таблицы SQL")
self.createConnection()
self.setUpMainWindow()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
if not database.open():
print("Невозможно открыть файл источника данных.")
sys.exit(1) # Код ошибки 1 - означает ошибку
table_view = QTableView()
table_view.setModel(model)
table_view.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Stretch)
main_v_box = QVBoxLayout()
main_v_box.addWidget(table_view)
self.setLayout(main_v_box)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(1000, 500)
self.setWindowTitle("Модель реляционной таблицы")
self.createConnection()
self.setUpMainWindow()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
В Листинге 14-15 мы подключаемся к базе данных точно так же, как и в примере
QSqlTableModel, только в этот раз мы проверяем обе таблицы - accounts и countries.
if not database.open():
print("Невозможно открыть файл источника данных.")
sys.exit(1) # Код ошибки 1 - означает ошибку
main_v_box = QVBoxLayout()
main_v_box.addWidget(table_view)
self.setLayout(main_v_box)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(1000, 600)
self.setWindowTitle("14.1 - Графический интерфейс управления счетами")
self.createConnection()
self.createModel()
self.setUpMainWindow()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
if not database.open():
print("Невозможно открыть файл источника данных.")
sys.exit(1) # Код ошибки 1 - означает ошибку
ГЛАВА 14 ВВЕДЕНИЕ В РАБОТУ С БАЗАМИ ДАННЫХ 377
# Проверить, существуют ли в базе данных нужные нам таблицы
tables_needed = {"accounts", "countries"}
tables_not_found = tables_needed - \
set(database.tables())
if tables_not_found:
QMessageBox.critical(None, "Ошибка",
f"""<p>The following tables are missing
from the database: {tables_not_found}</p>""")
sys.exit(1) # Код ошибки 1 - означает ошибку
del_product_button = QPushButton("Удалить")
del_product_button.setIcon(QIcon(os.path.join(
icons_path, "trash_can.png")))
del_product_button.setStyleSheet("padding: 10px")
del_product_button.clicked.connect(self.deleteItem)
buttons_h_box = QHBoxLayout()
ГЛАВА 14 ВВЕДЕНИЕ В РАБОТУ С БАЗАМИ ДАННЫХ 379
buttons_h_box.addWidget(add_product_button)
buttons_h_box.addWidget(del_product_button)
buttons_h_box.addStretch()
buttons_h_box.addWidget(sort_combo)
# Инстанцирование делегата
delegate = QSqlRelationalDelegate()
self.table_view.setItemDelegate(delegate)
Для классов представлений поведение при выборе строк, столбцов или отдельных
элементов может быть определено с помощью QAbstractItemView.SelectionBehavior. В
следующем списке описаны различные флаги:
• SelectRows - могут быть выбраны только строки
• SelectColumns - можно выбрать только столбцы
• SelectItems - можно выбрать только отдельные элементы
При программировании по принципу Model/View делегат предоставляет средства
по умолчанию для рисования данных элемента в представлении и для предоставления
виджетов редактора для моделей элементов. Внешний вид и виджеты редактора
делегата элемента могут быть настроены. Для графического интерфейса управления
счетами используется делегат QSqlRelationalDelegate. Этот класс предоставляет
комбобокс для редактирования данных в полях, являющихся внешними ключами
для других таблиц.
Пример комбобокса, используемого делегатом, можно увидеть в правом нижнем
углу рис. 14-4. Виджет появляется всякий раз, когда пользователю необходимо
выбрать страну из таблицы стран, которая будет отображаться в представлении.
Для функции addItem() в Листинге 14-23 мы проверяем количество строк в таблице
с помощью функции rowCount() и с помощью функции insertRow() вставляем пустую
строку в конец представления таблицы. Мы запрашиваем базу данных, чтобы узнать
наибольшее значение id в таблице. Если пользователь не ввел в строку значение id, то
id новой записи будет равен наибольшему значению id плюс единица. Следует также
отметить, что если в новой строке не заполнены все элементы, то при закрытии
приложения новая запись не будет сохранена в модели.
Листинг 14-23. Код для слотов addItem() и deleteItem() в графическом интерфейсе
управления счетами
# account_manager.py
def addItem(self):
"""Добавить новую запись в последнюю строку таблицы."""
last_row = self.model.rowCount()
self.model.insertRow(last_row)
query = QSqlQuery()
query.exec("SELECT MAX (id) FROM accounts")
if query.next():
int(query.value(0))
ГЛАВА 14 ВВЕДЕНИЕ В РАБОТУ С БАЗАМИ ДАННЫХ 381
def deleteItem(self):
"""Удаление всей строки из таблицы."""
current_item = self.table_view.selectedIndexes()
for index in current_item:
self.model.removeRow(index.row())
self.model.select()
Резюме
PyQt предоставляет удобные классы для работы со списками, таблицами и
деревьями. QListWidget, QTableWidget и QTreeWidget полезны при необходимости
просмотра данных в общих ситуациях. Хотя они удобны для создания быстрых
интерфейсов для редактирования данных, если в приложении необходимо иметь
более одного виджета для отображения набора данных, то необходимо также создать
процесс согласования наборов данных и виджетов. Лучшим вариантом является
использование архитектуры Model/View PyQt.
Существуют различные форматы для хранения и управления данными. Одним
из примеров является формат CSV, который удобен для чтения, разбора и хранения
небольших наборов данных. Однако для больших баз данных, содержащих
множество таблиц с реляционными характеристиками, более предпочтительным
вариантом управления данными является реляционная система управления базами
данных, использующая язык SQL. Язык SQL позволяет пользователям выбирать
нужную информацию, которая может быть разделена между таблицами, а также
легко вставлять, обновлять и удалять существующие записи.
Для работы с базами данных SQL очень удобны модели Model/View, предостав
ляющие средства, необходимые для подключения к базе данных и просмотра ее
содержимого. Qt предоставляет три модели для работы с базами данных SQL.
Для редактируемой модели данных без поддержки внешних ключей используйте
QSqlTableModel. Если у вас есть таблицы с реляционными свойствами, используйте
QSqlRelationalTableModel. Наконец, модель QSqlQueryModel удобна в тех случаях,
когда требуется только прочитать результаты запроса без их редактирования.
В ходе работы над этой книгой мы рассмотрели несколько приложений, которые
могли бы извлечь большую пользу из управления данными. Графический интерфейс
входа в систему, описанный в Главе 3, мог бы подключаться к базе данных для
получения имен пользователей и паролей. Есть также графический интерфейс заказа
пиццы из Главы 6. Вы могли бы реализовать базу данных для хранения информации
о клиентах, используя реляционную базу данных для добавления новых клиентов,
обновления существующих и предотвращения дублирования данных.
В Главе 15 мы кратко рассмотрим многопоточность в PyQt.
Управление потоками
Каждый из нас сталкивался с ситуацией, когда выполнение какого-либо процесса,
например копирование файлов между каталогами или запуск нового экземпляра
приложения, приводит к кратковременной задержке программы, а в некоторых
случаях и к ее полному зависанию. В этом случае мы вынуждены либо ждать
завершения текущей задачи, либо удалять Ctrl+Alt+Delete, чтобы освободиться.
При создании графических интерфейсов необходимо знать, как действовать в таких
ситуациях, и предусмотрительно избегать их.
В этой главе мы
• Рассмотрим методы обработки трудоемких процессов в PyQt
• Узнаем, как реализовать многопоточность в графических интерфейсах с
помощью QThread
• Используем виджет QProgressBar для визуального отображения хода
выполнения задачи.
Эта глава преследует две цели: помочь вам разработать более надежные GUI-
приложения, а также рассказать о том, как вы можете справиться с ситуациями,
когда вашим приложениям необходимо запускать длительные процессы. Любое
действие, приводящее к остановке обработки событий, плохо сказывается на работе
пользователя.
Виджет QProgressBar
Виджет QProgressBar визуально передает пользователю информацию о ходе
выполнения расширенной операции. Эта обратная связь может также использоваться
в качестве подтверждения того, что такой процесс, как загрузка, установка или
передача файлов, все еще выполняется. К числу параметров, которыми можно
управлять, относятся ориентация и дальность действия виджета.
Для настройки индикатора выполнения обратитесь к проекту в следующих
разделах.
ГЛАВА 15 УПРАВЛЕНИЕ ПОТОКАМИ 387
Пояснения к графическому интерфейсу
переименования файлов
Окно GUI содержит различные кнопки и виджеты редактора, которые позволяют
пользователю управлять переименованием файлов. Пользователь может выбрать
каталог с помощью кнопки QPushButton и появившегося диалогового окна QFileDialog.
Новое имя файла может быть введено в виджет QLineEdit. С помощью QComboBox
можно также выбрать расширение файлов, которые необходимо изменить.
Приложение использует потоки для обновления индикатора выполнения,
отображения информации об изменяемых файлах в текстовом редакторе и
выполнения самой операции переименования. Для этого используются сигналы и
слоты.
Начнем с использования в качестве шаблона сценария basic_window.py из Главы 1.
Далее импортируем классы Python и PyQt в Листинге 15-1. Класс QThread является
частью QtCore.
Листинг 15-1. Код для импорта и таблицы стилей, используемых в графическом
интерфейсе переименования файлов
# file_rename_threading.py
# Импорт необходимых модулей
import os, sys, time
from PyQt6.QtWidgets import (QApplication, QWidget, QLabel,
QProgressBar, QLineEdit, QPushButton, QTextEdit,
QComboBox, QFileDialog, QGridLayout)
from PyQt6.QtCore import pyqtSignal, QThread
style_sheet = """
QProgressBar{
background-color: #C0C6CA;
color: #FFFFFF;
border: 1px solid grey;
padding: 3px;
height: 15px;
text-align: center;
}
QProgressBar::chunk{
background: #538DB8;
width: 5px;
margin: 0.5px
}
"""
update_value_signal = pyqtSignal(int)
update_text_edit_signal = pyqtSignal(str, str)
clear_text_edit_signal = pyqtSignal()
def stopRunning(self):
"""Прервать поток."""
self.terminate()
self.wait()
self.update_value_signal.emit(0)
self.clear_text_edit_signal.emit()
self.update_value_signal.emit(i + 1)
self.update_text_edit_signal.emit(
file, new_file_name)
else:
pass
# Сброс значения индикатора выполнения
self.update_value_signal.emit(0)
Реализованный метод QThread, run(), начинает выполнение потока. В run()
выполняются трудоемкие операции - обход каталога, переименование файлов, выдача
сигналов для обновления QProgressBar и QTextEdit. Однако этот метод не вызывается
напрямую. Метод QThread start() используется для связи с рабочим потоком и начала
его выполнения путем вызова run(). Метод start() вызывается из метода renameFiles()
класса MainWindow в Листинге 15-8.
Слот stopRunning() используется для завершения процессов потока, когда
пользователь нажимает кнопку Stop в главном окне. Метод terminate() используется
для завершения потока, а wait() - для того, чтобы убедиться в его завершении,
заблокировав поток.
Листинг 15-3 начинает создание класса MainWindow, который наследует QWidget.
Листинг 15-3. Базовый код класса MainWindow
# file_rename_threading.py
class MainWindow(QWidget):
def __init__(self):
self.directory = ""
self.combo_value = ""
self.setUpMainWindow()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyleSheet(style_sheet)
window = MainWindow()
sys.exit(app.exec())
self.dir_edit = QLineEdit()
dir_button = QPushButton("Выберите каталог")
dir_button.setToolTip("Выберите каталог файлов.")
dir_button.clicked.connect(self.chooseDirectory)
self.progress_bar = QProgressBar()
self.progress_bar.setValue(0)
self.setLayout(grid)
Затем виджеты организуются в QGridLayout.
Слот chooseDirectory() в Листинге 15-7 вызывается при нажатии кнопки dir_button
и открывает QFileDialog для выбора директорий. После выбора каталога пользователь
может ввести новые имена файлов в change_name_edit и выбрать расширение для
типов изменяемых файлов в комбинированном окне.
Листинг 15-7. Создание слота chooseDirectory()
# file_rename_threading.py
def chooseDirectory(self):
"""Выбор каталога файлов."""
file_dialog = QFileDialog(self)
file_dialog.setFileMode(
QFileDialog.FileMode.Directory)
self.directory = file_dialog.getExistingDirectory(
self, "Open Directory", "",
QFileDialog.Option.ShowDirsOnly)
if self.directory:
self.dir_edit.setText(self.directory)
self.stop_button.setEnabled(True)
self.stop_button.repaint()
self.stop_button.clicked.connect(
self.worker.stopRunning)
self.worker.update_value_signal.connect(
self.updateProgressBar)
self.worker.update_text_edit_signal.connect(
self.updateTextEdit)
self.worker.finished.connect(
self.worker.deleteLater)
self.worker.start()
Резюме
Предотвращение зависания графического интерфейса при обработке длительных
операций очень важно для удобства пользователя. Существует несколько вариантов
эффективной обработки блокировки в приложении, включая использование
таймеров и потоков. Qt предоставляет класс QThread, который в сочетании с сигналами
и слотами может быть использован для обработки дополнительных процессов в
GUI-приложениях. Однако при использовании QThread следует быть осторожным,
чтобы гарантировать, что потоки защищают доступ к своим собственным данным.
Хотя в коротком проекте этой главы QThread не показан, он также имеет методы,
такие как started(), finished(), wait() и quit(), для управления потоками.
В Главе 16 мы построим несколько примеров проектов для изучения и отработки
различных концепций, не рассмотренных в предыдущих главах.
Дополнительные
проекты
На протяжении всей этой книги мы стремились использовать практический
подход к созданию приложений, чтобы помочь вам освоить основы разработки
графических интерфейсов. По мере дальнейшего использования PyQt6 и Python вы
сможете узнать о других модулях и классах, которые также окажутся полезными.
В некоторых случаях эта книга лишь поверхностно коснулась того, что можно
сделать с помощью PyQt. Благодаря большому количеству модулей, классов и
возможностей настройки, предоставляемых Qt, потенциал создания графических
интерфейсов безграничен. Чтобы расширить ваш опыт работы с PyQt, в этой главе
мы рассмотрим некоторые дополнительные классы Qt, которые не смогли уместить
в предыдущих главах.
В этой главе вы создадите проекты для
1. Отображения каталогов и файлов с помощью QFileSystemModel и QTreeView
2. Создание графического интерфейса, который делает фотографии с помощью
QCamera и демонстрирует, как создавать пользовательские диалоги с помощью
QDialog
3. Создание простого графического интерфейса часов с использованием QDate и
QTime
4. Изучение класса QCalendarWidget
5. Создание "Виселицы" с помощью QPainter и других классов PyQt
6. Построение framework для веб-браузера с помощью модуля QtWebEngineWidgets
7. Создание трехпозиционных виджетов QComboBox
Пояснения к каждому проекту не будут слишком подробными. Скорее, они
сфокусированы на объяснении ключевых моментов каждой программы и оставляют
за вами право изучить детали, в которых вы не уверены, либо найдя ответы в другой
главе, либо обратившись за помощью к Интернету.
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(500, 400)
self.setWindowTitle("16.1 – Графический интерфейс просмотра каталога")
self.setUpMainWindow()
self.createActions()
self.createMenu()
self.show()
def setUpMainWindow(self):
"""Настройте QTreeView в главном окне для отображения
содержимого локальной файловой системы."""
self.model = QFileSystemModel()
self.model.setRootPath("")
self.tree = QTreeView()
self.tree.setIndentation(10)
self.tree.setModel(self.model)
self.setCentralWidget(frame)
def createActions(self):
"""Создание действий меню приложения."""
# Создание действий для меню Каталоги
self.open_dir_act = QAction("Открыть каталог...")
self.open_dir_act.triggered.connect(self.chooseDirectory)
def createMenu(self):
"""Создать панель меню приложения."""
self.menuBar().setNativeMenuBar(False)
def chooseDirectory(self):
"""Слот выбора каталога для отображения."""
file_dialog = QFileDialog(self)
file_dialog.setFileMode(QFileDialog.FileMode.Directory)
directory = file_dialog.getExistingDirectory(self, "Открыть каталог",
"", QFileDialog.Option.ShowDirsOnly)
self.tree.setRootIndex(self.model.index(directory))
def returnToRootDirectory(self):
"""Слот для повторного отображения содержимого корневого каталога."""
self.tree.setRootIndex(self.model.index(""))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
class ImageDialog(QDialog):
self.pixmap = QPixmap().fromImage(image)
image_label = QLabel()
image_label.setPixmap(self.pixmap)
dialog_v_box = QVBoxLayout()
dialog_v_box.addWidget(image_label)
dialog_v_box.addWidget(self.button_box)
self.setLayout(dialog_v_box)
def accept(self):
"""Реализация метода accept() для сохранения файла
изображения в каталоге images."""
file_format = "png"
today = QDate().currentDate().toString(Qt.DateFormat.ISODate)
file_name = f"images/image{self.id}_{today}.png"
self.pixmap.save(file_name, file_format)
super().accept()
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
ГЛАВА 16 ДОПОЛНИТЕЛЬНЫЕ ПРОЕКТЫ 403
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(500, 400)
self.setWindowTitle("16.2 - Графический интерфейс камеры")
self.setUpMainWindow()
self.show()
def setUpMainWindow(self):
"""Создание и расположение виджетов в главном окне."""
# Создание каталога вывода изображения
exists = os.path.exists("images")
if not exists:
os.makedirs("images")
video_widget = QVideoWidget(self)
self.camera.start()
main_v_box = QVBoxLayout()
main_v_box.addWidget(info_label)
main_v_box.addWidget(video_widget, 1)
self.setLayout(main_v_box)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
class DisplayTime(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setGeometry(100, 100, 250, 100)
self.setWindowTitle("16.3 – Пример QDateTime")
self.setStyleSheet("background-color: black")
self.setUpMainWindow()
self.show()
def setUpMainWindow(self):
"""Создание меток, отображающих текущую дату и время в главном
окне программы."""
current_date, current_time = self.getDateTime()
self.date_label = QLabel(current_date)
self.date_label.setStyleSheet("color: white; font: 16px Courier")
self.time_label = QLabel(current_time)
self.time_label.setStyleSheet("""color: white;
border-color: white;
408 ГЛАВА 16 ДОПОЛНИТЕЛЬНЫЕ ПРОЕКТЫ
border-width: 2px;
border-style: solid;
border-radius: 4px;
padding: 10px;
font: bold 24px Courier""")
self.setLayout(v_box)
def getDateTime(self):
"""Возвращает текущую дату и время."""
date = QDate.currentDate().toString("MMMM dd, yyyy")
time = QTime.currentTime().toString("hh:mm:ss AP")
return date, time
def updateDateTime(self):
"""Слот, обновляющий значения даты и времени."""
date = QDate.currentDate().toString("MMMM dd, yyyy")
time = QTime.currentTime().toString("hh:mm:ss AP")
self.date_label.setText(date)
self.time_label.setText(time)
return date, time
if __name__ == '__main__':
app = QApplication(sys.argv)
window = DisplayTime()
sys.exit(app.exec())
style_sheet = """
QLabel{
padding: 5px;
font: 18px
}
QLabel#DateSelected{
font: 24px
}
QGroupBox{
border: 2px solid gray;
border-radius: 5px;
margin-top: 1ex;
font: 14px
}
"""
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(500, 400)
self.setWindowTitle("16.4 – Графический интерфейс календаря")
self.setUpMainWindow()
self.show()
def setUpMainWindow(self):
"""Создание и расположение виджетов в главном окне."""
self.calendar = QCalendarWidget()
def newDateSelection(self):
"""Обновление даты в виджетах current_label и current_date_edit
при выборе новой даты."""
date = self.calendar.selectedDate().toString("MMMM dd, yyyy")
self.current_date_edit.setDate(self.calendar.selectedDate())
self.current_label.setText(date)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(style_sheet)
window = MainWindow()
sys.exit(app.exec())
style_sheet = """
QLabel#Word{
font: bold 20px;
qproperty-alignment: AlignCenter
}
QPushButton#Letters{
background-color: #1FAEDE;
color: #D2DDE1;
border-style: solid;
border-radius: 3px;
border-color: #38454A;
font: 28px
}
QPushButton#Letters:pressed{
background-color: #C86354;
border-radius: 4px;
padding: 6px;
color: #DFD8D7
}
QPushButton#Letters:disabled{
background-color: #BBC7CB
}
"""
class DrawingLabel(QLabel):
def __init__(self):
"""Палач рисуется не в главном окне, а на объекте QLabel.
Этот класс обрабатывает рисование."""
super().__init__()
self.height = 200
self.width = 300
self.incorrect_letter = False
self.incorrect_turns = 0
self.wrong_parts_list = []
# Сброс переменной
self.incorrect_letter = False
painter.end()
class Hangman(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setFixedSize(400, 500)
self.setWindowTitle("16.5 - Графический интерфейс 'Виселица'")
self.newGame()
self.show()
def newGame(self):
"""Создание новой игры Виселица. Устанавливает объекты
для главного окна."""
self.setUpHangmanBoard()
self.setUpWord()
self.setUpBoard()
def setUpHangmanBoard(self):
"""Настройте объект метки для отображения палача."""
self.hangman_label = DrawingLabel()
self.hangman_label.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
def setUpWord(self):
"""Откройте файл words и выберите произвольное слово.
Создать метки, которые будут отображать '_' в зависимости
от длины слова."""
words = self.openFile()
self.chosen_word = random.choice(words).upper()
#print(self.chosen_word)
word_h_box = QHBoxLayout()
self.word_frame = QFrame()
self.word_frame.setLayout(word_h_box)
def setUpBoard(self):
"""Настройка объектов и раскладок для клавиатуры и главного окна."""
top_row_list = ["A", "B", "C", "D", "E",
"F", "G", "H"]
mid_row_list = ["I", "J", "K", "L", "M",
"N", "O", "P", "Q"]
bot_row_list = ["R", "S", "T", "U", "V",
"W", "X", "Y", "Z"]
top_frame = QFrame()
top_frame.setLayout(top_row_h_box)
mid_frame = QFrame()
mid_frame.setLayout(mid_row_h_box)
bot_frame = QFrame()
bot_frame.setLayout(bot_row_h_box)
keyboard_v_box = QVBoxLayout()
keyboard_v_box.addWidget(top_frame)
keyboard_v_box.addWidget(mid_frame)
keyboard_v_box.addWidget(bot_frame)
keyboard_frame = QFrame()
keyboard_frame.setLayout(keyboard_v_box)
def openFile(self):
"""Open words.txt file."""
try:
with open("files/words.txt", 'r') as f:
word_list = f.read().splitlines()
return word_list
except FileNotFoundError:
print("File Not Found.")
ex_list = ["nofile"]
return ex_list
if message == QMessageBox.StandardButton.No:
self.close()
else:
self.newGame()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(style_sheet)
window = Hangman()
sys.exit(app.exec())
Кроме того, убедитесь, что вы загрузили папку icons из репозитория GitHub этой
главы.
style_sheet = """
QTabWidget:pane{
border: none
}
"""
class WebBrowser(QMainWindow):
def __init__(self):
super().__init__()
# Создайте списки, в которых будут отслеживаться новые окна,
# вкладки и ссылки
self.window_list = []
self.list_of_web_pages = []
self.list_of_urls = []
self.initializeUI()
def initializeUI(self):
self.setMinimumSize(300, 200)
self.setWindowTitle("16.6 – Web-браузер")
self.setWindowIcon(QIcon(os.path.join("icons", "pyqt_logo.png")))
self.sizeMainWindow()
self.createToolbar()
self.setUpMainWindow()
def setUpMainWindow(self):
"""Создайте объект QTabWidget и различные страницы для главного окна.
Обработка закрытия вкладки."""
self.tab_bar = QTabWidget()
self.tab_bar.setTabsClosable(True) # Добавление кнопок закрытия на вкладки
self.tab_bar.setTabBarAutoHide(True) # Скрывает панель вкладок, если в ней менее 2
вкладок
self.tab_bar.tabCloseRequested.connect(self.closeTab)
# Создать вкладку
self.main_tab = QWidget()
self.tab_bar.addTab(self.main_tab, "New Tab")
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
def createActions(self):
"""Создание действий меню приложения."""
# Создание действий для меню Файл
self.new_window_act = QAction("Новое окно", self)
self.new_window_act.setShortcut("Ctrl+N")
self.new_window_act.triggered.connect(self.openNewWindow)
def createMenu(self):
"""Создание строки меню приложения."""
self.menuBar().setNativeMenuBar(False)
def createToolbar(self):
"""Настройка панели инструментов навигации."""
tool_bar = QToolBar("Адресный бар")
tool_bar.setIconSize(QSize(30, 30))
self.addToolBar(tool_bar)
tool_bar.addAction(home_button)
tool_bar.addAction(back_page_button)
tool_bar.addAction(forward_page_button)
tool_bar.addAction(refresh_button)
tool_bar.addWidget(self.address_line)
tool_bar.addAction(stop_button)
def setUpWebView(self):
ГЛАВА 16 ДОПОЛНИТЕЛЬНЫЕ ПРОЕКТЫ 427
"""Создайте объект QWebEngineView, который
используется для просмотра веб-документов.
Настройте главную страницу и обработайте сигналы web_view."""
web_view = QWebEngineView()
web_view.setUrl(QUrl("https://google.com"))
ok = web_view.loadFinished.connect(self.updateTabTitle)
if ok:
# Загрузка веб-страницы
return web_view
else:
print("Запрос завершился по таймеру.")
tab_v_box = QVBoxLayout()
# Устанавливает левое, верхнее, правое и нижнее поля,
# используемые для макета.
tab_v_box.setContentsMargins(0,0,0,0)
tab_v_box.addWidget(self.web_page)
tab.setLayout(tab_v_box)
def openNewTab(self):
"""Создание новой веб-вкладки."""
new_tab = QWidget()
self.tab_bar.addTab(new_tab, "Новая вкладка")
self.setUpTab(new_tab)
def updateTabTitle(self):
"""Обновите название вкладки, чтобы оно соответствовало названию веб-сайта."""
tab_index = self.tab_bar.currentIndex()
title = self.list_of_web_pages[self.tab_bar.currentIndex()].page().title()
self.tab_bar.setTabText(tab_index, title)
def updateUrl(self):
"""Обновление url в адресе для отражения текущей отображаемой страницы."""
url = self.list_of_web_pages[self.tab_bar.currentIndex()].page().url()
formatted_url = QUrl(url).toString()
self.list_of_urls[self.tab_bar.currentIndex()].setText(formatted_url)
# Запрос url
if url.isValid():
self.list_of_web_pages[self.tab_bar.currentIndex()].page().load(url)
else:
url.clear()
def backPageButton(self):
tab_index = self.tab_bar.currentIndex()
self.list_of_web_pages[tab_index].back()
def forwardPageButton(self):
tab_index = self.tab_bar.currentIndex()
self.list_of_web_pages[tab_index].forward()
def refreshButton(self):
tab_index = self.tab_bar.currentIndex()
self.list_of_web_pages[tab_index].reload()
def homeButton(self):
tab_index = self.tab_bar.currentIndex()
self.list_of_web_pages[tab_index].setUrl(QUrl("https://google.com"))
def stopButton(self):
tab_index = self.tab_bar.currentIndex()
self.list_of_web_pages[tab_index].stop()
self.tab_bar.removeTab(tab_index)
def sizeMainWindow(self):
"""Используйте QApplication.primaryScreen() для
430 ГЛАВА 16 ДОПОЛНИТЕЛЬНЫЕ ПРОЕКТЫ
доступа к информации об экране и использовать ее
для определения размера окна приложения
при запуске нового приложения."""
desktop = QApplication.primaryScreen()
size = desktop.availableGeometry()
screen_width = size.width()
screen_height = size.height()
self.setGeometry(0, 0, screen_width, screen_height)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(style_sheet)
window = WebBrowser()
app.exec()
Создание веб-вида
Метод setUpWebView() создает экземпляр класса QWebEngineView, web_view, и
устанавливает URL веб-представления для отображения веб-страницы Google:
web_view.setUrl(QUrl("https://google.com"))
Для создания базового экземпляра веб-представления в приложении достаточно
создать объект QWebEngineView, использовать метод load() для загрузки веб-
страницы в виджет веб-представления и вызвать show(). В следующем коде показан
процесс настройки простого виджета веб-представления.
web_view = QWebEngineView()
web_view.load(QUrl("https://google.com"))
web_view.show()
После загрузки веб-страницы сигнал urlChanged, подключенный к функции
updateUrl(), изменяет URL, отображаемый в адресной_строке. С помощью сигнала
loadFinished() мы можем сообщить текущей вкладке об обновлении ее заголовка с
помощью слота updateTabTitle() и вернуть виджет web_view.
Далее создадим макет для размещения виджета web view, добавим URL текущей
вкладки и объект web_page в списки list_of_urls и list_of_web_pages, а также установим
макет для страницы текущей вкладки. Объект web_page - это виджет web_view,
class MainWindow(QWidget):
def __init__(self):
434 ГЛАВА 16 ДОПОЛНИТЕЛЬНЫЕ ПРОЕКТЫ
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Настройка графического интерфейса приложения."""
self.setMinimumSize(300, 200)
self.setWindowTitle("Трехпозиционный QComboBox")
self.setUpMainWindow()
self.show()
def setUpMainWindow(self):
"""Создание и расположение виджетов в главном окне."""
self.tristate_cb = QCheckBox("Выберите все начинки")
self.tristate_cb.stateChanged.connect(self.updateTristateCb)
gb_v_box = QVBoxLayout()
gb_v_box.addWidget(self.tristate_cb)
gb_v_box.addWidget(topping1_cb)
gb_v_box.addWidget(topping2_cb)
gb_v_box.addWidget(topping3_cb)
gb_v_box.addStretch()
main_v_box = QVBoxLayout()
main_v_box.addWidget(group_box)
self.setLayout(main_v_box)
ГЛАВА 16 ДОПОЛНИТЕЛЬНЫЕ ПРОЕКТЫ 435
def updateTristateCb(self, state):
"""Используйте QCheckBox для установки или снятия всех флажков."""
for button in self.button_group.buttons():
if state == 2: # Qt.CheckState.Checked
button.setChecked(True)
elif state == 0: # Qt.CheckState.Unchecked
button.setChecked(False)
if all(button_states):
self.tristate_cb.setCheckState(Qt.CheckState.Checked)
self.tristate_cb.setTristate(False)
elif any(button_states) == False:
self.tristate_cb.setCheckState(Qt.CheckState.Unchecked)
self.tristate_cb.setTristate(False)
else:
self.tristate_cb.setCheckState(Qt.CheckState.PartiallyChecked)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
Резюме
В этой главе вы познакомились с различными GUI-приложениями, которые
создают структуру для более крупных проектов, таких как графический интерфейс
камеры или графический интерфейс веб-браузера. В других проектах были
представлены компоненты, которые можно включить в другие программы,
например, графический интерфейс просмотра каталогов, графический интерфейс
часов и графический интерфейс календаря. На примере графического интерфейса
"Виселица" мы продемонстрировали, как понимание QPainter полезно для рисования
и настройки внешнего вида виджетов. Наконец, трехпозиционные виджеты
QComboBox полезны для управления дочерними элементами.
На протяжении всей книги мы рассматривали различные темы проектирования
графических пользовательских интерфейсов с использованием PyQt6 и Python -
различные типы виджетов, классы и макеты. Мы рассмотрели, как стилизовать
интерфейсы, как добавлять меню и как упростить приложение с помощью Qt Designer.
Также были рассмотрены такие продвинутые темы, как работа с буфером обмена,
SQL и многопоточные приложения.
В Приложении вы найдете более подробную информацию о некоторых классах
PyQt6, используемых в этой книге, а также о некоторых других классах, которые не
были включены в предыдущие главы.
Ваши отзывы и вопросы всегда приветствуются. Большое спасибо за то, что
присоединились ко мне в этом путешествии и позволили поделиться с вами своими
знаниями о разработке графических интерфейсов.
Справочное
руководство по PyQt6
PyQt - это связка языка Python для прикладного фреймворка Qt, поддерживаемого
компанией Riverbank Computing Limited. Связка - это интерфейс прикладного
программирования (API), который предоставляет код, позволяющий языку
программирования использовать другие библиотеки, не являющиеся родными для
этого языка. Qt - это набор библиотек C++ и средств разработки, обеспечивающих
доступ к сетевым технологиям, потокам, базам данных SQL, OpenGL и другим
графическим инструментам, XML, разработке графических интерфейсов и ряду
других возможностей. Данная глава посвящена только PyQt6, однако многие
концепции и методы доступны и в PyQt5.
Приложение содержит справочник по некоторым инструментам, модулям и
классам, изученным в этой книге, в том числе
• Обзор модулей и классов PyQt
• Обзор таблиц стилей Qt
• Обсуждение пространства имен Qt
Дополнительную информацию о компании Riverbank Computing Limited и PyQt6
можно найти на сайте https://riverbankcomputing.com/software/pyqt/intro.
QApplication
QApplication отвечает за инициализацию и завершение работы виджетов в
графических интерфейсах пользователя. Если вы создаете приложения на основе
QWidget, то перед созданием любых других объектов, связанных с графическим
интерфейсом, вам необходимо создать экземпляр QApplication.
Среди обязанностей класса QApplication - инициализация приложения в
соответствии с настройками рабочего стола пользователя, обработка событий,
определение стиля графического интерфейса, работа с буфером обмена и учет всех
окон приложения.
Если вы создаете приложение, которое не нуждается в графическом интерфейсе
и может запускаться через командную строку, то вместо него следует использовать
QCoreApplication.
Метод Описание
addAction(action) Добавляет действие к виджету
close() Закрытие виджета
height() Получение высоты виджета
width() Получение ширины виджета
move(x, y) Устанавливает местоположение виджета в (x, y)
rect() Получает геометрию виджета за вычетом рамки
setDisabled(bool) Если значение True, то виджет отключен
setEnabled(bool) Если значение True, то виджет включен
setFont(font) Устанавливает шрифт текста виджета (если виджет может
отображать текст)
setLayout(layout) Устанавливает менеджер компоновки для виджета
setGeometry(x, y, Устанавливает местоположение виджета, (x, y), и его размер,
width, height) ширину и высоту.
setStyleSheet(styleSheet) Устанавливает таблицу стилей для виджета
setToolTip(text) Устанавливает подсказку виджета
repaint() Перекрашивает виджет немедленно, вызывая функцию
paintEvent().
showFullScreen() Отображение виджета в полноэкранном режиме
update() Обновляет виджет, планируя событие paint в основном цикле
событий
Метод Описание
addDockWidget(area, Создает виджет док-станции в главном окне в указанной
dockwidget) области
addToolBar(area, Создает панель инструментов для главного окна. Также
toolbar) может быть указана область
menuBar() Возвращает строку меню главного окна
setStatusBar(statusbar) Создает строку состояния для главного окна
setCentralWidget(widget) Устанавливает центральный виджет окна
setWindowIcon(icon) Устанавливает значок окна
setWindowTitle(text) Устанавливает заголовок окна. Этот метод унаследован от
QWidget
Метод Описание
accept() Скрывает модальный диалог и возвращает True, принимая действия,
указанные диалогом
reject() Скрывает модальный диалог и возвращает False, отклоняя действия,
указанные диалогом
open() Диалог отображается в виде модального диалога и блокирует дальнейшие
действия пользователя до тех пор, пока диалог не будет закрыт
show() Диалог является бесмодельным, возвращая управление пользователю
немедленно
Метод Описание
Ok Определяет кнопку OK с ролью AcceptRole
Open Определяет кнопку Open с параметром AcceptRole
Save Определяет кнопку Save с параметром AcceptRole
Cancel Определяет кнопку Cancel с параметром RejectRole
Close Определяет кнопку Close с ролью RejectRole
Yes Определяет кнопку Yes с ролью YesRole
No Определяет кнопку No с параметром NoRole
Reset Определяет кнопку Reset с ролью ResetRole
Метод Описание
begin(device) Начало окраски на устройстве окраски
end() Завершает рисование. Ресурсы, использованные во время
рисования, освобождаются
save() Сохраняет текущее состояние рисовальщика. За функцией
save() должна следовать функция restore(), которая
возвращает текущее состояние рисовальщика.
drawArc(QRectF, Рисует дугу, заданную прямоугольником QRectF, startAngle,
startAngle, spanAngle) и spanAngle
drawChord(QRectF, Рисует хорду, заданную прямоугольником QRectF, startAngle,
startAngle, spanAngle) и spanAngle
drawEllipse(QPointF, x_ Рисует эллипс с центром QPointF, радиусом x_rad и y_rad
rad, y_rad)
drawLine(x1, y1, x2, y2) Проводит прямую из точки (x1, y1) в точку (x2, y2)
drawPath(path) Рисует контур, заданный QPainterPath path
drawPie(QRectF, Рисует кружок, заданный прямоугольником QRectF,
startAngle, spanAngle) startAngle и spanAngle
drawPixmap(x, y, pixmap) Рисует пиксельное изображение в точке (x, y)
drawPoint(x, y) Строит точку в точке (x, y)
drawRect(x, y, width, Рисует прямоугольник в точке (x, y) с шириной и высотой
height)
drawRoundedRect(QRectF, Рисует прямоугольник со скругленными углами, заданный
x_rad, y_rad) QRectF, с радиусами x_rad и y_rad
drawText(QPointF, text) Рисует текст в точке QPointF
fillRect(QRectF, brush) Заливает прямоугольник QRectF цветом кисти
rotate(angle) Поворачивает систему координат по часовой стрелке на
угол (в градусах)
setBrush(brush) Устанавливает кисть художника
setPen(pen) Устанавливает перо художника
setFont() Устанавливает шрифт художника
Метод Описание
setIcon(icon) Устанавливает значок виджета
setText(text) Устанавливает текст виджета
setAutoExclusive(bool) Включение автоисключения для кнопок в группе
setCheckable(bool) Устанавливает, является ли кнопка кнопкой переключения
или нет
setChecked(bool) Устанавливает, отмечена ли кнопка флажком или нет
isChecked() Указывает, отмечена ли кнопка флажком или нет (если
setCheckable() равен True)
text() Получает текст кнопки
Виджеты ввода
В PyQt имеется достаточно много виджетов, предназначенных для получения
информации от пользователя. Эти виджеты предоставляют различные способы
сбора информации, такие как ввод текста или выбор значений с помощью ползунков,
комбобоксов и спинбоксов.
Комбобоксы
Класс QComboBox представляет пользователю список вариантов выбора в
виде компактного выпадающего меню. Некоторые методы класса приведены в
Таблице A-11. Когда с комбобоксом не взаимодействуют, все элементы, кроме
текущего выбранного, скрываются от глаз. Виджет QFontComboBox - это еще один
тип комбобокса, который наследует QComboBox и используется для выбора семейства
шрифтов.
Метод Описание
addItem(text) Добавляет элемент в список с текстом
addItems(list(text)) Добавляет список элементов в комбобокс
currentIndex() Получает индекс текущего выбранного элемента
currentText() Получает текст текущего выделенного элемента
insertItem(index, text) Вставляет текст в комбобокс по заданному индексу
setItemText(index, text) Устанавливает текст для элемента с заданным индексом
removeItem(index) Удаляет элемент с заданным индексом
clear() Очищает все элементы из комбобокса
setEditable(bool) Если значение True, то содержимое комбобокса можно
редактировать
Сигнал Описание
currentIndexChanged(index) Выдается, если текущий элемент в комбобоксе изменился
currentTextChanged(text) Сигнал, выдаваемый при изменении текущего элемента в
комбобоксе. Возвращает текст
activated(index) Выдается только при взаимодействии пользователя с
элементом
highlighted(index) Выдается при выделении элемента в комбобоксе
textActivated(text) Сигнал, выдаваемый при выборе пользователем элемента
currentFontChanged(font) Выдается при изменении текущего шрифта
Метод Описание
text() Получает текущий текст в строке редактирования
setAlignment(alignment) Устанавливает выравнивание текста, отображаемого в
виджете
setPlaceholderText(text) Отображение текста-заместителя при пустой строке
редактирования
setEchoMode(mode) Параметр mode описывает способ отображения содержимого
строки. Установите для параметра mode значение QLineEdit.
Пароль для маскировки символов
setMaxLength(int) Устанавливает максимальную длину символов
setTextMargins(left, top, Устанавливает текстовые поля для текста, отображаемого в
right, bottom) режиме редактирования строки
setDragEnabled(bool) Если значение True, то перетаскивание выделенного текста в
строке редактирования разрешено
Сигнал Описание
returnPressed() Выдается при нажатии клавиши Enter. Если установлен
валидатор, то сигнал подается только в том случае, если
текст принят
textChanged(text) Сигнал выдается при изменении текста
Метод Описание
find(text, flags) Находит следующее вхождение текста в текстовом редакторе
print(printer) Печать документа текстового редактора на принтер
setPlaceHolderText(text) Устанавливает текст-заполнитель для редактирования текста
setReadOnly(bool) Если значение True, то для редактирования текста
устанавливается режим "только для чтения".
toPlainText() Возвращает текст редактируемого текста в виде обычного
текста
zoomIn(range) Увеличение масштаба текста
zoomOut(range) Уменьшение масштаба текста
Сигнал Описание
selectionChanged() Сигнал, выдаваемый при изменении текста, выделенного
в текстовом редакторе
textChanged() Выдается при изменении содержимого редактируемого
текста
Метод Описание
setValue(val) Устанавливает значение val для спин-бокса
setMinimum(val) Устанавливает минимальное значение val для спин-бокса
setMaximum(val) Устанавливает максимальное значение val для спин-бокса
setPrefix(str) Добавляет префикс к началу отображаемого значения
setSuffix(str) Добавляет суффикс к концу отображаемого значения
setRange(min, max) Устанавливает минимальное и максимальное значения
диапазона
setSingleStep(val) При нажатии клавиш со стрелками значение спин-бокса
увеличивается/уменьшается на величину val
Сигнал Описание
valueChanged(val) Сигнал, выдаваемый при изменении значения.
Предоставляет новое значение val
textChanged(text) Сигнал, выдаваемый при изменении значения.
Предоставляет новое значение текста
Виджеты ползунка
Приведенные ниже виджеты отличаются друг от друга внешне, но на самом деле
весьма схожи по функциональности. Виджеты, наследующие класс QAbstractSlider,
используются для выбора целочисленных значений в ограниченном диапазоне.
Классы, наследующие QAbstractSlider, включают следующие:
1. QDial - предоставляет контроллер округлого диапазона для выбора или
настройки значений. Пример QDial показан на рисунке A-1.
2. QScrollBar - предоставляет горизонтальные или вертикальные полосы
прокрутки, которые пользователь может использовать для доступа к
другим частям документа, которые шире, чем виджет, используемый для его
отображения.
3. QSlider - создает классические виджеты горизонтальных и вертикальных
ползунков для управления значениями в заданном диапазоне.
Метод Описание
value() Удерживает текущее значение ползунка
setMinimum(int) Устанавливает минимальное значение ползунка
setMaximum(int) Устанавливает максимальное значение ползунка
setOrientation(orientation) Устанавливает ориентацию, горизонтальную или
вертикальную (обеспечивается перечислением
Qt.Orientation).
setSingleStep(int) При нажатии клавиш со стрелками значение ползунка
увеличивается/уменьшается на int
setTracking(bool) Если значение True, то положение ползунка можно
отслеживать
setSliderPosition(int) Устанавливает текущее положение ползунка
setValue(int) Устанавливает текущее положение ползунка в int. Если
отслеживание включено, то это значение совпадает со
значением геттера value()
Сигнал Описание
valueChanged(val) Сигнал, выдаваемый при изменении значения.
Предоставляет новое значение val
rangeChanged(min, max) Сигнал, выдаваемый при изменении диапазона с новыми
минимальным и максимальным значениями
sliderMoved(val) Выдается при нажатии на ползунок и его перемещении
sliderPressed() Выдается при нажатии на ползунок
sliderReleased() Выдается при отпускании ползунка
QLabel
QLabel - это универсальный виджет. Несмотря на то, что ярлык не предоставляет
никаких функций взаимодействия с пользователем, QLabel может отображать
обычный или насыщенный текст, пиксмапы и даже GIF-файлы. Для настройки
внешнего вида ярлыков существует ряд методов. В Таблице A-21 перечислены
некоторые из них.
Таблица A-21. Методы выбора из QLabel
Метод Описание
setPicture(picture) Устанавливает содержимое метки в виде изображения
setPixmap(pixmap) Устанавливает содержимое метки в виде pixmap
setMovie(movie) Устанавливает для содержимого метки значение movie
setText(text) Устанавливает содержимое метки в виде текста
setAlignment(alignment) Устанавливает выравнивание содержимого метки
setIndent(int) Устанавливает количество пикселей, на которое отступает
текст метки
setMargin(int) Устанавливает поля метки
Метод Описание
value() Получает текущее значение индикатора прогресса
setMinimum(int) Устанавливает минимальное значение индикатора
прогресса
setMaximum(int) Устанавливает максимальное значение индикатора
прогресса
setRange(min, max) Устанавливает минимальное и максимальное значения
setOrientation(orientation) Устанавливает ориентацию, горизонтальную или
вертикальную (обеспечивается перечислением
Qt.Orientation).
setTextVisible(bool) Если значение True, то отображается текущий процент
выполнения
QGraphicsView
Класс QGraphicsView предоставляет виджет для отображения содержимого сцены
QGraphicsScene. Как одна из частей Qt Graphics View Framework, класс QGraphicsView
отвечает за отображение элементов графической сцены в прокручиваемом окне. В
обязанности объекта QGraphicsScene входит управление элементами сцены. Объект
QGraphicsItem (или один из его подклассов) предоставляет элементы для сцены.
Если вы хотите узнать больше о Graphics View Framework, посетите сайт
https://doc.qt.io/qt-6/graphicsview.html.
QLCDNumber
Виджет QLCDNumber отображает числа на семисегментном ЖК-дисплее.
Его пример показан на рис. A-1. На дисплее могут отображаться десятичные,
шестнадцатеричные, восьмеричные и двоичные числа. ЖК-дисплей может
Метод Описание
value() Получение отображаемого на ЖК-дисплее значения
intValue() Получает отображаемое значение, округленное до
ближайшего целочисленного значения
display(val) Отображает на дисплее значение val. val может быть
плавающей точкой, целым числом или строкой.
setMode(mode) Устанавливает режим отображения на ЖК-дисплее
значений Bin, Oct, Dec или Hex
setSmallDecimalPoint(bool) Если True, то десятичная дробь выводится между двумя
цифрами
Представления элементов
Следующие классы представлений модели предоставляют средства для
отображения элементов в списках, таблицах или древовидных структурах. Они
должны использоваться вместе с классом модели как часть фреймворка Qt
Model/View.
1. QListView - Предоставляет представление списка и пиктограмм для отображения
элементов из модели.
2. QTableView - Предоставляет таблицу для отображения элементов из модели.
3. QTreeView - Предоставляет иерархическую древовидную архитектуру для
отображения элементов из модели.
Все эти классы наследуются от класса QAbstractItemView. С помощью сигналов
и слотов представления элементов, созданные на основе QAbstractItemView, могут
взаимодействовать с моделями, использующими QAbstractItemModel. Каждое из
представлений элементов имеет свои методы для работы со строками, столбцами,
заголовками и элементами. Для управления элементами представления используют
индексы. Некоторые методы для QAbstractItemView приведены в табл. A-24.
Метод Описание
clearSelection() Все выбранные элементы снимаются с выбора
selectAll() Выделяет все элементы в представлении
setCurrentIndex(index) Устанавливает элемент с индексом в качестве текущего
элемента
update(index) Обновление области по заданному индексу
setAlternatingRowColors(bool) Если True, то фон рисуется чередующимися цветами
setAcceptDrops(bool) Если значение True, то элементы могут быть
перетащены в представление
setDragEnabled(bool) Если значение True, элементы можно перетаскивать по
экрану
setIconSize(size) Устанавливает размер значков
setItemDelegate(delegate) Устанавливает делегат элемента для фреймворка
Model/View представления
setModel(model) Устанавливает модель для представления
Сигнал Описание
activated(index) Сигнал, выдаваемый при активации пользователем элемента с
индексом
clicked(index) Выдается при нажатии левой кнопки мыши на элемент в
представлении (указанный индексом)
doubleClicked(index) Выдается при двойном нажатии кнопки мыши на элементе в
представлении (указанном индексом)
entered(index) Сигнал, выдаваемый при попадании курсора мыши в элемент по
индексу. Включите слежение за мышью, чтобы использовать
pressed(index) Сигнал, выдаваемый при нажатии кнопки мыши на элементе с
индексом
Контейнеры с фреймами
Виджеты QFrame могут заключать в себе и группировать виджеты, а также
выполнять роль вместилищ в окнах. С помощью фреймов можно задавать внешний
вид других виджетов: приподнятый, утопленный или плоский. Класс QFrame
используется в качестве базового класса для нескольких других классов-контейнеров,
включая QToolBox и QStackedWidget. В Таблице A-26 перечислены некоторые методы
класса QFrame.
Таблица A-26. Выбор методов для QFrame
Метод Описание
setFrameRect(QRect) Задает прямоугольник, в котором рисуется рамка
setFrameShadow(shadow) Устанавливает тень рамки, используя такие флаги,
как Plain(плоский), Raised(приподнятый) или
Sunken(утопленный)
setFrameShape(shape) Устанавливает форму рамки, используя такие флаги, как
Box, Panel, HLine и VLine
setLineWidth(int) Устанавливает ширину линии, рисуемой вокруг рамки
Метод Описание
addItem(widget, text) Добавляет виджет в новую вкладку в нижней части
панели инструментов
insertItem(index, widget, text) Вставляет виджет в новую вкладку по заданному индексу
indexOf(widget) Возвращает индекс указанного виджета
setCurrentIndex(index) Устанавливает индекс для нового элемента
setCurrentWidget(widget) Делает виджет текущим виджетом, отображаемым на
панели инструментов
QScrollArea
Область прокрутки может быть добавлена к дочернему виджету для отображения
содержимого внутри фрейма. При изменении размера фрейма будут появляться
полосы прокрутки, что позволит пользователю по-прежнему просматривать весь
дочерний виджет. Несколько методов класса перечислены в Таблице A-28. То, как
появляются полосы прокрутки, можно контролировать с помощью политик размера
класса QAbstractScrollArea.
Метод Описание
ensureVisible(x, y, Убеждается, что указанная координата (x, y) с полями
xmargin, ymargin) xmargin и ymargin остается видимой в области просмотра
setAlignment(alignment) Устанавливает выравнивание виджета области
прокрутки
setWidget(widget) Устанавливает виджет области прокрутки
setWidgetResizable(bool) Если False, то область прокрутки подчиняется размеру
дочернего виджета
QMdiArea
Для многооконных графических интерфейсов (MDI) класс QMdiArea
предоставляет контейнер для отображения нескольких окон внутри одного окна
приложения. Под-окна являются экземплярами класса QMdiSubWindow и могут
быть расположены в виде плиток или каскадов. Под-окна могут работать совместно,
передавая информацию туда и обратно. Для удобства переключения между окнами
в виджет MDI-области может быть добавлено контекстное меню. Некоторые методы
для виджета MDI приведены в Таблице A-29.
Таблица A-29. Список избранных методов QMdiArea
Метод Описание
addSubWindow(widget) Добавляет виджет в качестве нового под-окна в область
MDI
activeSubWindow() Возвращает активное под-окно
cascadeSubWindow() Упорядочивает под-окна по каскадному принципу
tileSubWindows() Располагает под-окна в виде черепицы
removeSubWindow(widget) Удаляет виджет из области MDI, где виджет является
под-окном
setBackground(background) Устанавливает фон QBrush для области MDI
subWindowList(subwindows) Возвращает список под-окна
setTabsClosable(bool) Если значение True, то кнопки закрытия размещаются на
каждой вкладке в табличном представлении
setTabsMovable() Если значение True, то вкладки в представлении с
вкладками будут подвижными
Таблицы стилей Qt
Большой справочник по виджетам и свойствам, которыми
можно управлять с помощью таблиц стилей Qt, находится на сайте
https://doc.qt.io/qt-6/stylesheet-reference.html.
Таблицы стилей позволяют настраивать многие аспекты и поведение виджетов.
В табл. A-30 перечислены многие свойства, которые можно изменять. Виджеты
поддерживают только определенные свойства, поэтому, если вы не уверены в том,
какие свойства можно изменять, обязательно ознакомьтесь с документацией Qt.
Метод Описание
alternate-background-color Альтернативный цвет фона для виджетов QAbstractItemView
QListView{
alternate-background-color: blue;
background: grey
}
Background Сокращенное обозначение для установки фона
background-color Цвет фона, используемый для виджета
QPushButton{
background-color: #49DE1F
}
background-image Фоновое изображение, используемое для виджета
QFrame{
background-image: url(images/black_cat.png)
}
Border Сокращение для установки границы виджета
QComboBox{
border: 2px solid magenta
}
border-top, border-right, Сокращение для указания сторон границы виджета
border-bottom, border-left
border-color Цвет для всех сторон границы виджета
border-image Указание изображения для заполнения границы
border-radius Радиус углов границы
QTextEdit{
border-width: 1px;
border-style: groove;
border-radius: 3px
}
(продолжение следует)
Метод Описание
border-style Определяет стиль для всех краёв границы
border-width Задает ширину для всех краёв границы
color Цвет, используемый для отрисовки текста
font Сокращение для определения шрифта виджета
QRadioButton{
font: bold italic large “Helvetica”
}
font-family, font-size, fontstyle, Другие свойства, используемые для индивидуальной
font-weight настройки характеристик шрифта
height, width Высота и ширина виджета
icon-size Ширина и высота значка виджета
image Изображение, рисуемое на виджете. Можно
использовать url или svg
margin Задает поля виджета. Как и в случае с border, можно
задать конкретные стороны
max-height, max-width Максимальная высота или ширина виджета
min-height, min-width Минимальная высота или ширина виджета
outline Контур рисует границу виджета. Можно также задать
цвет, стиль и радиус
padding Задает отступы виджета. Как и в случае с border, можно
задать конкретные стороны
selection-color Цвет переднего плана выделенных элементов в текст
spacing Устанавливает внутренний интервал в виджете
text-align Определяет выравнивание текста и значков внутри
виджета
QPushButton{
text-align: right
}
Резюме
В ходе работы над этой книгой вы уже использовали многие из основополагающих
классов PyQt для построения графических интерфейсов пользователя. В Приложении
приведены ссылки, которые помогут вам проанализировать программы, приведенные
в этой книге, и узнать больше о виджетах, макетах и таблицах стилей, используемых
для проектирования и создания приложений PyQt. Содержащиеся здесь классы
и методы служат руководством к действию, позволяющим задуматься о способах
создания и совершенствования собственных программ.
Просто не хватит места, чтобы включить в это руководство все классы, методы и
сигналы. По мере выполнения примеров используйте данное приложение как ресурс,
помогающий узнать больше о возможностях PyQt. Если вы не нашли здесь ответа на
свой вопрос, перейдите по ссылкам, найдите его в Интернете или напишите мне по
электронной почте (автору книги, а не переводчику :))).
Счастливого кодирования!