冻结列示例
本例演示如何冻结QTableView 中的一列。
我们使用 Qt 的模型/视图框架来实现冻结第一列的表格。只要这些列或行位于表格的边缘,就可以将此技术应用于多个列或行。
模型/视图框架允许使用多个视图以不同方式显示一个模型。在本例中,我们在同一个模型上使用了两个视图--两个table views 共享一个模型。冻结列是主表格视图的子视图,我们使用叠加技术来提供所需的视觉效果,接下来的章节将逐步介绍这种技术。
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); };
注: 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()
来相应调整 widget 的几何形状。下文将进一步解释该函数。
在对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()); }
© 2025 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.