QML – Сортировка TableModel с использованием Python backend

Вопрос или проблема

В проекте Qt 6.8, использующем QML для фронтенда и Python для бэкенда, я пытаюсь реализовать систему фильтров для TableModel/ListModel. Я прочитал документацию по QSortFilterProxyModel, но не нашел работающий пример на Python.

Как я мог бы модифицировать упрощенный код ниже, чтобы интегрировать метод фильтрации модели через бэкенд моего приложения и связать его с панелью поиска?

Вот мой текущий код:

main.py:

import sys
from pathlib import Path
from PySide6.QtCore import QtMsgType, qInstallMessageHandler
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine

def handle_qml_message(msg_type, context, message):
    print(f"QML: {message}")

if __name__ == "__main__":
    qInstallMessageHandler(handle_qml_message)

    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    qml_file = Path(__file__).resolve().parent / "main.qml"
    engine.load(qml_file)
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec())

main.qml:

import QtQuick
import QtQuick.Window
import Qt.labs.qmlmodels
import QtQuick.Controls

Window {
    width: 640
    height: 480
    visible: true

    TextField {
        id: textField
        anchors {
            top: parent.top
            left: parent.left
        }
        width: 300
        height: 30
        placeholderText: "Поиск ..."
        onTextChanged: print(text)
    }

    HorizontalHeaderView {
        id: horizontalHeader
        z: 2
        syncView: tableView
        anchors {
            top: textField.bottom
            left: parent.left
        }
        resizableColumns: false
        model: ["Имя", "Цвет"]
        delegate: Rectangle {
            implicitHeight: 50
            implicitWidth: 50
            color: "lightblue"

            Text {
                anchors.centerIn: parent
                text: modelData
            }
        }
    }

    TableView {
        id: tableView
        anchors {
            top: horizontalHeader.bottom
            bottom: parent.bottom
            left: parent.left
            right: parent.right
        }
        columnSpacing: 1
        rowSpacing: 1
        clip: true

        model: TableModel {
            TableModelColumn { display: "name" }
            TableModelColumn { display: "color" }

            rows: [
                { "name": "кот", "color": "черный" },
                { "name": "собака", "color": "коричневый" },
                { "name": "птица", "color": "белый" },
                { "name": "рыба", "color": "синий" },
                { "name": "кролик", "color": "серый" },
                { "name": "лошадь", "color": "коричневый" },
                { "name": "змея", "color": "зеленый" },
                { "name": "хомяк", "color": "золотистый" },
                { "name": "черепаха", "color": "зеленый" },
                { "name": "попугай", "color": "красный" },
                { "name": "лягушка", "color": "зеленый" },
                { "name": "ящерица", "color": "коричневый" },
                { "name": "бабочка", "color": "многоцветный" },
                { "name": "пчела", "color": "желтый" },
                { "name": "овца", "color": "белый" },
                { "name": "коза", "color": "черный" },
                { "name": "свинья", "color": "розовый" },
                { "name": "корова", "color": "черно-белый" },
                { "name": "утка", "color": "желтый" },
                { "name": "лебедь", "color": "белый" },
                { "name": "орел", "color": "коричневый" },
                { "name": "сова", "color": "серый" },
                { "name": "павлин", "color": "синий и зеленый" },
                { "name": "олень", "color": "коричневый" },
                { "name": "медведь", "color": "темно-коричневый" },
                { "name": "волк", "color": "серый" },
                { "name": "лис", "color": "оранжевый" },
                { "name": "лев", "color": "золотистый" },
                { "name": "тигр", "color": "оранжевый с полосами" },
                { "name": "слон", "color": "серый" }
            ]
        }

        delegate: Rectangle {
            implicitWidth: 100
            implicitHeight: 50
            border.width: 1

            Text {
                text: display
                anchors.centerIn: parent
            }
        }
    }
}

Ответ или решение

Для реализации фильтрации в TableModel вашего проекта на Qt 6.8 с использованием QML для фронтенда и Python для бэкенда, мы можем воспользоваться классом QSortFilterProxyModel, который позволяет применять фильтрацию и сортировку к данным вашего модели. В данной инструкции я покажу, как интегрировать механизм фильтрации, связывая его с текстовым полем поиска в вашем интерфейсе.

1. Изменяем Python-бэкенд

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

import sys
from pathlib import Path
from PySide6.QtCore import Qt, QSortFilterProxyModel, QAbstractTableModel, QModelIndex
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine

# Исходная модель данных
class AnimalModel(QAbstractTableModel):
    def __init__(self, animals=None):
        super().__init__()
        self._animals = animals or []

    def rowCount(self, parent=QModelIndex()):
        return len(self._animals)

    def columnCount(self, parent=QModelIndex()):
        return 2  # Две колонки: name и color

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            animal = self._animals[index.row()]
            return animal['name'] if index.column() == 0 else animal['color']
        return None

    def get_animals(self):
        return self._animals

class AnimalFilterModel(QSortFilterProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.source_model = None

    def setSourceModel(self, model):
        super().setSourceModel(model)

    def filterRegExp(self):
        return super().filterRegExp()

    def setFilterRegExp(self, pattern):
        super().setFilterRegExp(pattern)

# Основной блок приложения
if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    animal_list = [
        {"name": "cat", "color": "black"},
        {"name": "dog", "color": "brown"},
        # ... и другие животные
    ]

    model = AnimalModel(animal_list)
    filter_model = AnimalFilterModel()
    filter_model.setSourceModel(model)

    engine.rootContext().setContextProperty("filterModel", filter_model)

    qml_file = Path(__file__).resolve().parent / "main.qml"
    engine.load(qml_file)
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec())

2. Обновляем QML интерфейс

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

import QtQuick
import QtQuick.Window
import QtQml.Models

Window {
    width: 640
    height: 480
    visible: true

    TextField {
        id: textField
        placeholderText: "Поиск ..."
        anchors {
            top: parent.top
            left: parent.left
        }
        width: 300
        height: 30
        onTextChanged: {
            filterModel.setFilterRegExp(textField.text);
        }
    }

    TableView {
        id: tableView
        anchors {
            top: textField.bottom
            bottom: parent.bottom
            left: parent.left
            right: parent.right
        }
        model: filterModel
        columnSpacing: 1
        rowSpacing: 1
        delegate: Rectangle {
            implicitWidth: 100
            implicitHeight: 50
            border.width: 1
            Text {
                text: model.name + " - " + model.color
                anchors.centerIn: parent
            }
        }
    }
}

Заключение

Теперь у вас есть рабочий пример фильтрации TableModel с использованием QSortFilterProxyModel на Python и QML. Ввод текста в поле поиска будет автоматически обновлять отображаемые данные в TableView. Эта реализация позволила отделить логику фильтрации от представления, что делает код более чистым и простым для понимания.

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

Оцените материал
Добавить комментарий

Капча загружается...

  1. Гость

    Спасибо за информативную статью! Я хотел бы поделиться альтернативным решением вашей задачи по фильтрации данных в TableModel без необходимости расширять бэкенд на Python.
    Вместо использования QSortFilterProxyModel на стороне Python, вы можете воспользоваться мощными возможностями JavaScript и QML для реализации фильтрации непосредственно в QML. Это может значительно упростить ваш код и сделать его более гибким.

    Вот как можно изменить ваш файл main.qml:

    import QtQuick import QtQuick.Window import Qt.labs.qmlmodels import QtQuick.Controls Window { width: 640 height: 480 visible: true property string filterText: "" TextField { id: textField anchors { top: parent.top left: parent.left } width: 300 height: 30 placeholderText: "Поиск ..." onTextChanged: filterText = text.toLowerCase() } ListModel { id: animalModel ListElement { name: "кот"; color: "черный" } ListElement { name: "собака"; color: "коричневый" } // ... остальные элементы } ListView { id: listView anchors { top: textField.bottom bottom: parent.bottom left: parent.left right: parent.right } model: ListModel { id: filteredModel dynamicRoles: true function updateFilter() { clear() for (var i = 0; i < animalModel.count; ++i) { var item = animalModel.get(i) if (item.name.toLowerCase().indexOf(filterText) !== -1 || item.color.toLowerCase().indexOf(filterText) !== -1) { append(item) } } } Component.onCompleted: updateFilter() } delegate: Rectangle { implicitWidth: parent.width height: 50 border.width: 1 Text { text: name + " - " + color anchors.centerIn: parent } } } Connections { target: textField onTextChanged: filteredModel.updateFilter() } }
    В этом примере мы используем ListModel для хранения данных и динамически обновляем фильтрованный список при изменении текста в поле поиска. Это позволяет избежать лишней сложности и сохранить всю логику на стороне QML.

    Однако, если ваш проект предполагает работу с большими объемами данных или требует более сложной логики фильтрации, использование QSortFilterProxyModel может быть более оправданным. В этом случае, я бы рекомендовал обратить внимание на библиотеку PyQt5, которая предоставляет более подробную документацию и примеры для Python.

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

    Надеюсь, это поможет вам найти оптимальное решение для вашего проекта!

    Ответить