Различные статьи по Qt и c++

Итоговый мини-проект: десктопное приложение на PySide6 с QML-интерфейсом и Python-backend

Введение

В заключительной статье цикла PySide6 (и всего плана статей в его текущем объёме) собираем итоговый проект — desktop-приложение для управления задачами, объединяющее QML-интерфейс (используя темы из QML-цикла, статьи 479-498, но реализованный через Python-инфраструктуру этого цикла) с Python-backend, включающим модель данных (статья 504), работу с БД через SQLAlchemy (статья 508), и фоновую обработку через QThread (статья 505).

Концепция

Архитектура итогового проекта: TaskListModel (статья 504) служит мостом между SQLAlchemy-сессией (статья 508), хранящей задачи в реальной, persistent БД, и QML-интерфейсом (статья 503), отображающим список задач через ListView; фоновая синхронизация с гипотетическим внешним сервисом (имитирующая сетевую операцию) выполняется через QThread (статья 505), не блокируя основной поток и, соответственно, отзывчивость QML-интерфейса; всё приложение упаковывается для распространения через PyInstaller (статья 506).

Пример кода: Итоговый мини-проект

# main.py — точка входа, объединяющая все компоненты итогового проекта
import sys
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from models import TaskListModel, Base
from sync_worker import SyncWorker

def main():
    app = QGuiApplication(sys.argv)
    engine_qml = QQmlApplicationEngine()

    db_engine = create_engine("sqlite:///tasks.db")
    Base.metadata.create_all(db_engine)  # создание таблиц при первом запуске, если их ещё нет
    Session = sessionmaker(bind=db_engine)
    session = Session()

    task_model = TaskListModel(session)
    engine_qml.rootContext().setContextProperty("taskListModel", task_model)

    sync_worker = SyncWorker()
    sync_worker.syncCompleted.connect(task_model.refresh)  # обновление модели после фоновой синхронизации
    sync_worker.start()

    engine_qml.load("qml/main.qml")
    if not engine_qml.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec())

if __name__ == "__main__":
    main()
# sync_worker.py — фоновая операция, не блокирующая основной поток (статья 505)
from PySide6.QtCore import QThread, Signal
import time

class SyncWorker(QThread):
    syncCompleted = Signal()

    def run(self):
        while True:
            time.sleep(30)  # периодическая синхронизация каждые 30 секунд
            # ... здесь была бы реальная сетевая синхронизация с внешним сервисом ...
            self.syncCompleted.emit()
// qml/main.qml — интерфейс, использующий модель из Python (объединение паттернов QML-цикла)
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    visible: true
    width: 400; height: 600
    title: qsTr("Менеджер задач (PySide6)")

    ListView {
        anchors.fill: parent
        model: taskListModel

        delegate: ItemDelegate {
            width: ListView.view.width
            text: title
            onClicked: taskListModel.toggleCompleted(index)
        }
    }
}

Пояснения к коду

main() показывает явную, последовательную сборку всего приложения — создание движка QML, инициализация БД через SQLAlchemy с автоматическим созданием схемы при первом запуске (Base.metadata.create_all), создание и передача модели в QML-контекст, и запуск фонового SyncWorker, чей сигнал syncCompleted подключается прямо к методу refresh() модели — это короткое соединение демонстрирует, как фоновая работа (статья 505) естественно интегрируется с моделью данных (статья 504) через стандартный механизм сигналов/слотов (статья 501), без необходимости в более сложной, явной синхронизации. QML-файл показывает, что использование Python-модели из QML синтаксически полностью идентично его C++ эквиваленту из QML-цикла (статья 489) — model: taskListModel и taskListModel.toggleCompleted(index) не отличаются от того, как выглядел бы тот же код, если бы модель была реализована на C++, что наглядно подтверждает практическую взаимозаменяемость двух языков для этой части архитектуры.

Подводные камни

  • Выполнение операций с БД (через SQLAlchemy-сессию) непосредственно внутри SyncWorker.run(), выполняемого в фоновом потоке, без учёта потокобезопасности самой сессии (статья 508) — если синхронизация с внешним сервисом в реальной реализации потребовала бы также записи результатов синхронизации непосредственно в БД из фонового потока, нужна отдельная, специально созданная для этого потока SQLAlchemy-сессия, а не совместное использование той же сессии, что и основной поток приложения, использующий её для обслуживания TaskListModel.
  • Бесконечный цикл с time.sleep(30) внутри run() без предусмотренного, явного способа корректно остановить этот поток при закрытии приложения — без явного механизма остановки (флага, проверяемого в цикле, или использования прерываемого ожидания вместо простого time.sleep) поток может продолжать попытки выполнения уже после того, как остальная часть приложения должна была завершиться, потенциально препятствуя корректному, своевременному закрытию всего процесса приложения.
  • Отсутствие обработки ошибок при инициализации БД или при сбое фоновой синхронизации, что может привести к необработанному исключению, аварийно завершающему либо весь процесс приложения (если ошибка происходит в основном потоке при инициализации), либо тихо «теряющему» фоновый поток без какого-либо уведомления пользователя о проблеме (если ошибка происходит внутри run() фонового QThread, исключения в котором не всегда поднимаются столь же явно, как в основном потоке) — production-готовый код должен явно перехватывать и обрабатывать вероятные категории ошибок в обоих случаях.
  • Игнорирование необходимости тщательного тестирования (статья 509) и упаковки (статья 506) именно полной, интегрированной комбинации QML-интерфейса, Python-backend, и SQLAlchemy перед распространением конечным пользователям — каждый из этих компонентов по отдельности может работать корректно при изолированном тестировании, но их совместная интеграция (особенно после упаковки через PyInstaller, который должен правильно обнаружить и включить зависимости и для PySide6, и для SQLAlchemy, и для самих QML-файлов как ресурсов) требует отдельной, целостной проверки именно итогового, полностью собранного и упакованного приложения, а не предположения об автоматической корректности этой комбинации на основании успешности отдельных, изолированных частей.

Цикл «Qt на Python (PySide6)» (статьи 499-510) завершён, замыкая текущий, обширный объём плана статей. Общий план теперь включает 510 тем — от языковых основ Qt/C++ через embedded/IoT, сети, СУБД, DevOps, алгоритмы, мультимедиа, углублённый QML, и теперь полноценную альтернативную точку входа в экосистему Qt через Python, делающую весь объём знаний доступным и для разработчиков, предпочитающих этот язык.