Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/351.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python PyQt中的分层代理模型_Python_Pyqt_Pyqt5_Qsortfilterproxymodel - Fatal编程技术网

Python PyQt中的分层代理模型

Python PyQt中的分层代理模型,python,pyqt,pyqt5,qsortfilterproxymodel,Python,Pyqt,Pyqt5,Qsortfilterproxymodel,使用PyQt5,我试图构建一个显示两个部分的GUI部分;一个在QTableView(类概述)中提供概述,另一个在QTabWidget(类DetailleView)中显示概述中所选项目的详细信息 现在,QTabWidget中的数据分布在几个选项卡上,显示来自不同表的数据(比下面的最小示例复杂得多)。因为它们的行为类似于键:值对,所以我希望垂直显示它们,而不是水平显示。所以我有一个可转换的类来实现这一点 然而,QTabWidget表的过滤并不十分有效:当我在概览表中选择一个项目时,QTabWidge

使用PyQt5,我试图构建一个显示两个部分的GUI部分;一个在QTableView(类概述)中提供概述,另一个在QTabWidget(类DetailleView)中显示概述中所选项目的详细信息

现在,QTabWidget中的数据分布在几个选项卡上,显示来自不同表的数据(比下面的最小示例复杂得多)。因为它们的行为类似于键:值对,所以我希望垂直显示它们,而不是水平显示。所以我有一个可转换的类来实现这一点

然而,QTabWidget表的过滤并不十分有效:当我在概览表中选择一个项目时,QTabWidget上的过滤器确实会得到更新,但只有在单击其他选项卡时才可见

我认为问题在于代理模型的分层:对于InvertedTables,我有两层代理模型。一个是正常的QSortFilterProxyModel,我使用它来过滤要显示的数据的右侧子集。除此之外,还有另一个代理模型(“FlippedProxyModel”,从QSortFilterProxyModel派生)来反转数据。我使用第一个用于过滤,我认为这就是为什么QTableViews不能立即更新的原因。(当我在下面的代码中使用SQLTables而不是InvertedTables时,一切都很好——当然除了方向。)

这可能也是筛选后保留空列的原因

我可以把翻转的模型放在过滤器模型的下面,但是我想过滤的列在过滤时已经是行了,那么我该如何过滤呢?(同时,正在显示的表格可能会变大,因此使用过滤拳头似乎是个好主意。)

如何使用QSortProxyFilterModels筛选和垂直反转表格,以便显示表格的QTableView在筛选后立即更新?

MCVE包含在下面:

#!/usr/bin/python3

from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlTableModel
from PyQt5.QtWidgets import (QTableView, QTabWidget, QGridLayout, QWidget, 
                             QApplication)
from PyQt5.QtCore import (Qt, pyqtSignal)
from PyQt5.Qt import QModelIndex, QSortFilterProxyModel, QSqlRelationalDelegate
import sys

db_file = "test.db"

#========================================
# handle database:

def create_connection(db_file):
    db = QSqlDatabase.addDatabase("QSQLITE")
    db.setDatabaseName(db_file)
    if not db.open():
        print("Cannot establish a database connection to {}!".format(db_file))
        return False
    return db

def fill_tables():
    q = QSqlQuery()
    q.exec_("DROP TABLE IF EXISTS Manufacturers;")
    q.exec_("CREATE TABLE Manufacturers (Name TEXT, Country TEXT);")
    q.exec_("INSERT INTO Manufacturers VALUES ('VW', 'Germany');")
    q.exec_("INSERT INTO Manufacturers VALUES ('Honda' , 'Japan');")

    q.exec_("DROP TABLE IF EXISTS Cars;")
    q.exec_("CREATE TABLE Cars (Company TEXT, Model TEXT, Year INT);")
    q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 2009);")
    q.exec_("INSERT INTO Cars VALUES ('VW', 'Golf', 2013);")
    q.exec_("INSERT INTO Cars VALUES ('VW', 'Polo', 1999);")

#========================================
# general classes:

class FlippedProxyModel(QSortFilterProxyModel):
    """a proxy model where all columns and rows are inverted
     (compared to the source model);
    source: http://www.howtobuildsoftware.com/index.php/how-do/bgJv/pyqt-pyside-qsqltablemodel-qsqldatabase-qsqlrelationaltablemodel-with-qsqlrelationaldelegate-not-working-behind-qabstractproxymodel
    """
    def __init__(self, parent=None):
        super().__init__(parent)

    def mapFromSource(self, index):
        return self.createIndex(index.column(), index.row())

    def mapToSource(self, index):
        return self.sourceModel().index(index.column(), index.row(), QModelIndex())

    def columnCount(self, parent):
        return self.sourceModel().rowCount(QModelIndex())

    def rowCount(self, parent):
        return self.sourceModel().columnCount(QModelIndex())

    def index(self, row, column, parent):
        return self.createIndex(row, column)

    def parent(self, index):
        return QModelIndex()

    def data(self, index, role):
        return self.sourceModel().data(self.mapToSource(index), role)

    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal:
            return self.sourceModel().headerData(section, Qt.Vertical, role)
        if orientation == Qt.Vertical:
            return self.sourceModel().headerData(section, Qt.Horizontal, role)


class FlippedProxyDelegate(QSqlRelationalDelegate):
    """a delegate for handling data displayed through a FlippedProxyModel;
    source: http://www.howtobuildsoftware.com/index.php/how-do/bgJv/pyqt-pyside-qsqltablemodel-qsqldatabase-qsqlrelationaltablemodel-with-qsqlrelationaldelegate-not-working-behind-qabstractproxymodel
    """
    def createEditor(self, parent, option, index):
        proxy = index.model()
        base_index = proxy.mapToSource(index)
        return super(FlippedProxyDelegate, self).createEditor(parent, option, base_index)

    def setEditorData(self, editor, index):
        proxy = index.model()
        base_index = proxy.mapToSource(index)
        return super(FlippedProxyDelegate, self).setEditorData(editor, base_index)

    def setModelData(self, editor, model, index):
        base_model = model.sourceModel()
        base_index = model.mapToSource(index)
        return super(FlippedProxyDelegate, self).setModelData(editor, base_model, base_index)


class SQLTable(QWidget):
    def __init__(self, query):
        super().__init__()
        self.create_model(query)
        self.init_UI()

    def create_model(self, query):
        raw_model = QSqlTableModel()
        q = QSqlQuery()
        q.exec_(query)
        self.check_error(q)
        raw_model.setQuery(q)
        self.model = QSortFilterProxyModel()
        self.model.setSourceModel(raw_model)

    def init_UI(self):
        self.grid = QGridLayout()
        self.setLayout(self.grid)
        self.table = QTableView()
        self.grid.addWidget(self.table, 1,0)
        self.table.setModel(self.model)

    def check_error(self, q):
        lasterr = q.lastError()
        if lasterr.isValid():
            print(lasterr.text())
            self.mydb.close()
            exit(1)


class InvertedTable(SQLTable):
    """a Widget that displays content of an SQLite query inverted
    (= with rows and columns flipped);
    """
    def __init__(self, query = ""):
        self.query = query
        super().__init__(query)

        self.flipped_model = FlippedProxyModel()
        self.flipped_model.setSourceModel(self.model)
        self.table.setModel(self.flipped_model)
        self.table.setItemDelegate(FlippedProxyDelegate(self.table)) # use flipped proxy delegate
        h_header = self.table.horizontalHeader()
        h_header.hide()
        v_header = self.table.verticalHeader()
        v_header.setFixedWidth(70)
        self.table.resizeColumnsToContents()

#========================================
# application classes:

class MainWidget(QWidget):
    def __init__(self, company):
        super().__init__()
        self.init_UI()
        self.filter(company)

        self.overview.company_changed.connect(self.details.filter)

    def init_UI(self):
        self.resize(400,400)
        self.grid = QGridLayout()
        self.setLayout(self.grid)

        self.overview = Overview()
        self.grid.addWidget(self.overview, 0, 0)

        self.details = DetailedView()
        self.grid.addWidget(self.details, 1, 0)

    def filter(self, company):
        self.details.filter(company)


class Overview(SQLTable):
    company_changed = pyqtSignal(str)

    def __init__(self):
        query = "select * from Manufacturers"
        super().__init__(query)
        self.table.clicked.connect(self.on_clicked)

    def on_clicked(self, index):
        company_index = self.model.index(index.row(), 0)
        company = self.model.data(company_index)
        self.company_changed.emit(company)


class DetailedView(QTabWidget):
    def __init__(self):
        super().__init__()
        self.add_tab1()
        self.add_tab2()

    def add_tab1(self):
        query = "select * from cars"
        self.tab1 = InvertedTable(query)
        self.addTab(self.tab1, "Cars")

    def add_tab2(self):
        query = "SELECT company, count(*) as nr_cars from cars group by company"
        self.tab2 = InvertedTable(query)
        self.addTab(self.tab2, "Numbers")

    def filter(self, company):
        for mytab in [self.tab1, self.tab2]:
            mytab.model.setFilterKeyColumn(0)
            mytab.model.setFilterFixedString(company)

#========================================
# execution:

def main():
    mydb = create_connection(db_file)
    if not mydb:
        sys.exit(-1)
    fill_tables()
    app = QApplication(sys.argv)
    ex = MainWidget('VW')
    ex.show()
    result = app.exec_()

    if (mydb.open()):
        mydb.close()

    sys.exit(result)


if __name__ == '__main__':
    main()
它起作用了

试试看:

在DetailedView类中添加了row park

class DetailedView(QTabWidget):
    def __init__(self):
        super().__init__()
        self.name_tab = ["Cars", "Numbers"]                  # +++
        self.add_tab1()
        self.add_tab2()

    def add_tab1(self):
        query = "select * from cars"
        self.tab1 = InvertedTable(query)
        self.addTab(self.tab1, "Cars")

    def add_tab2(self):
        query = "SELECT company, count(*) as nr_cars from cars group by company"
        self.tab2 = InvertedTable(query)
        self.addTab(self.tab2, "Numbers")

    def filter(self, company):
        self.clear()                                         # +++

        #for mytab in [self.tab1, self.tab2]:                # ---
        for i, mytab in enumerate([self.tab1, self.tab2]):
            mytab.model.setFilterKeyColumn(0)
            mytab.model.setFilterFixedString(company)
            self.addTab(mytab, self.name_tab[i])             # +++
@s.nick的命令是强制的,它正在删除QTabWidget的widget,并添加回来,如果处理大量数据,这将消耗大量资源

问题是Proxy希望
LayoutBoutToBeChanged
layoutChanged
信号,但在
QSortProxyModel
的情况下,它没有这样做,因此解决方案只是发出它:

def filter(self, company):
    for mytab in [self.tab1, self.tab2]:
        mytab.model.layoutAboutToBeChanged.emit()
        mytab.model.setFilterFixedString(company)
        mytab.model.layoutChanged.emit()
此外,我看到您不必要地使用了
QSqlTableModel
,使用
QSqlQueryModel
就足够了,
QSqlTableModel
在这种情况下是超尺寸的

另一个需要改进的地方是
FlippedProxyModel
应该继承自
QIdentityProxyModel
,不需要进行筛选或排序,因此
QSortProxyModel
的维度也过大

我已使用上述改进修改了应用程序,生成的代码如下:

#!/usr/bin/python3

import sys

from PyQt5.QtCore import Qt, pyqtSignal, QIdentityProxyModel, QModelIndex, QSortFilterProxyModel
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel, QSqlRelationalDelegate
from PyQt5.QtWidgets import QTableView, QTabWidget, QGridLayout, QWidget, QApplication

db_file = "test.db"


# ========================================
# handle database:

def create_connection(db_file):
    db = QSqlDatabase.addDatabase("QSQLITE")
    db.setDatabaseName(db_file)
    if not db.open():
        print("Cannot establish a database connection to {}!".format(db_file))
        return False
    return db


def fill_tables():
    q = QSqlQuery()
    q.exec_("DROP TABLE IF EXISTS Manufacturers;")
    q.exec_("CREATE TABLE Manufacturers (Name TEXT, Country TEXT);")
    q.exec_("INSERT INTO Manufacturers VALUES ('VW', 'Germany');")
    q.exec_("INSERT INTO Manufacturers VALUES ('Honda' , 'Japan');")

    q.exec_("DROP TABLE IF EXISTS Cars;")
    q.exec_("CREATE TABLE Cars (Company TEXT, Model TEXT, Year INT);")
    q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 2009);")
    q.exec_("INSERT INTO Cars VALUES ('VW', 'Golf', 2013);")
    q.exec_("INSERT INTO Cars VALUES ('VW', 'Polo', 1999);")


# ========================================
# general classes:

class FlippedProxyModel(QIdentityProxyModel):
    """a proxy model where all columns and rows are inverted
     (compared to the source model);
    source: http://www.howtobuildsoftware.com/index.php/how-do/bgJv/pyqt-pyside-qsqltablemodel-qsqldatabase-qsqlrelationaltablemodel-with-qsqlrelationaldelegate-not-working-behind-qabstractproxymodel
    """

    def mapFromSource(self, index):
        return self.index(index.column(), index.row())

    def mapToSource(self, index):
        return self.sourceModel().index(index.column(), index.row())

    def columnCount(self, parent=QModelIndex()):
        return self.sourceModel().rowCount(parent)

    def rowCount(self, parent=QModelIndex()):
        return self.sourceModel().columnCount(parent)

    def index(self, row, column, parent=QModelIndex()):
        return self.createIndex(row, column)

    def parent(self, index):
        return QModelIndex()

    def data(self, index, role):
        return self.sourceModel().data(self.mapToSource(index), role)

    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal:
            return self.sourceModel().headerData(section, Qt.Vertical, role)
        if orientation == Qt.Vertical:
            return self.sourceModel().headerData(section, Qt.Horizontal, role)


class FlippedProxyDelegate(QSqlRelationalDelegate):
    """a delegate for handling data displayed through a FlippedProxyModel;
    source: http://www.howtobuildsoftware.com/index.php/how-do/bgJv/pyqt-pyside-qsqltablemodel-qsqldatabase-qsqlrelationaltablemodel-with-qsqlrelationaldelegate-not-working-behind-qabstractproxymodel
    """

    def createEditor(self, parent, option, index):
        proxy = index.model()
        base_index = proxy.mapToSource(index)
        return super(FlippedProxyDelegate, self).createEditor(parent, option, base_index)

    def setEditorData(self, editor, index):
        proxy = index.model()
        base_index = proxy.mapToSource(index)
        return super(FlippedProxyDelegate, self).setEditorData(editor, base_index)

    def setModelData(self, editor, model, index):
        base_model = model.sourceModel()
        base_index = model.mapToSource(index)
        return super(FlippedProxyDelegate, self).setModelData(editor, base_model, base_index)


class SQLTable(QWidget):
    def __init__(self, query):
        super().__init__()
        self.create_model(query)
        self.init_UI()

    def create_model(self, query):
        self.model = QSortFilterProxyModel()
        querymodel = QSqlQueryModel()
        querymodel.setQuery(query)
        self.model.setSourceModel(querymodel)

    def init_UI(self):
        self.grid = QGridLayout()
        self.setLayout(self.grid)
        self.table = QTableView()
        self.grid.addWidget(self.table, 1, 0)
        self.table.setModel(self.model)


class InvertedTable(SQLTable):
    """a Widget that displays content of an SQLite query inverted
    (= with rows and columns flipped);
    """

    def __init__(self, query=""):
        super().__init__(query)

        self.flipped_model = FlippedProxyModel()
        self.flipped_model.setSourceModel(self.model)
        self.table.setModel(self.flipped_model)
        self.table.setItemDelegate(FlippedProxyDelegate(self.table))  # use flipped proxy delegate
        h_header = self.table.horizontalHeader()
        h_header.hide()
        v_header = self.table.verticalHeader()
        v_header.setFixedWidth(70)
        self.table.resizeColumnsToContents()


# ========================================
# application classes:

class MainWidget(QWidget):
    def __init__(self, company):
        super().__init__()
        self.init_UI()
        self.filter(company)

        self.overview.company_changed.connect(self.details.filter)

    def init_UI(self):
        self.resize(400, 400)
        self.grid = QGridLayout()
        self.setLayout(self.grid)

        self.overview = Overview()
        self.grid.addWidget(self.overview, 0, 0)

        self.details = DetailedView()
        self.grid.addWidget(self.details, 1, 0)

    def filter(self, company):
        self.details.filter(company)


class Overview(SQLTable):
    company_changed = pyqtSignal(str)

    def __init__(self):
        query = "select * from Manufacturers"
        super().__init__(query)
        self.table.clicked.connect(self.on_clicked)

    def on_clicked(self, index):
        company_index = self.model.index(index.row(), 0)
        company = self.model.data(company_index)
        self.company_changed.emit(company)


class DetailedView(QTabWidget):
    def __init__(self):
        super().__init__()
        self.add_tab1()
        self.add_tab2()

    def add_tab1(self):
        query = "select * from cars"
        self.tab1 = InvertedTable(query)
        self.addTab(self.tab1, "Cars")

    def add_tab2(self):
        query = "SELECT company, count(*) as nr_cars from cars group by company"
        self.tab2 = InvertedTable(query)
        self.addTab(self.tab2, "Numbers")

    def filter(self, company):
        for mytab in [self.tab1, self.tab2]:
            mytab.model.layoutAboutToBeChanged.emit()
            mytab.model.setFilterFixedString(company)
            mytab.model.layoutChanged.emit()


# ========================================
# execution:

def main():
    mydb = create_connection(db_file)
    if not mydb:
        sys.exit(-1)
    fill_tables()
    app = QApplication(sys.argv)
    ex = MainWidget('VW')
    ex.show()
    result = app.exec_()

    if (mydb.open()):
        mydb.close()

    sys.exit(result)


if __name__ == '__main__':
    main()

我确实需要QSqlTableModel,因为实际应用程序中的表是用户与db交互所必需的(这些表需要可编辑)。但你其余的见解非常有用。谢谢大家!@CodingCat QSqlTableModel是一个专门用于表的QSqlQueryModel,您不应在QSqlTableModel中插入查询,而应使用其选择和筛选方法或类似方法:但如果查询比“从表中选择*更复杂”,该怎么办?比如,左连接和汇总数字?我可以将所有这些表示为SQL查询,然后将其放入QSqlTableModel。你能解释一下为什么会有问题吗?埃塔:我把这个问题单独提了出来: