PyQt QtreeView 滚动条重叠 header

PyQt QtreeView scrollbar overlapping header

我一直在尝试使用 QTreeView(而不是 QListView),因为我需要一个水平的 header。 此外,我想使用样式表来自定义 header 颜色和滚动条。

默认情况下,滚动条与 header 重叠。这是一个已经讨论过的问题,解决方法似乎很好,除了即使滚动条开始低于 header,滚动条背景仍然与 header 重叠。

就我而言,我希望 header 完全适应小部件,并且滚动条刚好在

下方开始

下面是一些重现我的问题的代码示例:

import sys
from PyQt6 import QtCore, QtGui, QtWidgets


class custom_treeview(QtWidgets.QTreeView):

    def __init__(self, parent, name, color):
        super().__init__(parent)
        self.setAcceptDrops(True)
        self.setRootIsDecorated(False)
        self.setDragEnabled(True)
        self.setAlternatingRowColors(True)
        self.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)

        # set model
        model = QtGui.QStandardItemModel()
        model.setRowCount(0)
        model.setColumnCount(1)
        model.setHeaderData(0, QtCore.Qt.Orientation.Horizontal, name)
        self.setModel(model)

        self.setStyleSheet(f"""QTreeView {{ border: 2px solid {color}; }}
        ::section {{ background-color: {color} ;border: none;font: bold 12px;}}
QScrollBar::handle:vertical {{ background: {color}; }}
QScrollBar::handle:horizontal {{ background: {color} }}
""")


    def updateVertScrollBar(self):
        sb = self.verticalScrollBar()
        rect = sb.geometry()
        rect.setTop(self.header().height())
        sb.setGeometry(rect)

    def resizeEvent(self, e: QtGui.QResizeEvent) -> None:
        super().resizeEvent(e)
        self.updateVertScrollBar()


class MyApp(QtWidgets.QMainWindow):

    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.setObjectName("main_win")
        self.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidget.setObjectName("centralwidget")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.splitter = QtWidgets.QSplitter(self.centralwidget)
        self.splitter.setOrientation(QtCore.Qt.Orientation.Horizontal)
        self.splitter.setObjectName("splitter")
        self.horizontalLayout.addWidget(self.splitter)
        self.setCentralWidget(self.centralwidget)
        self.centralwidget.setStyleSheet("""QScrollBar:vertical { background: #FFFFFF;width: 10px; } 
        QScrollBar::add-line:vertical { height: 0px; } 
        QScrollBar::sub-line:vertical { height: 0px; } 
        QScrollBar::add-line:horizontal { width: 0px; } 
        QScrollBar::sub-line:horizontal { width: 0px; } 
        QScrollBar:horizontal { background: #FFFFFF;height: 10px } 
        QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { height: 0px; background: none; } 
        QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { width: 0px; background: none; }""")

        self.left_tree = custom_treeview(self.splitter, "left tree", "#AB1234")

        self.right_tree = custom_treeview(self.splitter, "right tree", "#4331BA")

        a = [str(i) for i in range(500)]

        for i in a:

            item = QtGui.QStandardItem()
            item.setText(i)
            self.left_tree.model().appendRow(item)

        for i in a:

            item = QtGui.QStandardItem()
            item.setText(i)
            self.right_tree.model().appendRow(item)




sys.argv = ['']
app = QtWidgets.QApplication(sys.argv)
main_app = MyApp()
main_app.show()
app.exec()

问题是:

我还希望小部件可以调整大小(这就是我的示例中有分隔符的原因)。

我应该怎么做才能获得干净的行为?

问题在于滚动条是滚动区域的直接child,而视图的内容(包括header)是其视口的一部分。您不能“扩展”header,因为它无论如何都会被视口本身的几何形状隐藏。

一个可能的解决方案是添加一个与滚动条小部件颜色相同的基本 QWidget,并确保它始终身高 header。为此,您不需要覆盖 resizeEvent(),而是覆盖 updateGeometries(),每当视图需要更新其内部小部件时都会调用它。

class custom_treeview(QtWidgets.QTreeView):
    def __init__(self, parent, name, color):
        # ...
        self.setStyleSheet(f"""
            QTreeView {{ 
                border: 2px solid {color}; 
            }}
            QHeaderView::section {{ 
                background-color: {color}; 
                border: none; 
                font: bold 12px; 
            }}
            QScrollBar::handle:vertical {{ 
                background: {color}; 
            }}
            QScrollBar::handle:horizontal {{ 
                background: {color}; 
            }}
            QWidget#fakeHeader {{
                background: {color}; 
            }}
            """)
        self.fakeHeader = QtWidgets.QWidget(objectName='fakeHeader')
        self.addScrollBarWidget(self.fakeHeader, QtCore.Qt.AlignTop)

    def updateGeometries(self):
        super().updateGeometries()
        self.fakeHeader.setFixedHeight(
            self.header().height() + self.frameWidth()
        )

请注意,这显然是一种解决方法,并不是“正确”的解决方案:例如,如果内容比视图宽度宽,header 标签仍​​将受到视口边距的限制.