如何将带有列标题的 QTreeView 重做为 QTableView?

Posted

技术标签:

【中文标题】如何将带有列标题的 QTreeView 重做为 QTableView?【英文标题】:How to rework a QTreeView with column headers into a QTableView? 【发布时间】:2021-10-01 11:18:59 【问题描述】:

我有一个带有列标题过滤器的 QTreeView,但我想使用 QTableView。

问题:我不知道如何修改 QTableView 的标题功能。 如果我只是将使用的类从 QTreeView() 切换到 QTableView() 我会收到几十个错误 喜欢AttributeError: 'QTableView' object has no attribute 'setHeader'

目前它看起来像这样(见下面的 MRE):

我想用这样的列标题过滤器构建一个 TableView: (由“DB Browser for SQLite”提供)

在第一个回复的返工后,我删除时有以下内容 self.treeView.verticalHeader().hide():

MRE:

import sys
import re
from PyQt5 import QtWidgets, QtGui, QtCore, QtSql

COUNT_PERS_COLS = 3
col_persID, col_persLAST_NAME, col_persFIRST_NAME = range(COUNT_PERS_COLS)

db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(':memory:')

modelQuery = QtSql.QSqlQueryModel()
modelTable = QtSql.QSqlRelationalTableModel()

def _human_key(key):
    parts = re.split(r'(\d*\.\d+|\d+)', key)
    return tuple((e.swapcase() if i % 2 == 0 else float(e))
            for i, e in enumerate(parts))

class FilterHeader(QtWidgets.QHeaderView):
    filterActivated = QtCore.pyqtSignal()

    def __init__(self, parent):
        super().__init__(QtCore.Qt.Horizontal, parent)
        self._editors = []
        self._padding = 4
        self.setStretchLastSection(True)        
        self.setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
        self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
        self.setSortIndicatorShown(False)
        self.setSectionsMovable(True)
        self.sectionResized.connect(self.adjustPositions)
        parent.horizontalScrollBar().valueChanged.connect(self.adjustPositions)

    def setFilterBoxes(self, count):
        while self._editors:
            editor = self._editors.pop()
            editor.deleteLater()
        for index in range(count):
            editor = QtWidgets.QLineEdit(self.parent())            
            editor.setPlaceholderText('Filter')
            editor.setClearButtonEnabled(True)
            editor.returnPressed.connect(self.filterActivated.emit)
            self._editors.append(editor)
        self.adjustPositions()

    def sizeHint(self):
        size = super().sizeHint()
        if self._editors:
            height = self._editors[0].sizeHint().height()
            size.setHeight(size.height() + height + self._padding)
        return size

    def updateGeometries(self):
        if self._editors:
            height = self._editors[0].sizeHint().height()
            self.setViewportMargins(0, 0, 0, height + self._padding)
        else:
            self.setViewportMargins(0, 0, 0, 0)
        super().updateGeometries()
        self.adjustPositions()

    def adjustPositions(self):
        for index, editor in enumerate(self._editors):
            height = editor.sizeHint().height()
            editor.move(
                self.sectionPosition(index) - self.offset() + 2,
                height + (self._padding // 2))
            editor.resize(self.sectionSize(index), height)

    def filterText(self, index):
        if 0 <= index < len(self._editors):
            return self._editors[index].text()
        return ''

    def setFilterText(self, index, text):
        if 0 <= index < len(self._editors):
            self._editors[index].setText(text)

    def clearFilters(self):
        for editor in self._editors:
            editor.clear()        


class HumanProxyModel(QtCore.QSortFilterProxyModel):
    def lessThan(self, source_left, source_right):
        data_left = source_left.data()
        data_right = source_right.data()
        if type(data_left) == type(data_right) == str:
            return _human_key(data_left) < _human_key(data_right)
        return super(HumanProxyModel, self).lessThan(source_left, source_right)

    @property
    def filters(self):
        if not hasattr(self, "_filters"):
            self._filters = []
        return self._filters

    @filters.setter
    def filters(self, filters):
        print(f"filters() called.")        

        self._filters = filters
        self.invalidateFilter()                

    def filterAcceptsRow(self, sourceRow, sourceParent):        
        for i, text in self.filters:
            if 0 <= i < self.columnCount():
                ix = self.sourceModel().index(sourceRow, i, sourceParent)                
                data = ix.data()
                if text not in data:
                    return False            
        return True        

class winMain(QtWidgets.QMainWindow):
    def __init__(self, parent=None):        
        super().__init__(parent)                
        self.setupUi()
        self.setGeometry(300,200,700,500)        

        self.show()        

    def createPersonModel(self,parent):        
        model = QtGui.QStandardItemModel(0, COUNT_PERS_COLS, parent)                
        model.setHorizontalHeaderLabels(['ID', 'Last Name', 'First Name'])

        return model

    def addPerson(self, model, id, last_name, first_name):        
        model.insertRow(0)        
        model.setData(model.index(0, col_persID), id)
        model.setData(model.index(0, col_persLAST_NAME), last_name)
        model.setData(model.index(0, col_persFIRST_NAME), first_name)

    def handleFilterActivated(self):                
        header = self.treeView.header()
        filters = []

        for i in range(header.count()):
            text = header.filterText(i)
            if text:        
                filters.append((i, text))

        proxy = self.treeView.model()
        proxy.filters = filters        

    def setupUi(self):
        self.centralwidget = QtWidgets.QWidget(self)        
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)        

        self.treeView = QtWidgets.QTreeView(self.centralwidget)        

        self.treeView.setSortingEnabled(True)
        self.treeView.setAlternatingRowColors(True)        
        self.treeView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
        self.treeView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
        self.treeView.setAnimated(True)
        self.treeView.setItemsExpandable(True)

        self.horizontalLayout.addWidget(self.treeView)
        self.setCentralWidget(self.centralwidget)

        header = FilterHeader(self.treeView)        
        self.treeView.setHeader(header)        

        self.statusBar = QtWidgets.QStatusBar()
        self.setStatusBar(self.statusBar)        

        modelTable.setTable("person")
        self.treeView.setModel(modelTable)

        proxy = HumanProxyModel(self)
        proxy.setSourceModel(modelTable)
        self.treeView.setModel(proxy)        

        header.setFilterBoxes(modelTable.columnCount())
        header.filterActivated.connect(self.handleFilterActivated)        


def create_sample_data():     
    modelQuery.setQuery("""CREATE TABLE IF NOT EXISTS country (                                    
                                    id   INTEGER PRIMARY KEY UNIQUE NOT NULL,
                                    name TEXT
                                    )""")

    modelQuery.setQuery("""CREATE TABLE IF NOT EXISTS person (
                                   id         INTEGER PRIMARY KEY UNIQUE NOT NULL,
                                   persId     TEXT,
                                   lastName   TEXT,
                                   firstName  TEXT,
                                   country_id INTEGER NOT NULL DEFAULT 3,
              FOREIGN KEY (country_id) REFERENCES country(id)
                                   )""")

    modelQuery.setQuery("INSERT INTO country (id, name) VALUES (0, 'None')")    
    modelQuery.setQuery("INSERT INTO country (id, name) VALUES (1, 'Angola')")    
    modelQuery.setQuery("INSERT INTO country (id, name) VALUES (2, 'Serbia')")
    modelQuery.setQuery("INSERT INTO country (id, name) VALUES (3, 'Georgia')")

    modelQuery.setQuery("INSERT INTO person (id, persId, lastName, firstName, country_id) VALUES (1, '1001', 'Martin', 'Robert', 1)")
    modelQuery.setQuery("INSERT INTO person (id, persId, lastName, firstName, country_id) VALUES (2, '1002', 'Smith', 'Brad', 2)")
    modelQuery.setQuery("INSERT INTO person (id, persId, lastName, firstName, country_id) VALUES (3, '1003', 'Smith', 'Angelina', 3)")

if __name__ == '__main__':                         
    app = QtWidgets.QApplication(sys.argv)         

    create_sample_data()        

    window = winMain()    
    sys.exit(app.exec_())

添加了“DB Browser for SQLite”的第二张截图:

【问题讨论】:

QTreeView 只有一个(水平)表头,QTableView has two: "该表有一个垂直表头,可以使用verticalHeader() 函数获取,还有一个水平表头,可以通过@ 987654328@函数。” 【参考方案1】:

QTreeView 只有一个标题(水平的),而 QTableView 有 2 个。另外,QTableView 没有分支,因此它也没有 setAnimated()setItemsExpandable() 方法。

def handleFilterActivated(self):
    # header = self.treeView.header()
    header = self.treeView.horizontalHeader()
    filters = []

    for i in range(header.count()):
        text = header.filterText(i)
        if text:
            filters.append((i, text))

    proxy = self.treeView.model()
    proxy.filters = filters

def setupUi(self):
    self.centralwidget = QtWidgets.QWidget(self)
    self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)

    self.treeView = QtWidgets.QTableView(self.centralwidget)

    self.treeView.setSortingEnabled(True)
    self.treeView.setAlternatingRowColors(True)
    self.treeView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
    self.treeView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
    # self.treeView.setAnimated(True)
    # self.treeView.setItemsExpandable(True)

    self.horizontalLayout.addWidget(self.treeView)
    self.setCentralWidget(self.centralwidget)

    header = FilterHeader(self.treeView)
    # self.treeView.setHeader(header)
    self.treeView.setHorizontalHeader(header)
    self.treeView.verticalHeader().hide()

    self.statusBar = QtWidgets.QStatusBar()
    self.setStatusBar(self.statusBar)

    modelTable.setTable("person")
    modelTable.select()
    self.treeView.setModel(modelTable)

    proxy = HumanProxyModel(self)
    proxy.setSourceModel(modelTable)
    self.treeView.setModel(proxy)

    header.setFilterBoxes(modelTable.columnCount())
    header.filterActivated.connect(self.handleFilterActivated)

【讨论】:

谢谢,这成功了!您还隐藏了verticalHeader()。我想看看verticalHeader()。唯一的问题是,QLineEdit 不与列左对齐。我认为这就是您隐藏verticalHeader() 的原因。当 verticalHeader() 保持可见时,是否有机会移动 QLineEdits 以与列正确对齐? 我想实现这一点,如“DB Browser for SQLite”程序(用 C++ 编写):请参阅更新后的原始帖子以获取第二张截图

以上是关于如何将带有列标题的 QTreeView 重做为 QTableView?的主要内容,如果未能解决你的问题,请参考以下文章

Qt - QTreeView 和带有复选框列的自定义模型

如何根据作为输入的列表正确排序 QTreeView 中的列

如何隐藏 QTreeView 中的某些列? [复制]

如何从 qtreeview 中的父行中获取特定列

将复选框列添加到树左侧的 QTreeView 的方法?

自定义 QTreeView 项目