如何向 QScrollArea 添加固定标题?

发布于 2025-01-07 20:36:30 字数 5398 浏览 0 评论 0 原文

我目前有一个 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

<块引用> <块引用>

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

但我一生都无法弄清楚如何真正实现这一目标,要么我的 GoogleFu 今天抛弃了我,要么很少有关于如何实现这一点的信息/示例实际上实现了这一点。

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

我刚刚学习 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()

这些内容是否就是您所指向的方法?一切都按预期工作,这正是我所追求的,除了用户调整窗口大小之前的一些奇怪的初始行为,一旦调整大小,一切都会排列起来并且很好。 我可能又想太多了,或者至少忽略了一些事情……有什么想法吗?

I currently have a QScrollArea defined by:

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)

which sits quite happily nested within other layouts/widgets, resizes as expected, etc.

To provide headings for the grid columns, I'm using another QGridLayout positioned directly above the scroll area - this works... but looks a little odd, even when styled appropriately, especially when the on-demand (vertical) scrollbar appears or disappears as needed and the headers no longer line up correctly with the grid columns. It's an aesthetic thing I know... but I'm kinda picky ;)

Other widgets are added/removed to the self.results_grid_layout programatically elsewhere. The last line above I've just recently added as I thought it would be easy to use the created margin area, the docs for setViewportMargins state:

Sets margins around the scrolling area. This is useful for applications such as spreadsheets with "locked" rows and columns. The marginal space is is left blank; put widgets in the unused area.

But I cannot for the life of me work out how to actually achieve this, and either my GoogleFu has deserted me today, or there's little information/examples out there on how to actually achieve this.

My head is telling me I can assign just one widget, controlled by a layout (containing any number of other widgets) to the scrollarea - as I have done. If I add say a QHeaderview for example to row 0 of the gridlayout, it will just appear below the viewport's margin and scroll with the rest of the layout? Or am I missing something and just can't see the wood for the trees?

I'm just learning Python/Qt, so any help, pointers and/or examples (preferably with Python but not essential) would be appreciated!


Edit: Having followed the advice given so far (I think), I came up with the following little test program to try things out:

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()

Is something along these lines the method to which you were pointing? Everything works as expected as is exactly what I was after, except for some odd initial behaviour before the window is resized by the user, once it is resized everything lines up and is fine.
I'm probably over-thinking again or at least overlooking something... any thoughts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

后知后觉 2025-01-14 20:36:30

你可能有点想太多了。

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

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

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

编辑

为了处理测试用例中的小尺寸调整问题,我建议您阅读QRect 文档中的坐标部分(特别是第三段开始)。

我能够通过重写您的测试用例来获得更准确的大小调整,如下所示:

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_())

You may be over-thinking things slightly.

All you need to do is use the geometry of the scrollarea's viewport and the current margins to calculate the geometry of any widgets you want to place in the margins.

The geometry of these widgets would also need to be updated in the resizeEvent of the scrollarea.

If you look at the source code for QTableView, I think you'll find it uses this method to manage its header-views (or something very similar).

EDIT

To deal with the minor resizing problems in your test case, I would advise you to read the Coordinates section in the docs for QRect (in particular, the third paragraph onwards).

I was able to get more accurate resizing by rewriting your test case like this:

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_())
有深☉意 2025-01-14 20:36:30

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

  1. 在 QVBoxLayout 中创建两个 QScrollArea 小部件。
  2. 它禁用顶部 QScrollArea 滚动条的可见性并为其分配固定高度。
  3. 使用下方水平滚动条的 valueChanged 信号QScrollArea 可以将水平滚动条值从较低的 QScrollArea“转发”到顶部的 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)

窗口结果来自上面的代码。

I had a similar problem and solved it a little differently. Instead of using one QScrollArea I use two and forward a movement of the lower scroll area to the top one. What the code below does is

  1. It creates two QScrollArea widgets in a QVBoxLayout.
  2. It disables the visibility of the scroll bars of the top QScrollArea and assigns it a fixed height.
  3. Using the valueChanged signal of the horizontal scroll bar of the lower QScrollArea it is possible to "forward" the horizontal scroll bar value from the lower QScrollArea to the top one resulting a fixed header at the top of the window.

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)

Window resulting from above code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文