Ejemplo de columna congelada
Este ejemplo muestra cómo congelar una columna en QTableView.

Utilizamos el framework modelo/vista de Qt para implementar una tabla con su primera columna congelada. Esta técnica puede aplicarse a varias columnas o filas, siempre que se encuentren en el borde de la tabla.
El marco de trabajo modelo/vista permite mostrar un modelo de diferentes maneras utilizando múltiples vistas. En este ejemplo, utilizamos dos vistas sobre el mismo modelo: dos table views que comparten un modelo. La columna congelada es hija de la vista de tabla principal, y proporcionamos el efecto visual deseado utilizando una técnica de superposición que se describirá paso a paso en las próximas secciones.

Definición de la clase FreezeTableWidget
La clase FreezeTableWidget tiene un constructor y un destructor. Además, tiene dos miembros privados: la vista de tabla que utilizaremos como superposición, y el modelo compartido para ambas vistas de tabla. Se añaden dos ranuras para ayudar a mantener sincronizados los tamaños de las secciones, así como una función para reajustar la geometría de la columna congelada. Además, reimplementamos dos funciones: resizeEvent() y 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); };
Nota: QAbstractItemView es el ancestro de QTableView.
Implementación de la clase FreezeTableWidget
El constructor toma model como argumento y crea una vista de tabla que utilizaremos para mostrar la columna congelada. Luego, dentro del constructor, invocamos la función init() para configurar la columna congelada. Por último, conectamos las señales QHeaderView::sectionResized() (para cabeceras horizontales y verticales) a las ranuras correspondientes. Esto garantiza que las secciones de nuestra columna congelada estén sincronizadas con las cabeceras. También conectamos las barras de desplazamiento vertical para que la columna congelada se desplace verticalmente con el resto de la tabla.
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); }
En la función init(), nos aseguramos de que la vista de tabla superpuesta responsable de mostrar la columna congelada esté configurada correctamente. Esto significa que esta vista de tabla, frozenTableView, tiene que tener el mismo modelo que la vista de tabla principal. Sin embargo, la diferencia es la siguiente: la única columna visible de frozenTableView es la primera; ocultamos las demás mediante setColumnHidden()
void FreezeTableWidget::init() { frozenTableView->setModel(model()); frozenTableView->setFocusPolicy(Qt::NoFocus); frozenTableView->verticalHeader()->hide(); frozenTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); viewport()->stackUnder(frozenTableView);
En cuanto al orden z de la columna congelada, la apilamos en la parte superior de la ventana gráfica. Esto se consigue llamando a stackUnder() en la ventana gráfica. Para mantener la apariencia, evitamos que la columna robe el foco de la vista principal de la tabla. Además, nos aseguramos de que ambas vistas comparten el mismo modelo de selección, por lo que sólo se puede seleccionar una celda a la vez. Algunos otros ajustes se hacen para que nuestra aplicación se vea bien y se comporte de manera coherente con la vista de tabla principal. Observe que hemos llamado a updateFrozenTableGeometry() para que la columna ocupe el lugar correcto.
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); }
Cuando se cambia el tamaño de la columna congelada, la misma columna en la vista de tabla principal debe cambiar de tamaño en consecuencia, para proporcionar una integración perfecta. Esto se consigue obteniendo el nuevo tamaño de la columna a partir del valor newSize de la señal sectionResized(), emitida tanto por la cabecera horizontal como por la vertical.
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); }
Dado que se modifica la anchura de la columna congelada, ajustamos la geometría del widget en consecuencia invocando a updateFrozenTableGeometry(). Esta función se explica con más detalle a continuación.
En nuestra reimplementación de QTableView::resizeEvent(), llamamos a updateFrozenTableGeometry() después de invocar la implementación de la clase base.
void FreezeTableWidget::resizeEvent(QResizeEvent * event) { QTableView::resizeEvent(event); updateFrozenTableGeometry(); }
Cuando navegamos por la tabla con el teclado, necesitamos asegurarnos de que la selección actual no desaparece detrás de la columna congelada. Para sincronizar esto, reimplementamos QTableView::moveCursor() y ajustamos las posiciones de la barra de desplazamiento si es necesario, después de llamar a la implementación de la clase base.
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; }
El cálculo de la geometría de la columna congelada se basa en la geometría de la tabla que hay debajo, por lo que siempre aparece en el lugar correcto. El uso de la función QFrame::frameWidth() ayuda a calcular correctamente esta geometría, independientemente del estilo que se utilice. Nos basamos en la geometría de la ventana gráfica y las cabeceras para establecer los límites de la columna congelada.
void FreezeTableWidget::updateFrozenTableGeometry() { frozenTableView->setGeometry(verticalHeader()->width() + frameWidth(), frameWidth(), columnWidth(0), viewport()->height()+horizontalHeader()->height()); }
© 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.