Change color highlight of icons in QTableView, when the cell is selected

Learn change color highlight of icons in qtableview, when the cell is selected with practical examples, diagrams, and best practices. Covers c++, user-interface, qtableview development techniques w...

Customizing QTableView Icon Highlights on Selection

Hero image for Change color highlight of icons in QTableView, when the cell is selected

Learn how to prevent QTableView from changing the color of icons when a cell is selected, maintaining visual consistency in your Qt applications.

When working with QTableView in Qt, a common challenge arises when you use icons within cells. By default, QTableView's selection mechanism often applies a highlight color that can obscure or alter the appearance of your carefully chosen icons. This article provides a comprehensive guide on how to customize the rendering of icons in selected cells, ensuring they retain their original colors and clarity, thereby enhancing the user experience and visual integrity of your application.

Understanding QTableView's Default Selection Behavior

By default, QTableView uses a QStyledItemDelegate to draw items. When a cell is selected, the delegate typically draws a selection background and adjusts the foreground (text and icon) color to ensure readability against the new background. This adjustment often involves inverting colors or applying a tint, which can be undesirable for custom icons that rely on specific color palettes for their meaning or branding. To override this, we need to implement a custom delegate that takes control over how selected items are drawn.

flowchart TD
    A[QTableView Selection Event] --> B{QStyledItemDelegate::paint()}
    B --> C{Is Item Selected?}
    C -- Yes --> D[Draw Selection Background]
    D --> E[Adjust Foreground Color (Text/Icon)]
    E --> F[Draw Text/Icon with Adjusted Color]
    C -- No --> G[Draw Text/Icon with Default Color]
    F --> H[Render Cell]
    G --> H

Default QTableView Item Painting Flow

Implementing a Custom Item Delegate

The most robust way to control the rendering of items in QTableView is to subclass QStyledItemDelegate and override its paint() method. Inside paint(), you gain full control over how each item is drawn, including its background, text, and icon. The key is to detect if the item is selected and, if so, draw the icon without applying the default selection color transformation.

#include <QStyledItemDelegate>
#include <QPainter>
#include <QIcon>

class CustomIconDelegate : public QStyledItemDelegate
{
public:
    explicit CustomIconDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        QStyleOptionViewItem opt = option;
        initStyleOption(&opt, index);

        painter->save();

        // Draw background (including selection background)
        opt.icon = QIcon(); // Prevent default icon drawing by base class
        QStyledItemDelegate::paint(painter, opt, index);

        // Draw icon manually, ignoring selection state for icon color
        if (!opt.icon.isNull()) {
            QIcon::Mode iconMode = QIcon::Normal;
            // You might want to adjust icon state based on selection for visual feedback
            // For example, QIcon::Selected, but we want to keep original color.
            // So, we stick to QIcon::Normal or QIcon::Active if needed.

            QPixmap pixmap = opt.icon.pixmap(opt.decorationSize, iconMode, QIcon::Off);

            // Calculate icon rectangle
            QRect iconRect = QStyle::alignedRect(
                opt.direction,
                opt.decorationAlignment,
                opt.decorationSize,
                opt.rect
            );

            painter->drawPixmap(iconRect, pixmap);
        }

        // Draw text manually, potentially adjusting color for selection
        // If you want text to also ignore selection color, remove the if (opt.state & QStyle::State_Selected) block
        if (!opt.text.isEmpty()) {
            QColor textColor = opt.palette.text().color();
            if (opt.state & QStyle::State_Selected) {
                textColor = opt.palette.highlightedText().color(); // Use highlighted text color for readability
            }
            painter->setPen(textColor);

            QRect textRect = QStyle::alignedRect(
                opt.direction,
                opt.textAlignment,
                opt.fontMetrics.size(Qt::TextSingleLine, opt.text),
                opt.rect
            );

            // Adjust textRect if icon is present to avoid overlap
            if (!opt.icon.isNull()) {
                int iconWidth = opt.decorationSize.width();
                if (opt.decorationAlignment & Qt::AlignLeft) {
                    textRect.setLeft(textRect.left() + iconWidth + opt.decorationMargin);
                } else if (opt.decorationAlignment & Qt::AlignRight) {
                    textRect.setRight(textRect.right() - iconWidth - opt.decorationMargin);
                }
            }

            painter->drawText(textRect, opt.displayAlignment, opt.text);
        }

        painter->restore();
    }
};

Custom QStyledItemDelegate to control icon and text drawing.

Applying the Custom Delegate to QTableView

Once your custom delegate is implemented, you need to assign it to your QTableView instance. You can set it for the entire view using setItemDelegate() or for specific columns using setItemDelegateForColumn(). For icons that appear in all columns, setting it for the entire view is usually sufficient.

// In your QTableView setup code:
QTableView *tableView = new QTableView(this);
// ... set model for tableView ...

CustomIconDelegate *delegate = new CustomIconDelegate(tableView);
tableView->setItemDelegate(delegate);

// If you only want this behavior for a specific column (e.g., column 0):
// tableView->setItemDelegateForColumn(0, delegate);

Assigning the custom delegate to a QTableView.

1. Create a Custom Delegate Class

Subclass QStyledItemDelegate and override its paint() method. This method will be responsible for drawing each cell's content.

2. Handle Background Drawing

Inside paint(), call the base class's paint() method first, but clear the icon from the QStyleOptionViewItem to prevent the default icon drawing. This ensures the selection background is drawn correctly.

3. Draw Icon Manually

Extract the QIcon from the QModelIndex using index.data(Qt::DecorationRole). Then, use QIcon::pixmap() with QIcon::Normal mode to get the icon's original appearance, and draw it using painter->drawPixmap().

4. Draw Text Manually (Optional Color Adjustment)

Extract the text using index.data(Qt::DisplayRole). You can choose to draw it with the default highlightedText() color for selected items or keep its original color, similar to the icon. Use painter->drawText().

5. Apply the Delegate

Instantiate your custom delegate and set it on your QTableView using setItemDelegate() or setItemDelegateForColumn().