冻结列示例

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

"Screenshot of the example"

我们使用Qt的模型/视图框架来实现一个具有第一列冻结的表格。此技术可以应用于多个列或行,只要它们在表格的边缘。

模型/视图框架允许一个模型通过多个视图以不同的方式进行显示。对于这个示例,我们在同一个模型上使用两个视图 - 两个共享一个模型的tableViews。冻结列是主的一个子项,我们使用叠加技术提供所需的视觉效果,这些技术将在接下来的几节中逐步描述。

FreezeTableWidget 类定义

FreezeTableWidget 类有一个构造函数和一个析构函数。还有一个两个私有成员:我们将用作叠加的表格视图和两个表格视图的共享模型。添加了两个槽来帮助同步区域大小,以及一个调整冻结列几何形状的功能。此外,我们还重写了两个函数: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);

};

注意:QAbstractItemViewQTableView 的祖先。

FreezeTableWidget 类实现

构造函数接受 模型 作为参数并创建一个表格视图,我们将使用它来显示冻结列。然后在构造函数中,我们调用 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

© 2024 Qt 公司有限公司。本文件中包含的文档贡献属于其各自的版权所有者。所提供的文档是根据自由软件基金会发布的 GNU 自由文档许可证版本 1.3 的条款许可的。Qt及其相关标志是芬兰和/或全世界 Qt 公司的商标。所有其他商标均为其各自所有者的财产。