本页内容

冻结列示例

本示例演示了如何在QTableView 中冻结一列。

应用程序中显示表格中某列被冻结

我们使用 Qt 的模型/视图框架来实现一个第一列被固定的表格。只要位于表格边缘,该技术即可应用于多列或多行。

模型/视图框架允许通过多个视图以不同方式显示同一个模型。在本示例中,我们针对同一个模型使用了两个视图——两个共享同一模型的table views 。固定列是主 的子视图,我们通过叠加技术实现了所需的视觉效果,后续章节将分步详细说明该技术。

表格视图、表头和滚动条的布局

FreezeTableWidget 类定义

FreezeTableWidget 类有一个构造函数和一个析构函数。此外,它还有两个私有成员:我们将用作叠加层的表格视图,以及供两个表格视图共用的共享模型。 添加了两个槽(slot)以帮助保持各部分尺寸同步,并提供了一个函数用于重新调整冻结列的几何尺寸。此外,我们重实现了两个函数:resizeEvent() 和moveCursor()。

class FreezeTableWidget : public QTableView {
     Q_OBJECT

public:
      FreezeTableWidget(QAbstractItemModel * model);
      ~FreezeTableWidget();

protected:
      void resizeEvent(QResizeEvent *event) override;
      QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
      void scrollTo (const QModelIndex & index, ScrollHint hint = EnsureVisible) override;

private:
      QTableView *frozenTableView;
      void init();
      void updateFrozenTableGeometry();

private slots:
      void updateSectionWidth(int logicalIndex, int oldSize, int newSize);
      void updateSectionHeight(int logicalIndex, int oldSize, int newSize);

};

注意: QAbstractItemView 是QTableView 的基类。

FreezeTableWidget 类的实现

构造函数将model 作为参数,并创建一个表格视图,用于显示冻结列。然后,在构造函数内部,我们调用init() 函数来设置冻结列。 最后,我们将QHeaderView::sectionResized()信号(用于水平和垂直标题)连接到相应的槽中。这确保了冻结列的各部分与标题保持同步。我们还连接了垂直滚动条,以便冻结列能与表格的其余部分一起垂直滚动。

FreezeTableWidget::FreezeTableWidget(QAbstractItemModel * model)
{
      setModel(model);
      frozenTableView = new QTableView(this);

      init();

      //connect the headers and scrollbars of both tableviews together
      connect(horizontalHeader(),&QHeaderView::sectionResized, this,
              &FreezeTableWidget::updateSectionWidth);
      connect(verticalHeader(),&QHeaderView::sectionResized, this,
              &FreezeTableWidget::updateSectionHeight);

      connect(frozenTableView->verticalScrollBar(), &QAbstractSlider::valueChanged,
              verticalScrollBar(), &QAbstractSlider::setValue);
      connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
              frozenTableView->verticalScrollBar(), &QAbstractSlider::setValue);

}

在init() 函数中,我们确保负责显示固定列的覆盖表视图已正确设置。这意味着该表视图frozenTableView 必须与主表视图使用相同的模型。但这里的区别在于:frozenTableView 中唯一可见的列是第一列;我们通过setColumnHidden()隐藏了其余列

void FreezeTableWidget::init()
{
      frozenTableView->setModel(model());
      frozenTableView->setFocusPolicy(Qt::NoFocus);
      frozenTableView->verticalHeader()->hide();
      frozenTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);

      viewport()->stackUnder(frozenTableView);

关于固定列的 z 顺序,我们将其堆叠在视口之上。这是通过对视口调用stackUnder() 实现的。出于界面美观的考虑,我们防止该列抢占主表格视图的焦点。 此外,我们确保两个视图共享同一选中模型,因此每次只能选中一个单元格。还进行了一些其他调整,以确保应用程序外观美观,且与主表视图的行为保持一致。请注意,我们调用了updateFrozenTableGeometry() 来使该列占据正确的位置。

      frozenTableView->setStyleSheet("QTableView { border: none;"
                                     "background-color: #8EDE21;"
                                     "selection-background-color: #999}"); //for demo purposes
      frozenTableView->setSelectionModel(selectionModel());
      for (int col = 1; col < model()->columnCount(); ++col)
            frozenTableView->setColumnHidden(col, true);

      frozenTableView->setColumnWidth(0, columnWidth(0) );

      frozenTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
      frozenTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
      frozenTableView->show();

      updateFrozenTableGeometry();

      setHorizontalScrollMode(ScrollPerPixel);
      setVerticalScrollMode(ScrollPerPixel);
      frozenTableView->setVerticalScrollMode(ScrollPerPixel);
}

当您调整固定列的大小时,主表格视图中的对应列必须相应调整大小,以实现无缝集成。这是通过从水平和垂直标题栏发出的sectionResized()信号中获取newSize 值,从而获取列的新尺寸来实现的。

void FreezeTableWidget::updateSectionWidth(int logicalIndex, int /* oldSize */, int newSize)
{
      if (logicalIndex == 0){
            frozenTableView->setColumnWidth(0, newSize);
            updateFrozenTableGeometry();
      }
}

void FreezeTableWidget::updateSectionHeight(int logicalIndex, int /* oldSize */, int newSize)
{
      frozenTableView->setRowHeight(logicalIndex, newSize);
}

由于冻结列的宽度发生了变化,我们通过调用updateFrozenTableGeometry() 相应地调整控件的几何属性。该函数将在下文进一步说明。

在我们对QTableView::resizeEvent() 的重写中,我们在调用基类实现后,会再次调用updateFrozenTableGeometry() 。

void FreezeTableWidget::resizeEvent(QResizeEvent * event)
{
      QTableView::resizeEvent(event);
      updateFrozenTableGeometry();
 }

当使用键盘在表格中导航时,我们需要确保当前选中的区域不会被冻结列遮挡。为了实现同步,我们在调用基类实现后,重写QTableView::moveCursor() 函数,并在必要时调整滚动条的位置。

QModelIndex FreezeTableWidget::moveCursor(CursorAction cursorAction,
                                          Qt::KeyboardModifiers modifiers)
{
      QModelIndex current = QTableView::moveCursor(cursorAction, modifiers);

      if (cursorAction == MoveLeft && current.column() > 0
              && visualRect(current).topLeft().x() < frozenTableView->columnWidth(0) ){
            const int newValue = horizontalScrollBar()->value() + visualRect(current).topLeft().x()
                                 - frozenTableView->columnWidth(0);
            horizontalScrollBar()->setValue(newValue);
      }
      return current;
}

固定列的几何计算基于其下方的表格几何,因此它总能显示在正确的位置。使用QFrame::frameWidth() 函数有助于正确计算该几何,无论采用何种样式。我们依靠视口和表头的几何来设置固定列的边界。

void FreezeTableWidget::updateFrozenTableGeometry()
{
      frozenTableView->setGeometry(verticalHeader()->width() + frameWidth(),
                                   frameWidth(), columnWidth(0),
                                   viewport()->height()+horizontalHeader()->height());
}

示例项目 @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.