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 标签仍将受到视口边距的限制.
我一直在尝试使用 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 标签仍将受到视口边距的限制.