You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在QScrollArea中实现首行首列部件冻结 类似Excel冻结窗格

实现方案(PyQt5为例,Qt C++逻辑完全一致)

完全可以实现,核心思路是拆分滚动区域与冻结标签区域,通过滚动条信号同步偏移量即可实现类Excel的冻结效果,无需自定义重绘控件。

实现逻辑

  • 拆分4个独立布局模块:
    • 左上角空白占位块,尺寸和冻结行高、冻结列宽保持一致
    • 顶部冻结行标签容器:仅允许水平方向偏移,垂直位置固定
    • 左侧冻结列标签容器:仅允许垂直方向偏移,水平位置固定
    • 中间核心网格滚动区域:放置所有功能按钮,支持横竖滚动
  • 信号绑定规则:
    • 滚动区域水平滚动条的valueChanged信号触发时,同步修改顶部行标签容器的水平偏移量
    • 滚动区域垂直滚动条的valueChanged信号触发时,同步修改左侧列标签容器的垂直偏移量
  • 尺寸绑定:冻结标签的行高/列宽和网格区域对应行/列的尺寸绑定,保证滚动时标签和按钮完全对齐

可运行示例代码

import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QGridLayout, QPushButton, 
                             QLabel, QScrollArea, QHBoxLayout, QVBoxLayout)
from PyQt5.QtCore import Qt

class FrozenGridDemo(QWidget):
    def __init__(self, rows=20, cols=15, btn_size=60):
        super().__init__()
        self.rows = rows
        self.cols = cols
        self.btn_size = btn_size
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("冻结行列网格示例")
        main_layout = QVBoxLayout(self)
        main_layout.setContentsMargins(0,0,0,0)
        main_layout.setSpacing(0)

        # 顶部水平布局:放左上角占位 + 顶部行标签
        top_layout = QHBoxLayout()
        top_layout.setContentsMargins(0,0,0,0)
        top_layout.setSpacing(0)

        # 左上角空白占位
        corner_placeholder = QWidget()
        corner_placeholder.setFixedSize(self.btn_size, self.btn_size)
        corner_placeholder.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;")
        top_layout.addWidget(corner_placeholder)

        # 顶部行标签容器
        self.header_row_widget = QWidget()
        self.header_row_layout = QHBoxLayout(self.header_row_widget)
        self.header_row_layout.setContentsMargins(0,0,0,0)
        self.header_row_layout.setSpacing(0)
        # 生成列标签
        for col in range(self.cols):
            label = QLabel(f"列{col+1}")
            label.setFixedSize(self.btn_size, self.btn_size)
            label.setAlignment(Qt.AlignCenter)
            label.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;")
            self.header_row_layout.addWidget(label)
        # 顶部标签总宽度
        self.header_row_widget.setFixedWidth(self.cols * self.btn_size)
        top_layout.addWidget(self.header_row_widget)
        main_layout.addLayout(top_layout)

        # 下方水平布局:放左侧列标签 + 中间滚动区域
        bottom_layout = QHBoxLayout()
        bottom_layout.setContentsMargins(0,0,0,0)
        bottom_layout.setSpacing(0)

        # 左侧列标签容器
        self.header_col_widget = QWidget()
        self.header_col_layout = QVBoxLayout(self.header_col_widget)
        self.header_col_layout.setContentsMargins(0,0,0,0)
        self.header_col_layout.setSpacing(0)
        # 生成行标签
        for row in range(self.rows):
            label = QLabel(f"行{row+1}")
            label.setFixedSize(self.btn_size, self.btn_size)
            label.setAlignment(Qt.AlignCenter)
            label.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;")
            self.header_col_layout.addWidget(label)
        # 左侧标签总高度
        self.header_col_widget.setFixedHeight(self.rows * self.btn_size)
        bottom_layout.addWidget(self.header_col_widget)

        # 中间滚动区域
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidgetResizable(False)
        # 滚动区域内部网格容器
        self.grid_container = QWidget()
        self.grid_layout = QGridLayout(self.grid_container)
        self.grid_layout.setContentsMargins(0,0,0,0)
        self.grid_layout.setSpacing(0)
        # 生成按钮
        for row in range(self.rows):
            for col in range(self.cols):
                btn = QPushButton(f"{row+1},{col+1}")
                btn.setFixedSize(self.btn_size, self.btn_size)
                self.grid_layout.addWidget(btn, row, col)
        self.grid_container.setFixedSize(self.cols*self.btn_size, self.rows*self.btn_size)
        self.scroll_area.setWidget(self.grid_container)
        bottom_layout.addWidget(self.scroll_area)
        main_layout.addLayout(bottom_layout)

        # 绑定滚动信号同步偏移
        self.scroll_area.horizontalScrollBar().valueChanged.connect(self.sync_header_row)
        self.scroll_area.verticalScrollBar().valueChanged.connect(self.sync_header_col)

        self.resize(800, 600)

    def sync_header_row(self, value):
        # 顶部行标签同步水平偏移
        self.header_row_widget.move(-value, self.header_row_widget.y())

    def sync_header_col(self, value):
        # 左侧列标签同步垂直偏移
        self.header_col_widget.move(self.header_col_widget.x(), -value)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    demo = FrozenGridDemo()
    demo.show()
    sys.exit(app.exec_())

自定义修改说明

  • 调整构造函数的rows、cols参数可以修改网格的行列数
  • 修改btn_size参数可以调整按钮和标签的尺寸
  • 替换按钮为其他自定义控件即可适配不同业务需求

内容的提问来源于stack exchange,提问作者Nevermore

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 14:45:03