Разработка десктопных приложений python

Десктопное приложение на Python: UI и сигналы

Считается, что Python не лучший выбор для десктопных приложений. Однако, когда в 2016 году я собирался переходить от разработки сайтов к программному обеспечению, Google подсказал мне, что на Python можно создавать сложные современные приложения. Например blender3d, который написан на Python.

Скриншот программы Blender

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

Мы будем использовать PyQt (произносится «Пай-Кьют‎»‎). Это фреймворк Qt, портированный с C++. Qt известен тем, что необходим C++ разработчикам. С помощью этого фреймворка сделаны blender3d, Tableau, Telegram, Anaconda Navigator, Ipython, Jupyter Notebook, VirtualBox, VLC и другие. Мы будем использовать его вместо удручающего Tkinter.

Требования

  1. Вы должны знать основы Python
  2. Вы должны знать, как устанавливать пакеты и библиотеки с помощью pip.
  3. У вас должен быть установлен Python.

Установка

Вам нужно установить только PyQt. Откройте терминал и введите команду:

Мы будем использовать PyQt версии 5.15. Дождитесь окончания установки, это займёт пару минут.

Hello, World!

Создайте папку с проектом, мы назовём его helloApp. Откройте файл main.py, лучше сделать это vscode, и введите следующий код:

import sys from PyQt5.QtGui import QGuiApplication from PyQt5.QtQml import QQmlApplicationEngine app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() engine.quit.connect(app.quit) engine.load('./UI/main.qml') sys.exit(app.exec())

Этот код вызывает QGuiApplication и QQmlApplicationEngine которые используют Qml вместо QtWidget в качестве UI слоя в Qt приложении. Затем, мы присоединяем UI функцию выхода к главной функции выхода приложения. Теперь они оба закроются одновременно, когда пользователь нажмёт выход. Затем, загружаем qml файл для Qml UI. Вызов app.exec(), запускает приложение, он находится внутри sys.exit, потому что возвращает код выхода, который передается в sys.exit.

Читайте также:  overflow

Добавьте этот код в main.qml:

import QtQuick 2.15 import QtQuick.Controls 2.15 ApplicationWindow < visible: true width: 600 height: 500 title: "HelloApp" Text < anchors.centerIn: parent text: "Hello, World" font.pixelSize: 24 >>

Этот код создает окно, делает его видимым, с указанными размерами и заголовком. Объект Text отображается в середине окна.

Теперь давайте запустим приложение:

Десктопное приложение на python
Обновление UI

Давайте немного обновим UI, добавим фоновое изображение и время:

import QtQuick 2.15 import QtQuick.Controls 2.15 ApplicationWindow < visible: true width: 400 height: 600 title: "HelloApp" Rectangle < anchors.fill: parent Image < sourceSize.width: parent.width sourceSize.height: parent.height source: "./images/playas.jpg" fillMode: Image.PreserveAspectCrop >Rectangle < anchors.fill: parent color: "transparent" Text < text: "16:38:33" font.pixelSize: 24 color: "white" >> > >

Внутри типа ApplicationWindow находится содержимое окна, тип Rectangle заполняет пространство окна. Внутри него находится тип Image и другой прозрачный Rectangle который отобразится поверх изображения.

Если сейчас запустить приложение, то текст появится в левом верхнем углу. Но нам нужен левый нижний угол, поэтому используем отступы:

Text < anchors < bottom: parent.bottom bottomMargin: 12 left: parent.left leftMargin: 12 >text: "16:38:33" font.pixelSize: 24 . >

После запуска вы увидите следующее:

Десктопное приложение на python
Показываем текущее время

Модуль gmtime позволяет использовать структуру со временем, а strftime даёт возможность преобразовать её в строку. Импортируем их:

import sys from time import strftime, gmtime

Теперь мы можем получить строку с текущим временем:

curr_time = strftime("%H:%M:%S", gmtime())

Строка «%H:%M:%S» означает, что мы получим время в 24 часовом формате, с часами минутами и секундами (подробнее о strtime).

Давайте создадим property в qml файле, для хранения времени. Мы назовём его currTime.

property string currTime: "00:00:00"

Теперь заменим текст нашей переменной:

Теперь, передадим переменную curr_time из pyhton в qml:

engine.load('./UI/main.qml') engine.rootObjects()[0].setProperty('currTime', curr_time)

Это один из способов передачи информации из Python в UI.

Запустите приложение и вы увидите текущее время.

Обновление времени

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

Чтобы использовать сигналы нам нужен подкласс QObject. Назовём его Backend.

. from PyQt5.QtCore import QObject, pyqtSignal class Backend(QObject): def __init__(self): QObject.__init__(self) . 

У нас уже имеется свойства для строки со временем curr_time, теперь создадим свойство backend типа QtObject в файле main.qml.

property string currTime: "00:00:00" property QtObject backend

Передадим данные из Python в qml:

engine.load('./UI/main.qml') back_end = Backend() engine.rootObjects()[0].setProperty('backend', back_end)

В qml файле один объект QtObject может получать несколько функций (называемых сигналами) из Python.

Создадим тип Connections и укажем backend в его target. Теперь внутри этого типа может быть столько функций, сколько нам необходимо получить в backend.

Таким образом мы свяжем qml и сигналы из Python.

Мы используем потоки, для того чтобы обеспечить своевременное обновление UI. Создадим две функции, одну для управления потоками, а вторую для выполнения действий. Хорошая практика использовать в названии одной из функций _.

. import threading from time import sleep . class Backend(QObject): def __init__(self): QObject.__init__(self) def bootUp(self): t_thread = threading.Thread(target=self._bootUp) t_thread.daemon = True t_thread.start() def _bootUp(self): while True: curr_time = strftime("%H:%M:%S", gmtime()) print(curr_time) sleep(1) . 

Создадим pyqtsignal и назовём его updated, затем вызовем его из функции updater.

. from PyQt5.QtCore import QObject, pyqtSignal . def __init__(self): QObject.__init__(self) updated = pyqtSignal(str, arguments=['updater']) def updater(self, curr_time): self.updated.emit(curr_time) . 

В этом коде updated имеет параметр arguments, который является списком, содержащим имя функции «updater». Qml будет получать данные из этой функции. В функции updater мы вызываем метод emit и передаём ему данные о времени.

Обновим qml, получив сигнал, с помощью обработчика, название которого состоит из «on» и имени сигнала:

 target: backend function onUpdated(msg)

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

curr_time = strftime("%H:%M:%S", gmtime()) self.updater(curr_time) sleep(0.1)

Функция bootUp должна быть вызвана сразу же после загрузки UI:

engine.rootObjects()[0].setProperty('backend', back_end) back_end.bootUp() sys.exit(app.exec())

Всё готово

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

flags: Qt.FramelessWindowHint | Qt.Window

Так должен выглядеть файл main.py:

import sys from time import strftime, gmtime import threading from time import sleep from PyQt5.QtGui import QGuiApplication from PyQt5.QtQml import QQmlApplicationEngine from PyQt5.QtCore import QObject, pyqtSignal class Backend(QObject): def __init__(self): QObject.__init__(self) updated = pyqtSignal(str, arguments=['updater']) def updater(self, curr_time): self.updated.emit(curr_time) def bootUp(self): t_thread = threading.Thread(target=self._bootUp) t_thread.daemon = True t_thread.start() def _bootUp(self): while True: curr_time = strftime("%H:%M:%S", gmtime()) self.updater(curr_time) sleep(0.1) app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() engine.quit.connect(app.quit) engine.load('./UI/main.qml') back_end = Backend() engine.rootObjects()[0].setProperty('backend', back_end) back_end.bootUp() sys.exit(app.exec())

Вот содержимое файла main.qml:

import QtQuick 2.15 import QtQuick.Controls 2.15 ApplicationWindow < visible: true width: 360 height: 600 x: screen.desktopAvailableWidth - width - 12 y: screen.desktopAvailableHeight - height - 48 title: "HelloApp" flags: Qt.FramelessWindowHint | Qt.Window property string currTime: "00:00:00" property QtObject backend Rectangle < anchors.fill: parent Image < sourceSize.width: parent.width sourceSize.height: parent.height source: "./images/playas.jpg" fillMode: Image.PreserveAspectFit >Text < anchors < bottom: parent.bottom bottomMargin: 12 left: parent.left leftMargin: 12 >text: currTime font.pixelSize: 48 color: "white" > > Connections < target: backend function onUpdated(msg) < currTime = msg; >> >

Сборка приложения

Для сборки десктопного приложения на Python нам понадобится pyinstaller.

Чтобы в сборку добавились все необходимые ресурсы, создадим файл spec:

Настройки файла spec

Параметр datas можно использовать для того, чтобы включить файл в приложение. Это список кортежей, каждый из которых обязательно должен иметь target path(откуда брать файлы) и destination path(где будет находится приложение). destination path должен быть относительным. Чтобы расположить все ресурсы в одной папке с exe-файлами используйте пустую строку.

Измените параметр datas, на путь к вашей папке с UI:

a = Analysis(['main.py'], . datas=[('I:/path/to/helloApp/UI', 'UI')], hiddenimports=[], . exe = EXE(pyz, a.scripts, [], . name='main', debug=False, . console=False ) coll = COLLECT(exe, . upx_exclude=[], name='main')

Параметр console установим в false, потому что у нас не консольное приложение.

Параметр name внутри вызова Exe, это имя исполняемого файла. name внутри вызова Collect, это имя папки в которой появится готовое приложение. Имена создаются на основании файла для которого мы создали spec — main.py.

Теперь можно запустить сборку:

В папке dist появится папка main. Для запуска программы достаточно запустить файл main.exe.

Так будет выглядеть содержимое папки с десктопным приложением на Python:

Содержимое папки с готовым приложением

О том, как использовать Qt Designer для создания UI приложений на Python читайте в нашей статье.

Источник

Оцените статью