QSortFilterProxyModel 仅对获取的数据进行排序

Posted

技术标签:

【中文标题】QSortFilterProxyModel 仅对获取的数据进行排序【英文标题】:QSortFilterProxyModel sorts only the fetched data 【发布时间】:2021-10-01 02:27:29 【问题描述】:

我有一个 QTableView,其模型为 QSortFilterProxyModel,其源模型为 QSqlRelationalTableModel。

当我通过 QSortFilterProxyModel 对查看的数据进行排序或过滤时,只有获取的数据会受到影响。

我设法通过使用从表中获取所有数据来使其工作

if model.canfetchMore():
   model.fetchMore()

问题是表有大量记录(200,000 条记录)。

有没有其他方法可以在不从数据库中获取所有数据的情况下对代理模型进行排序和过滤?

【问题讨论】:

为什么不用SQL实现过滤和排序? 我正在为最终用户的 QTableView 实现排序和多列过滤功能,类似于 MS-EXCEL,以及在底层 QSqlRelationalTableModel 中添加、编辑和删除记录。我发现这是我(作为没有经验的人)这样做的最简单的方法。如果您能建议我另一种合适的方式,将不胜感激。 【参考方案1】:

您可能使用 Sqlite,因为您必须使用 fetchMore()

当您处理大量记录时,一次从数据库加载 200.000 条左右的记录会很慢,这不是fetchMore() 的问题,而是从数据库中加载这么多的数据很慢。即使您在不必使用fetchMore() 的情况下使用PostgreSQL,它也很慢。所以我使用了一个技巧来拥有所有记录,但不必等待太久。我将(同一个)表拆分为两个视图,我称之为父子视图:

import sys
import random

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtSql import *

class Window(QWidget):
    def __init__(self, parent=None):
        #QWidget.__init__(self, parent)
        super(Window, self).__init__(parent)

        self.search_label = QLabel("Name:")
        self.search_lineedit = QLineEdit('')

        self.parent_model = QSqlQueryModel(self)
        self.refresh_parent()
        self.parent_proxy_model = QSortFilterProxyModel()
        self.parent_proxy_model.setSourceModel(self.parent_model)
        self.parent_view = QTableView()
        self.parent_view.setModel(self.parent_proxy_model)
        self.parent_view.setSelectionMode(QTableView.SingleSelection)
        self.parent_view.setSelectionBehavior(QTableView.SelectRows)
        self.parent_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.parent_view.horizontalHeader().setStretchLastSection(True)
        self.parent_view.verticalHeader().setVisible(False)
        self.parent_view.setSortingEnabled(True)
        self.parent_view.horizontalHeader().setSortIndicator(0, Qt.AscendingOrder)
        self.parent_view.setAlternatingRowColors(True)
        self.parent_view.setShowGrid(False)
        #self.parent_view.verticalHeader().setDefaultSectionSize(24)
        self.parent_view.setStyleSheet("QTableView::item:selected:!active  selection-background-color:#BABABA; ")
        for i, header in enumerate(self.parent_headers):
            self.parent_model.setHeaderData(i, Qt.Horizontal, self.parent_headers[self.parent_view.horizontalHeader().visualIndex(i)])
        self.parent_view.resizeColumnsToContents()

        self.child_model = QSqlQueryModel(self)
        self.refresh_child()
        self.child_proxy_model = QSortFilterProxyModel()
        self.child_proxy_model.setSourceModel(self.child_model)
        self.child_view = QTableView()
        self.child_view.setModel(self.child_proxy_model)
        self.child_view.setSelectionMode(QTableView.SingleSelection)
        self.child_view.setSelectionBehavior(QTableView.SelectRows)
        self.child_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.child_view.horizontalHeader().setStretchLastSection(True)
        self.child_view.verticalHeader().setVisible(False)
        self.child_view.setSortingEnabled(True)
        self.child_view.horizontalHeader().setSortIndicator(1, Qt.AscendingOrder)
        self.child_view.setAlternatingRowColors(True)
        self.child_view.setShowGrid(False)
        #self.child_view.verticalHeader().setDefaultSectionSize(24)
        self.child_view.setStyleSheet("QTableView::item:selected:!active  selection-background-color:#BABABA; ")
        for i, header in enumerate(self.child_headers):
            self.child_model.setHeaderData(i, Qt.Horizontal, self.child_headers[self.child_view.horizontalHeader().visualIndex(i)])
        self.child_view.resizeColumnsToContents()

        search_layout = QHBoxLayout()
        search_layout.addStretch()
        search_layout.addWidget(self.search_label)
        search_layout.addWidget(self.search_lineedit)

        view_layout = QHBoxLayout()
        view_layout.addWidget(self.parent_view)
        view_layout.addWidget(self.child_view)

        layout = QVBoxLayout(self)
        layout.addLayout(search_layout)
        layout.addLayout(view_layout)
        #self.setLayout(layout)

        self.parent_view.selectionModel().currentRowChanged.connect(self.parent_changed)
        self.search_lineedit.textChanged.connect(self.search_changed)

        self.parent_view.setCurrentIndex(self.parent_view.model().index(0, 0))
        self.parent_view.setFocus()

    def refresh_parent(self):
        self.parent_headers = ['Category']
        query_string = "SELECT category FROM parent"
        query = QSqlQuery()
        query.exec(query_string)
        self.parent_model.setQuery(query)
        while self.parent_model.canFetchMore():
            self.parent_model.fetchMore()

    def refresh_child(self, category=''):
        self.child_headers = ['Category', 'Name', 'Description']
        query_string = ("SELECT category, name, description FROM child "
            "WHERE child.category = 'category'").format(category = category)
        query = QSqlQuery()
        query.exec(query_string)
        self.child_model.setQuery(query)
        while self.child_model.canFetchMore():
            self.child_model.fetchMore()

    def parent_changed(self, index):
        if index.isValid():
            index = self.parent_proxy_model.mapToSource(index)
            record = self.parent_model.record(index.row())
            self.refresh_child(record.value("parent.category"))
            #self.child_view.scrollToBottom() # if needed

    def search_changed(self, text):
        query_string = ("SELECT category, name FROM child WHERE name = 'name'").format(name = text)
        query = QSqlQuery()
        query.exec(query_string)
        if query.next():
            category = query.value('category')
            start = self.parent_proxy_model.index(0, 0)
            matches = self.parent_proxy_model.match(start, Qt.DisplayRole, category, 1, Qt.MatchExactly) # Qt.MatchExactly # Qt.MatchStartsWith
            if matches:
                print('parent matches')
                index = matches[0]
                self.parent_view.selectionModel().select(index, QItemSelectionModel.Select)
                self.parent_view.setCurrentIndex(index)
                self.refresh_child(category)
                #------------------------------------------------
                start = self.child_proxy_model.index(0, 1)
                matches = self.child_proxy_model.match(start, Qt.DisplayRole, text, 1, Qt.MatchExactly) # Qt.MatchExactly # Qt.MatchStartsWith
                if matches:
                    print('child matches')
                    index = matches[0]
                    self.child_view.selectionModel().select(index, QItemSelectionModel.Select)
                    self.child_view.setCurrentIndex(index)
                    self.child_view.setFocus()

def create_fake_data():
    categories = []
    #import random
    query = QSqlQuery()
    query.exec("CREATE TABLE parent(category TEXT)")
    for i in range(1, 201):
        category = str(i).zfill(3)
        categories.append(category)
        query.prepare("INSERT INTO parent (category) VALUES(:category)")
        query.bindValue(":category", category)
        query.exec()
    query.exec("CREATE TABLE child(category TEXT, name TEXT, description TEXT)")
    counter = 1
    for category in categories:
        for i in range(1, 1001):
            name = str(counter).zfill(6)
            description = str(random.randint(1,100)).zfill(3)
            counter += 1
            query.prepare("INSERT INTO child (category, name, description) VALUES(:category, :name, :description)")
            query.bindValue(":category", category)
            query.bindValue(":name", name)
            query.bindValue(":description", description)
            query.exec()

def create_connection():
    db = QSqlDatabase.addDatabase("QSQLITE")
    #db.setDatabaseName('test.db')
    db.setDatabaseName(":memory:")
    db.open()
    create_fake_data()
    print('database is full, now starting a program...')

app = QApplication(sys.argv)
create_connection()
window = Window()
#window.resize(800, 600)
#window.show()
window.showMaximized()
app.exec()

只有当你有一个属性(或一个属性的前缀,或多个属性)你可以用作一个“类别”时才可能。

此程序加载缓慢,但只有数据库输入。一旦数据库已满,程序就会快速运行。这只是一个示例,在实际应用中,您的数据库已满。

使用这个技巧,您可以处理 1.000.000 条记录,只需使用 1.000 x 1.000(或 200 x 5.000,或其他组合)。问题是搜索和过滤器更难以这种方式实现。我展示了一种实现搜索的方法。

关于“你为什么不使用SQL实现过滤和排序”评论,那是因为sql只能以一个顺序和一个(固定)过滤器返回数据,而QSortFilterProxyModel你可以将数据放入模型后,以任何您喜欢的方式对数据进行排序和过滤,并且比使用 sql 更快。

【讨论】:

以上是关于QSortFilterProxyModel 仅对获取的数据进行排序的主要内容,如果未能解决你的问题,请参考以下文章

不能从 `QSortFilterProxyModel` 派生

链式 QSortFilterProxyModel

QSortFilterProxyModel 不更新 QTableview

QSortFilterProxyModel 打破 columnWidths

QSortFilterProxyModel.mapToSource 崩溃。没有信息为啥

为视图创建多个 QSortFilterProxyModel 实例