Search code examples
c++qtselectionqtableview

A QTableView shows selected lines in blue, but using QItemSelectionModel::select they become grey


I'm working on a C++ program that uses Qt 6.6.1 to show a table.

It is possible to click on rows to select them, in which case they are highlighted in blue (maybe the colour is OS-specific; I'm using Windows 10). At a certain point I must be able to save that selection, and later I have to restore it. I have almost managed to do it, but when I restore the selection, those lines are highlighted in grey, instead of blue. I don't understand why, and I'd like to know how to fix it.

Here's a minimal example, written as a single file to make it easier to reproduce:

#include <QApplication>
#include <QTableView>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QGuiApplication>
#include <QScreen>

class TableViewExample : public QWidget
{
    Q_OBJECT

public:
    TableViewExample(QWidget *parent = nullptr)
        : QWidget(parent)
    {
        // Create a custom TableModel
        model = new QStandardItemModel(0, 3, this);
        model->setHeaderData(0, Qt::Horizontal, "Name");
        model->setHeaderData(1, Qt::Horizontal, "Age");
        model->setHeaderData(2, Qt::Horizontal, "City");

        // Populate the model with some rows
        model->appendRow({ new QStandardItem("Alice"),   new QStandardItem("30"), new QStandardItem("London") });
        model->appendRow({ new QStandardItem("Bob"),     new QStandardItem("35"), new QStandardItem("Paris")  });
        model->appendRow({ new QStandardItem("Charles"), new QStandardItem("18"), new QStandardItem("Rome")   });
        model->appendRow({ new QStandardItem("Dave"),    new QStandardItem("28"), new QStandardItem("Berlin") });
        model->appendRow({ new QStandardItem("Emily"),   new QStandardItem("40"), new QStandardItem("Madrid") });

        // Create a QSortFilterProxyModel and set the source model
        proxyModel = new QSortFilterProxyModel(this);
        proxyModel->setSourceModel(model);

        // Create a QTableView and set the proxy model as its model
        tableView = new QTableView(this);
        tableView->setModel(proxyModel);
        tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
        tableView->setSortingEnabled(true);

        // Create buttons for saving and restoring the selection
        QPushButton* saveButton    = new QPushButton("Save Selection", this);
        QPushButton* restoreButton = new QPushButton("Restore Selection", this);

        // Connect the buttons to functions
        connect(saveButton,    &QPushButton::clicked, this, &TableViewExample::saveSelection);
        connect(restoreButton, &QPushButton::clicked, this, &TableViewExample::restoreSelection);

        // Create a layout and add the table view and buttons
        QVBoxLayout* layout = new QVBoxLayout(this);
        layout->addWidget(tableView);
        layout->addWidget(saveButton);
        layout->addWidget(restoreButton);

        setLayout(layout);

        // Make the window large enough to avoid scrollbars
        setGeometry(100, 100, 340, 260);

        // Center on screen
        setGeometry( QStyle::alignedRect( Qt::LeftToRight,
                                          Qt::AlignCenter,
                                          size(),
                                          QGuiApplication::primaryScreen()->availableGeometry() ) );
    }

private slots:
    void saveSelection()
    {
        savedSourceIndices.clear();
        QModelIndexList proxyIndices = tableView->selectionModel()->selectedIndexes();

        for (const QModelIndex& proxyIndex : proxyIndices) {
            QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
            savedSourceIndices.append(sourceIndex);
        }
    }

    void restoreSelection()
    {
        tableView->selectionModel()->clearSelection();

        for (const QModelIndex& savedIndex : savedSourceIndices)
        {
            tableView->selectionModel()->select(proxyModel->mapFromSource(savedIndex), QItemSelectionModel::Select);
            //tableView->setCurrentIndex(proxyModel->mapFromSource(savedIndex));
        }
    }

private:
    QTableView* tableView;
    QStandardItemModel* model;
    QSortFilterProxyModel* proxyModel;
    QModelIndexList savedSourceIndices;
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    TableViewExample example;
    example.show();

    return app.exec();
}

#include "main.moc"

For example, select Bob and Alice: their lines are blue.
The selection I save, with lines highlighted in blue

Click on Save, select Emily, sort by city (there's a proxy, so this is handled), then click on Restore. While it does deselect Emily and re-select Bob and Alice, the lines are grey instead of blue:
The restored selection, with lines highlighted in grey

I found an answer on the Qt Forum which mentions that there's also the index, apart from the selection. I tried changing that too (see the commented line calling setCurrentIndex), but it doesn't work either (and it only supports a single line, whereas I need to be able to select many).

I've run out of ideas. Is this a bug?


Solution

  • What you are seeing is the selection for an unfocused widget. You would see a similar result using a QTextEdit or QPlainTextEdit with some selected text, and then clicking on some other widget: the selection would similarly become gray as soon as the widget loses focus.

    Qt draws the UI based on the application style() (or the widget one), which tries to follow the OS configuration (at least on Windows and macOS), eventually considering the system/app/widget palette.

    By default, the Windows behavior shows the Highlight color role (used as background for selections), considering the color group which is normally based on the current focus state of the widget and its parents (including the window).

    In a standard configuration, the highlight color on Windows is a shade of blue for an active widget, and light gray for inactive/unfocused widgets.

    When a button is clicked, it normally gets focused (due to default its focusPolicy, which is Qt::StrongFocus), meaning that any previously focused widget will possibly become Inactive.

    The selection of the view then becomes gray because the focus was "stolen" by the button. Note that this also happens when you click on the "save" button.

    The simplest solution would be to call setFocus() on the view within the slots connected to the buttons.

    Alternatively, you could override the focus policy of the buttons by using setFocusPolicy() with Qt::NoFocus, or Qt::TabFocus if you want to keep tab navigation.

    Note that focus and focus-aware colors are fundamental aspects of a desktop UI and should always be considered carefully. Since you were unaware of these aspects, I strongly suggest you to do more research on those subjects, including the QPalette/QStyle documentation and general UI conventions.

    You can also check this related question I addressed in the past; it's Python based, but the concepts remain and the code is easily comprehensible for a C++ developer.