Beispiel einer eingefrorenen Spalte

Dieses Beispiel zeigt, wie man eine Spalte in einer QTableView einfriert.

"Screenshot of the example"

Wir verwenden das Model/View-Framework von Qt, um eine Tabelle zu implementieren, deren erste Spalte eingefroren ist. Diese Technik kann auf mehrere Spalten oder Zeilen angewendet werden, solange sie sich am Rand der Tabelle befinden.

Das Model/View-Framework erlaubt es, ein Modell auf unterschiedliche Weise mit mehreren Ansichten darzustellen. In diesem Beispiel verwenden wir zwei Ansichten auf dasselbe Modell - zwei table views, die sich ein Modell teilen. Die eingefrorene Spalte ist ein untergeordnetes Element des Haupttabellenviews, und wir erzeugen den gewünschten visuellen Effekt mithilfe einer Überlagerungstechnik, die in den folgenden Abschnitten Schritt für Schritt beschrieben wird.

Definition der FreezeTableWidget-Klasse

Die Klasse FreezeTableWidget hat einen Konstruktor und einen Destruktor. Außerdem hat sie zwei private Mitglieder: die Tabellenansicht, die wir als Overlay verwenden werden, und das gemeinsame Modell für beide Tabellenansichten. Es werden zwei Slots hinzugefügt, um die Abschnittsgrößen synchron zu halten, sowie eine Funktion, um die Geometrie der eingefrorenen Spalte neu zu justieren. Darüber hinaus werden zwei Funktionen neu implementiert: resizeEvent() und 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);

};

Hinweis: QAbstractItemView ist der Vorgänger von QTableView.

Implementierung der FreezeTableWidget-Klasse

Der Konstruktor nimmt model als Argument und erstellt eine Tabellensicht, die wir zur Anzeige der eingefrorenen Spalte verwenden werden. Innerhalb des Konstruktors rufen wir dann die Funktion init() auf, um die eingefrorene Spalte einzurichten. Schließlich verbinden wir die Signale QHeaderView::sectionResized() (für horizontale und vertikale Überschriften) mit den entsprechenden Slots. Dadurch wird sichergestellt, dass die Abschnitte der eingefrorenen Spalte mit den Kopfzeilen synchronisiert sind. Außerdem verbinden wir die vertikalen Bildlaufleisten miteinander, damit die eingefrorene Spalte mit dem Rest der Tabelle vertikal scrollt.

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);

}

In der Funktion init() stellen wir sicher, dass die Overlay-Tabellenansicht, die für die Anzeige der eingefrorenen Spalte verantwortlich ist, richtig eingerichtet ist. Das bedeutet, dass diese Tabellensicht, frozenTableView, dasselbe Modell haben muss wie die Haupttabellensicht. Der Unterschied besteht jedoch darin, dass die einzige sichtbare Spalte von frozenTableView die erste Spalte ist; die anderen werden mit setColumnHidden() ausgeblendet.

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

      viewport()->stackUnder(frozenTableView);

Was die z-Reihenfolge der eingefrorenen Spalte betrifft, so stapeln wir sie über das Ansichtsfenster. Dies wird durch den Aufruf von stackUnder() auf dem Ansichtsfenster erreicht. Um das Erscheinungsbild zu wahren, verhindern wir, dass die Spalte den Fokus vom Haupt-Tableview stiehlt. Außerdem stellen wir sicher, dass beide Ansichten dasselbe Auswahlmodell verwenden, so dass jeweils nur eine Zelle ausgewählt werden kann. Es wurden noch ein paar andere Änderungen vorgenommen, damit unsere Anwendung gut aussieht und sich konsistent mit dem Haupttableview verhält. Beachten Sie, dass wir updateFrozenTableGeometry() aufgerufen haben, um die Spalte an der richtigen Stelle zu platzieren.

      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);
}

Wenn Sie die Größe der eingefrorenen Spalte ändern, muss die gleiche Spalte in der Haupttabelle entsprechend angepasst werden, um eine nahtlose Integration zu gewährleisten. Dazu wird die neue Größe der Spalte aus dem Wert newSize des Signals sectionResized() abgeleitet, das sowohl von der horizontalen als auch von der vertikalen Kopfzeile ausgegeben wird.

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);
}

Da die Breite der eingefrorenen Spalte geändert wird, passen wir die Geometrie des Widgets durch den Aufruf von updateFrozenTableGeometry() entsprechend an. Diese Funktion wird weiter unten erklärt.

In unserer Neuimplementierung von QTableView::resizeEvent() rufen wir updateFrozenTableGeometry() auf, nachdem wir die Implementierung der Basisklasse aufgerufen haben.

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

Wenn wir mit der Tastatur in der Tabelle navigieren, müssen wir sicherstellen, dass die aktuelle Auswahl nicht hinter der eingefrorenen Spalte verschwindet. Um dies zu synchronisieren, reimplementieren wir QTableView::moveCursor() und passen die Positionen der Bildlaufleisten bei Bedarf an, nachdem wir die Implementierung der Basisklasse aufgerufen haben.

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;
}

Die Berechnung der Geometrie der eingefrorenen Spalte basiert auf der Geometrie der darunter liegenden Tabelle, so dass sie immer an der richtigen Stelle erscheint. Die Verwendung der Funktion QFrame::frameWidth() hilft bei der korrekten Berechnung dieser Geometrie, unabhängig davon, welcher Stil verwendet wird. Wir verlassen uns auf die Geometrie des Ansichtsfensters und der Überschriften, um die Grenzen für die eingefrorene Spalte festzulegen.

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

Beispielprojekt @ code.qt.io

© 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.