ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов
Скачать 7.92 Mb.
|
Если второй параметр не указан, функция возвращает ссылку на объект формы. С помощью этой ссылки можно получить доступ к компонентам формы и, например, назначить об- работчики сигналов (листинг 17.4). Имена компонентов задаются в программе Qt Designer в свойстве objectName. Листинг 17.4. Использование функции loadUi(). Вариант 1 # -*- coding: utf-8 -*- from PyQt5 import QtWidgets, uic import sys app = QtWidgets.QApplication(sys.argv) window = uic.loadUi("MyForm.ui") window.btnQuit.clicked.connect(app.quit) window.show() sys.exit(app.exec_()) Если во втором параметре указать ссылку на экземпляр класса, то все компоненты формы будут доступны через указатель self (листинг 17.5). Листинг 17.5. Использование функции loadUi(). Вариант 2 # -*- coding: utf-8 -*- from PyQt5 import QtWidgets, uic class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) uic.loadUi("MyForm.ui", self) self.btnQuit.clicked.connect(QtWidgets.qApp.quit) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() 338 Часть II. Библиотека PyQt 5 window.show() sys.exit(app.exec_()) Загрузить UI-файл позволяет также функция loadUiType() — она возвращает кортеж из двух элементов: ссылки на класс формы и ссылки на базовый класс. Так как функция воз- вращает ссылку на класс, а не на экземпляр класса, мы можем создать множество экземпля- ров класса. После создания экземпляра класса формы необходимо вызвать метод setupUi() и передать ему указатель self (листинг 17.6). Листинг 17.6. Использование функции loadUiType(). Вариант 1 # -*- coding: utf-8 -*- from PyQt5 import QtWidgets, uic class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) Form, Base = uic.loadUiType("MyForm.ui") self.ui = Form() self.ui.setupUi(self) self.ui.btnQuit.clicked.connect(QtWidgets.qApp.quit) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_()) Загрузить UI-файл можно и вне класса, после чего указать класс формы во втором парамет- ре в списке наследования, — в этом случае наш класс унаследует все методы класса формы (листинг 17.7). Листинг 17.7. Использование функции loadUiType(). Вариант 2 from PyQt5 import QtWidgets, uic Form, Base = uic.loadUiType("MyForm.ui") class MyWindow(QtWidgets.QWidget, Form): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.setupUi(self) self.btnQuit.clicked.connect(QtWidgets.qApp.quit) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_()) Глава 17. Знакомство с PyQt 5 339 17.5.3. Преобразование UI-файла в PY-файл Вместо подключения UI-файла можно сгенерировать на его основе программный код на языке Python. Для этого служит утилита pyuic5 , чей исполняемый файл располагается в каталоге <путь, по которому установлен Python>\Scripts . Запустим командную строку и перейдем в каталог, в котором находится UI-файл. Для генерации Python-программы выпол- ним команду: pyuic5 MyForm.ui -o ui_MyForm.py В результате будет создан файл ui_MyForm.py , который мы уже можем подключить с по- мощью инструкции import . Внутри файла находится класс Ui_MyForm с методами setupUi() и retranslateUi() . При использовании процедурного стиля программирования следует соз- дать экземпляр класса формы, а затем вызвать метод setupUi() и передать ему ссылку на экземпляр окна (листинг 17.8). Листинг 17.8. Использование класса формы. Вариант 1 # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys, ui_MyForm app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() ui = ui_MyForm.Ui_MyForm() ui.setupUi(window) ui.btnQuit.clicked.connect(QtWidgets.qApp.quit) window.show() sys.exit(app.exec_()) При использовании ООП-стиля программирования следует создать экземпляр класса фор- мы, а затем вызвать метод setupUi() и передать ему указатель self (листинг 17.9). Листинг 17.9. Использование класса формы. Вариант 2 # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import ui_MyForm class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.ui = ui_MyForm.Ui_MyForm() self.ui.setupUi(self) self.ui.btnQuit.clicked.connect(QtWidgets.qApp.quit) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_()) 340 Часть II. Библиотека PyQt 5 Класс формы можно указать во втором параметре в списке наследования — в этом случае он унаследует все методы класса формы (листинг 17.10). Листинг 17.10. Использование класса формы. Вариант 3 from PyQt5 import QtWidgets import ui_MyForm class MyWindow(QtWidgets.QWidget, ui_MyForm.Ui_MyForm): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.setupUi(self) self.btnQuit.clicked.connect(QtWidgets.qApp.quit) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_()) Как видите, в PyQt можно создавать формы, размещать компоненты с помощью мыши, а затем непосредственно подключать UI-файлы в программе или преобразовывать их в Python-код с помощью утилиты pyuic5 , — все это очень удобно. Тем не менее, чтобы пол- ностью овладеть программированием на PyQt, необходимо уметь создавать код вручную. Поэтому в оставшейся части книги мы больше не станем задействовать программу Qt Designer. 17.6. Модули PyQt 5 В состав библиотеки PyQt 5 входит множество модулей, объединенных в пакет PyQt5 . Упо- мянем самые важные из них: QtCore — содержит классы, не связанные с реализацией графического интерфейса. От этого модуля зависят все остальные модули; QtGui — содержит классы, реализующие низкоуровневую работу с оконными элемента- ми, обработку сигналов, вывод двухмерной графики и текста и др.; QtWidgets — содержит классы, реализующие компоненты графического интерфейса: окна, диалоговые окна, надписи, кнопки, текстовые поля и др.; QtWebEngineCore — включает низкоуровневые классы для отображения веб-страниц; QtWebEngineWidgets — реализует высокоуровневые компоненты графического интер- фейса, предназначенные для вывода веб-страниц и использующие модуль QtWebEngineCore ; П РИМЕЧАНИЕ Ранее для вывода веб-страниц использовались модули QtWebKit и QtWebKitWidgets. Однако в версии PyQt 5.5 они были объявлены нерекомендованными для использования, а в версии 5.6 удалены. QtMultimedia — включает низкоуровневые классы для работы с мультимедиа; Глава 17. Знакомство с PyQt 5 341 QtMultimediaWidgets — реализует высокоуровневые компоненты графического интер- фейса с мультимедиа, использующие модуль QtMultimedia ; QtPrintSupport — содержит классы, обеспечивающие поддержку печати и предвари- тельного просмотра документов; QtSql — включает поддержку работы с базами данных, а также реализацию SQLite; QtSvg — позволяет работать с векторной графикой (SVG); QtNetwork — содержит классы, предназначенные для работы с сетью; QtXml и QtXmlPatterns — предназначены для обработки XML; QtHelp — содержат инструменты для создания интерактивных справочных систем; QtWinExtras — включает поддержку специфических возможностей Microsoft Windows; Qt — включает классы из всех модулей сразу. П РИМЕЧАНИЕ Модуль QtOpenGL, обеспечивающий поддержку OpenGL, в версии PyQt 5.9 был объявлен нерекомендованным к использованию и будет удален в одной из последующих версий этой библиотеки. Его функциональность перенесена в модуль QtGui. Для подключения модулей используется следующий синтаксис: from PyQt5 import <Названия модулей через запятую> Так, например, можно подключить модули QtCore и QtWidgets : from PyQt5 import QtCore, QtWidgets В этой книге мы не станем рассматривать все упомянутые модули — чтобы получить ин- формацию по не рассмотренным здесь модулям, обращайтесь к соответствующей докумен- тации. 17.7. Типы данных в PyQt Библиотека PyQt является надстройкой над написанной на языке C++ библиотекой Qt. По- следняя содержит множество классов, которые расширяют стандартные типы данных языка C++ и реализуют динамические массивы, ассоциативные массивы, множества и др. Все эти классы очень помогают при программировании на языке C++, но для языка Python они не представляют особого интереса, т. к. весь этот функционал содержат стандартные типы данных. Тем не менее, при чтении документации вы столкнетесь с ними, поэтому сейчас мы кратко рассмотрим основные типы: QByteArray — массив байтов. Преобразуется в тип bytes : >>> from PyQt5 import QtCore >>> arr = QtCore.QByteArray(bytes("str", "cp1251")) >>> arr PyQt5.QtCore.QByteArray(b'str') >>> bytes(arr) b'str' QVariant — может хранить данные любого типа. Создать экземпляр этого класса можно вызовом конструктора, передав ему нужное значение. А чтобы преобразовать данные, хранящиеся в экземпляре класса QVariant , в тип данных Python, нужно вызвать метод value() : 342 Часть II. Библиотека PyQt 5 >>> from PyQt5 import QtCore >>> n = QtCore.QVariant(10) >>> n >>> n.value() 10 Также можно создать «пустой» экземпляр класса QVariant , вызвав конструктор без па- раметров: >>> QtCore.QVariant() # Пустой объект Если какой-либо метод ожидает данные типа QVariant , ему можно передать данные лю- бого типа. Еще этот класс поддерживает метод typeName() , возвращающий наименование типа хра- нящихся в экземпляре данных: >>> from PyQt5 import QtCore >>> n = QtCore.QVariant(10) >>> n.typeName() 'int' Кроме того, PyQt 5 поддерживает классы QDate (значение даты), QTime (значение времени), QDateTime (значение даты и времени), QTextStream (текстовый поток), QUrl (URL-адрес) и некоторые другие. 17.8. Управление основным циклом приложения Для взаимодействия с системой и обработки возникающих сигналов предназначен основной цикл приложения. После вызова метода exec_() программа переходит в бесконечный цикл. Инструкции, расположенные после вызова этого метода, будут выполнены только после завершения работы всего приложения. Цикл автоматически прерывается после закры- тия последнего открытого окна приложения. С помощью статического метода setQuitOnLastWindowClosed() класса QApplication это поведение можно изменить. Чтобы завершить работу приложения, необходимо вызвать слот quit() или метод exit([returnCode=0]) класса QApplication . Поскольку программа находится внутри цикла, вызвать эти методы можно лишь при наступлении какого-либо события, — например, при нажатии пользователем кнопки. После возникновения любого сигнала основной цикл прерывается, и управление передается в обработчик этого сигнала. После завершения работы обработчика управление возвраща- ется основному циклу приложения. Если внутри обработчика выполняется длительная операция, программа перестает реагиро- вать на события. В качестве примера изобразим длительный процесс с помощью функции sleep() из модуля time (листинг 17.11). Листинг 17.11. Выполнение длительной операции # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys, time Глава 17. Знакомство с PyQt 5 343 def on_clicked(): time.sleep(10) # "Засыпаем" на 10 секунд app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Запустить процесс") button.resize(200, 40) button.clicked.connect(on_clicked) button.show() sys.exit(app.exec_()) В этом примере при нажатии кнопки Запустить процесс вызывается функция on_clicked() , внутри которой мы приостанавливаем выполнение программы на десять секунд и тем са- мым прерываем основной цикл. Попробуйте нажать кнопку, перекрыть окно другим окном, а затем заново его отобразить, — вам не удастся это сделать, поскольку окно перестает реа- гировать на любые события, пока не закончится выполнение процесса. Короче говоря, про- грамма просто зависнет. Длительную операцию можно разбить на несколько этапов и по завершении каждого этапа выходить в основной цикл с помощью статического метода processEvents([flags=AllEvents]) класса QCoreApplication , от которого наследуется класс QApplication . Переделаем преды- дущую программу, инсценировав с помощью цикла длительную операцию, которая выпол- няется 20 секунд (листинг 17.12). Листинг 17.12. Использование метода processEvents() # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys, time def on_clicked(): button.setDisabled(True) # Делаем кнопку неактивной for i in range(1, 21): QtWidgets.qApp.processEvents() # Запускаем оборот цикла time.sleep(1) # "Засыпаем" на 1 секунду print("step -", i) button.setDisabled(False) # Делаем кнопку активной app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Запустить процесс") button.resize(200, 40) button.clicked.connect(on_clicked) button.show() sys.exit(app.exec_()) В этом примере длительная операция разбита на одинаковые этапы, после выполнения каж- дого из которых выполняется выход в основной цикл приложения. Теперь при перекрытии окна и повторном его отображении оно будет перерисовано — таким образом, приложение по-прежнему будет взаимодействовать с системой, хотя и с некоторой задержкой. 344 Часть II. Библиотека PyQt 5 17.9. Многопоточные приложения При обработке больших объемов данных не всегда можно равномерно разбить операцию на небольшие по времени этапы, поэтому при использовании метода processEvents() возмож- ны проблемы, и тогда имеет смысл вынести длительную операцию в отдельный поток, — в этом случае операция станет выполняться параллельно с основным циклом приложения и не будет его блокировать. В одном процессе можно запустить сразу несколько независимых потоков, и если ваш ком- пьютер оснащен многоядерным процессором, потоки будут равномерно распределены по его ядрам. За счет этого можно не только избежать блокировки GUI-потока приложения, в котором выполняется обновление его интерфейса, но и значительно увеличить эффектив- ность выполнения кода. Завершение основного цикла приложения приводит к завершению работы всех потоков. 17.9.1. Класс QThread: создание потока Для создания потока в PyQt предназначен класс QThread , который объявлен в модуле QtCore и наследует класс QObject . Конструктор класса QThread имеет следующий формат: <Объект> = QThread([parent=None]) Чтобы использовать потоки, следует создать класс, который будет наследником класса QThread , и определить в нем метод run() . Код, расположенный в методе run() , будет вы- полняться в отдельном потоке, а после завершения выполнения метода run() этот поток прекратит свое существование. Затем нужно создать экземпляр класса и вызвать метод start() , который после запуска потока вызовет метод run() . Обратите внимание, что если напрямую вызвать метод run() , то код станет выполняться в основном, а не в отдельном потоке. Метод start() имеет следующий формат: start([priority=QThread.InheritPriority]) Параметр priority задает приоритет выполнения потока по отношению к другим потокам. Следует учитывать, что при наличии потока с самым высоким приоритетом поток с самым низким приоритетом в некоторых операционных системах может быть просто проигнори- рован. Приведем допустимые значения параметра (в порядке увеличения приоритета) и со- ответствующие им атрибуты из класса QThread : 0 — IdlePriority — самый низкий приоритет; 1 — LowestPriority ; 2 — LowPriority ; 3 — NormalPriority ; 4 — HighPriority ; 5 — HighestPriority ; 6 — TimeCriticalPriority — самый высокий приоритет; 7 — InheritPriority — автоматический выбор приоритета (значение по умолчанию). Задать приоритет потока также позволяет метод setPriority(<Приоритет>) . Узнать, какой приоритет использует запущенный поток, можно с помощью метода priority() После запуска потока генерируется сигнал started() , а после завершения — сигнал finished() . Назначив обработчики этим сигналам, можно контролировать статус потока из Глава 17. Знакомство с PyQt 5 345 основного цикла приложения. Если необходимо узнать текущий статус, следует воспользо- ваться методами isRunning() и isFinished() : метод isRunning() возвращает значение True , если поток выполняется, а метод isFinished() — значение True , если поток закончил выполнение. Потоки выполняются внутри одного процесса и имеют доступ ко всем глобальным пере- менным. Однако следует учитывать, что из потока нельзя изменять что-либо в GUI-потоке приложения, — например, выводить текст на надпись. Для изменения данных в GUI-потоке нужно использовать сигналы. Внутри потока у нужного сигнала вызывается метод emit() , который, собственно, и выполняет его генерацию. В параметрах метода emit() можно ука- зать данные, которые будут переданы обработчику сигнала. А внутри GUI-потока назна- чаем обработчик этого сигнала и в обработчике пишем код, который и будет обновлять ин- терфейс приложения. Рассмотрим использование класса QThread на примере (листинг 17.13). Листинг 17.13. Использование класса QThread # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyThread(QtCore.QThread): mysignal = QtCore.pyqtSignal(str) def __init__(self, parent=None): QtCore.QThread.__init__(self, parent) def run(self): for i in range(1, 21): self.sleep(3) # "Засыпаем" на 3 секунды # Передача данных из потока через сигнал self.mysignal.emit("i = %s" % i) class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None): |