【问题标题】:How to add a fixed header to a QScrollArea?如何将固定标题添加到 QScrollArea?
【发布时间】:2012-02-21 14:03:52
【问题描述】:

我目前有一个由以下定义的 QScrollArea:

self.results_grid_scrollarea = QScrollArea()
self.results_grid_widget = QWidget()
self.results_grid_layout = QGridLayout()
self.results_grid_layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
self.results_grid_widget.setLayout(self.results_grid_layout)
self.results_grid_scrollarea.setWidgetResizable(True)
self.results_grid_scrollarea.setWidget(self.results_grid_widget)
self.results_grid_scrollarea.setViewportMargins(0,20,0,0)

它非常高兴地嵌套在其他布局/小部件中,按预期调整大小等。

为了为网格列提供标题,我使用了位于滚动区域正上方的另一个 QGridLayout - 这很有效……但看起来有点奇怪,即使样式设置得当,尤其是在按需(垂直)滚动条的情况下根据需要出现或消失,并且标题不再与网格列正确对齐。这是我所知道的美学问题......但我有点挑剔;)

其他小部件以编程方式在其他地方添加/删除到self.results_grid_layout。上面的最后一行是我最近添加的,因为我认为使用创建的边距区域很容易,docs 用于 setViewportMargins 状态:

设置滚动区域周围的边距。这对于具有“锁定”行和列的电子表格等应用程序很有用。边缘空间留空; 将小部件放在未使用的区域。

但我终其一生都无法弄清楚如何真正实现这一目标,要么我的 GoogleFu 今天抛弃了我,要么几乎没有关于如何真正实现这一目标的信息/示例。

我的头脑告诉我,我可以只分配一个小部件,由布局控制(包含任意数量的其他小部件)到滚动区域 - 正如我所做的那样。如果我在gridlayout的第0行添加一个QHeaderview,它只会出现在视口的边距下方并与布局的其余部分一起滚动?还是我错过了什么,只见树木不见森林?

我只是在学习 Python/Qt,所以任何帮助、指针和/或示例(最好使用 Python,但不是必需的)将不胜感激!


编辑:按照目前给出的建议(我认为),我想出了以下小测试程序来尝试一下:

import sys
from PySide.QtCore import *
from PySide.QtGui import *

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setMinimumSize(640, 480)

        self.container_widget = QWidget()
        self.container_layout = QVBoxLayout()
        self.container_widget.setLayout(self.container_layout)
        self.setCentralWidget(self.container_widget)

        self.info_label = QLabel(
            "Here you can see the problem.... I hope!\n"
            "Once the window is resized everything behaves itself.")
        self.info_label.setWordWrap(True)

        self.headings_widget = QWidget()
        self.headings_layout = QGridLayout()
        self.headings_widget.setLayout(self.headings_layout)
        self.headings_layout.setContentsMargins(1,1,0,0)

        self.heading_label1 = QLabel("Column 1")
        self.heading_label1.setContentsMargins(16,0,0,0)
        self.heading_label2 = QLabel("Col 2")
        self.heading_label2.setAlignment(Qt.AlignCenter)
        self.heading_label2.setMaximumWidth(65)
        self.heading_label3 = QLabel("Column 3")
        self.heading_label3.setContentsMargins(8,0,0,0)
        self.headings_layout.addWidget(self.heading_label1,0,0)
        self.headings_layout.addWidget(self.heading_label2,0,1)
        self.headings_layout.addWidget(self.heading_label3,0,2)
        self.headings_widget.setStyleSheet(
            "background: green; border-bottom: 1px solid black;" )

        self.grid_scrollarea = QScrollArea()
        self.grid_widget = QWidget()
        self.grid_layout = QGridLayout()
        self.grid_layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
        self.grid_widget.setLayout(self.grid_layout)
        self.grid_scrollarea.setWidgetResizable(True)
        self.grid_scrollarea.setWidget(self.grid_widget)
        self.grid_scrollarea.setViewportMargins(0,30,0,0)
        self.headings_widget.setParent(self.grid_scrollarea)
        ### Add some linedits to the scrollarea just to test
        rows_to_add = 10
        ## Setting the above to a value greater than will fit in the initial
        ## window will cause the lineedits added below to display correctly,
        ## however - using the 10 above, the lineedits do not expand to fill
        ## the scrollarea's width until you resize the window horizontally.
        ## What's the best way to fix this odd initial behaviour?
        for i in range(rows_to_add):
            col1 = QLineEdit()
            col2 = QLineEdit()
            col2.setMaximumWidth(65)
            col3 = QLineEdit()
            row = self.grid_layout.rowCount()
            self.grid_layout.addWidget(col1,row,0)
            self.grid_layout.addWidget(col2,row,1)
            self.grid_layout.addWidget(col3,row,2)
        ### Define Results group to hold the above sections
        self.test_group = QGroupBox("Results")
        self.test_layout = QVBoxLayout()
        self.test_group.setLayout(self.test_layout)
        self.test_layout.addWidget(self.info_label)
        self.test_layout.addWidget(self.grid_scrollarea)
        ### Add everything to the main layout
        self.container_layout.addWidget(self.test_group)


    def resizeEvent(self, event):
        scrollarea_vpsize = self.grid_scrollarea.viewport().size()
        scrollarea_visible_size = self.grid_scrollarea.rect()
        desired_width = scrollarea_vpsize.width()
        desired_height = scrollarea_visible_size.height()
        desired_height =  desired_height - scrollarea_vpsize.height()
        new_geom = QRect(0,0,desired_width+1,desired_height-1)
        self.headings_widget.setGeometry(new_geom)


def main():
    app = QApplication(sys.argv)
    form = MainWindow()
    form.show()
    app.exec_()

if __name__ == '__main__':
   main()

您所指的方法是否符合这些思路?一切都按预期工作,这正是我所追求的,除了在用户调整窗口大小之前的一些奇怪的初始行为,一旦调整大小,一切都排好了。 我可能又想多了,或者至少忽略了一些东西……有什么想法吗?

【问题讨论】:

  • 在您的测试程序中:注释掉grid_layout.setSizeConstraint 行,似乎可以解决问题。
  • 好的,谢谢。我确信我自己能够解决剩下的小问题(主要是标题初始宽度)。非常感谢您的帮助,并接受了您的原始答案,因为它使我走上了正确的道路。干杯。

标签: python header pyqt pyside qscrollarea


【解决方案1】:

我遇到了类似的问题,但解决方法略有不同。我没有使用一个QScrollArea,而是使用两个并将下部滚动区域的移动向前移动到顶部。下面的代码所做的是

  1. 它在 QVBoxLayout 中创建两个 QScrollArea 小部件。
  2. 它禁用顶部 QScrollArea 的滚动条的可见性并为其分配一个固定的高度。
  3. 使用下部 QScrollArea 的水平滚动条的valueChanged 信号,可以将水平滚动条值从下部 QScrollArea “转发”到顶部,从而在窗口顶部产生一个固定的标题。

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        widget = QWidget()
        self.setCentralWidget(widget)

        vLayout = QVBoxLayout()
        widget.setLayout(vLayout)

        # TOP
        scrollAreaTop = QScrollArea()
        scrollAreaTop.setWidgetResizable(True)
        scrollAreaTop.setFixedHeight(30)
        scrollAreaTop.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scrollAreaTop.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scrollAreaTop.setWidget(QLabel(" ".join([str(i) for i in range(100)])))

        # BOTTOM
        scrollAreaBottom = QScrollArea()
        scrollAreaBottom.setWidgetResizable(True)
        scrollAreaBottom.setWidget(QLabel("\n".join([" ".join([str(i) for i in range(100)]) for _ in range(10)])))
        scrollAreaBottom.horizontalScrollBar().valueChanged.connect(lambda value: scrollAreaTop.horizontalScrollBar().setValue(value))

        vLayout.addWidget(scrollAreaTop)
        vLayout.addWidget(scrollAreaBottom)

【讨论】:

  • 我选择了这个答案,因为当滚动区域内容需要水平滚动时,具有边距技巧的答案不起作用。这非常有效。
【解决方案2】:

你可能有点想多了。

您需要做的就是使用滚动区域视口的几何形状和当前边距来计算要放置在边距中的任何小部件的几何形状。

这些小部件的几何形状也需要在滚动区域的resizeEvent 中更新。

如果您查看QTableView 的源代码,我想您会发现它使用这种方法来管理其标题视图(或类似的东西)。

编辑

为了处理测试用例中的小问题,我建议您阅读QRect 文档中的Coordinates 部分(特别是第三段之后的内容)。

通过像这样重写您的测试用例,我能够更准确地调整大小:

import sys
from PySide.QtCore import *
from PySide.QtGui import *

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setMinimumSize(640, 480)
        self.container_widget = QWidget()
        self.container_layout = QVBoxLayout()
        self.container_widget.setLayout(self.container_layout)
        self.setCentralWidget(self.container_widget)
        self.grid_scrollarea = ScrollArea(self)
        self.test_group = QGroupBox("Results")
        self.test_layout = QVBoxLayout()
        self.test_group.setLayout(self.test_layout)
        self.test_layout.addWidget(self.grid_scrollarea)
        self.container_layout.addWidget(self.test_group)

class ScrollArea(QScrollArea):
    def __init__(self, parent=None):
        QScrollArea.__init__(self, parent)
        self.grid_widget = QWidget()
        self.grid_layout = QGridLayout()
        self.grid_widget.setLayout(self.grid_layout)
        self.setWidgetResizable(True)
        self.setWidget(self.grid_widget)
        # save the margin values
        self.margins = QMargins(0, 30, 0, 0)
        self.setViewportMargins(self.margins)
        self.headings_widget = QWidget(self)
        self.headings_layout = QGridLayout()
        self.headings_widget.setLayout(self.headings_layout)
        self.headings_layout.setContentsMargins(1,1,0,0)
        self.heading_label1 = QLabel("Column 1")
        self.heading_label1.setContentsMargins(16,0,0,0)
        self.heading_label2 = QLabel("Col 2")
        self.heading_label2.setAlignment(Qt.AlignCenter)
        self.heading_label2.setMaximumWidth(65)
        self.heading_label3 = QLabel("Column 3")
        self.heading_label3.setContentsMargins(8,0,0,0)
        self.headings_layout.addWidget(self.heading_label1,0,0)
        self.headings_layout.addWidget(self.heading_label2,0,1)
        self.headings_layout.addWidget(self.heading_label3,0,2)
        self.headings_widget.setStyleSheet(
            "background: green; border-bottom: 1px solid black;" )
        rows_to_add = 10
        for i in range(rows_to_add):
            col1 = QLineEdit()
            col2 = QLineEdit()
            col2.setMaximumWidth(65)
            col3 = QLineEdit()
            row = self.grid_layout.rowCount()
            self.grid_layout.addWidget(col1,row,0)
            self.grid_layout.addWidget(col2,row,1)
            self.grid_layout.addWidget(col3,row,2)

    def resizeEvent(self, event):
        rect = self.viewport().geometry()
        self.headings_widget.setGeometry(
            rect.x(), rect.y() - self.margins.top(),
            rect.width() - 1, self.margins.top())
        QScrollArea.resizeEvent(self, event)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    form = MainWindow()
    form.show()
    sys.exit(app.exec_())

【讨论】:

  • 啊,有趣,谢谢。好的,我可以计算出如何获取几何图形、边距并进行计算以获得所需的大小和定位,以及更新这些。所以我不需要使用布局的 addWidget 方法来包含标题小部件,并且可以创建它,设置几何图形,并使用 setParent 使其也是布局的子级,以确保它在网格时与网格一起被销毁不再需要?我确实看过 QTableView 源代码,1282 行 - 我对 C++ 知之甚少
  • @DazGreen。标题根本不应该是任何布局的一部分。所以只要让它成为grid_widget 的孩子。我所指的QTableView 源代码在updateGeometries 方法中——希望您能找到一个更容易理解的很多
  • 优秀。只要我有能力,回到我的开发机器上,我就会试一试并回复(如果它对我来说很成功,我也会投票并接受这个答案,并且到那时我已经得到了代表)。感谢您的宝贵时间。
  • 添加了一些可运行的示例代码来测试您所说的内容。
  • @DazGreen。抱歉延迟正确回复。请参阅我的更新答案以了解对您的测试用例的一些改进。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多