如何在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
相关产品推荐
相关产品推荐

