Custom Qt Table Widget with Row Hover, Selection, and Column Sorting Support

Table of Contents- I. Quick Humor

  • II. Introduction
  • III. Feature Demonstration
  • IV. Implementation Overview
  • V. Custom Data Source
    1. data() function
    1. flags() function
  • VI. Custom View
    1. Objectives
    1. Problem Analysis
  • VII. Testing
  • VIII. Related Articles

Original link: Custom Qt Table Widget with Row Hover, Selecsion, and Column Sorting Support

I. Quick Humor

A husband and wife were walking home at night when three masked men with knives suddenly jumped out: "Kidnapping! One of you can go, wait for news at home."

The husband immediately pushed his wife away: "Honey, go quickly!" After the wife was far away, the three masked men took off their masks: "Damn, is it so hard to find you for a mahjong game now?"

Five minutes later, the husband called his wife: "Transfer 5,000 to the card, don't call the police, they said they'll release me tomorrow morning after keeping me overnight." Ten minutes later, the husband withdrew 5,000 from the card and fought until dawn.

The next day, the husband returned home, and the wife threw herself on him with tears: "Only in critical moments can you see how good you are to me, husband! From now on, I'll listen to everything you say!

II. Introduction

After that joke, let's get to the main topic.

This article presents a simple table widget implementation that customizes hover and selection behaviors, allowing users to set their own colors and enabling sorting on specific columns.

Why write this article? I've been using Qt for several years and have gained considerable experience with it. Recently, while working on table-related features, I found that many online articles don't explain things very well, so I'm sharing a simple example I created, hoping it will help those who need it.

In this article, we've encapsulated the following table features. The content is quite limited, and students with deep customization needs can contact me via QQ for detailed discussion.

  1. Hover row background color
  2. Selected row background color
  3. Column sortability
  4. Column width drag range
  5. Precise hover state positioning

III. Feature Demonstration

As shown in the GIF below, here's a simple demonstration of the effects.

The color scheme used here is just a quick mockup, the color scheme can only be described as average, but focus on the effects and implementation approach.

IV. Implementation Overview

Those who have used QTableView or are familiar with Qt widgets should know that tables are very powerful controls that support various functionalities. I've previously written several articles about table widgets:

  1. Qt Table Widget Implementation - Supporting Multi-level Headers, Multi-row Headers, Cell Merging, and Font Settings
  2. Qt Excel-like Table Component - Supporting Frozen Columns, Frozen Rows, Content Adaptation, and Merged Cells
  3. Property Browser Widget QtTreePropertyBrowser Compiled as Dynamic Library (Designer Plugin)
  4. Super Practical Property Browser Widget - QtTreePropertyBrowser
  5. Qt Table Widget Ant Trail

Of course, table widget usage goes far beyond this. I'll continue to release more useful and interesting features when time and energy permit.

The implementation of this article's features is also quite simple. It took me about half a day to write this demo. The code volume isn't large; most of the time was spent designing interfaces and refactoring logical functions.

As shown below, this is the entire project directory structure, where we mainly rewrote the data source model and view.

Now let's discuss what the model and view have accomplished respectively.

V. Custom Data Source

The data source, in essence, is the place that provides data to the view. Here we took a shortcut by directly inheriting from QStandardItemModel, so we don't need to worry about most data storage and retrieval work, as QStandardItemModel has already done a thorough job.

Here we mainly rewrote two interfaces:

virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

virtual Qt::ItemFlags flags(const QModelIndex & index) const override;

1. data() function

The view's data acquisition interface is data(), so we must override this interface. The implementation is quite simple, but requires a basic understanding of Qt's MVC pattern.

QVariant CustomTableModel::data(const QModelIndex & index, int role /*= Qt::DisplayRole*/) const
{
    if (Qt::BackgroundRole == role)
    {
        if (index.row() == m_hoverRow)
        {
            return m_hoverColor;
        }
    }
    else if (role == Qt::ForegroundRole)
    {
        if (index.row() == m_hoverRow)
        {
            return m_hoverColor.lighter(255);
        }
    }

    return QStandardItemModel::data(index, role);
}

Looking at the above code, when hovering over a row, we need to return a custom color value; otherwise, we can proceed with the default processing. It's quite simple, isn't it?

The implementation of the selected state cannot be placed here because when an item is selected, its background color is not obtained from Qt::BackgroundRole, and its foreground color is not obtained from Qt::ForegroundRole, so we cannot handle it here.

If we really want to implement the selected state here, there is a way. We can override the flags() function to make items unselectable. I've tried this method, and it works. However, there's a hidden issue: if we want our items to be selectable later, it becomes quite troublesome.

Based on the above considerations, we only need to override the flags() function like this, and then return the background and foreground colors of the selected row in the data() function.

Qt::ItemFlags CustomTableModel::flags(const QModelIndex & index) const
{
    return Qt::ItemIsEnabled;
}

2. flags() function

The flags() function was already shown in the previous section, with items in an enabled state. Here we also need to add the selectable state to avoid other pitfalls.

Qt::ItemFlags CustomTableModel::flags(const QModelIndex & index) const
{
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

That's all for the model function overrides, quite simple. Below is the header file for the model.

/**
* Introduction: Mainly provides interfaces for hover rows
*/
class CustomTableModel : public QStandardItemModel
{
    Q_OBJECT

public:
    explicit CustomTableModel(QObject * parent = nullptr);
    ~CustomTableModel();

private:
    // Hover background color - not recommended for direct external calls
    void setHoverColor(const QColor & color);
    QColor hoverColor() const { return m_hoverColor; }

    // Set current hover row - not recommended for direct external calls
    void setHoverRow(int row);
    int hoverRow() const { return m_hoverRow; }

protected:
    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

    virtual Qt::ItemFlags flags(const QModelIndex & index) const override;

private:
    int m_hoverRow = -1; // No hover
    QColor m_hoverColor = QColor(20, 22, 23);

    friend class CustomTableView;
};

VI. Custom View

Now let's discuss today's main feature: the view.

First, we need to understand what work needs to be done to override the view, so we can better understand the meaning of each function.

1. Objectives

  1. When hovering, notify the data source of the hover row
  2. Accurately handle hover and unhover states (this problem really took a long time to solve)
  3. When clicking an item, set the row selection color
  4. Enable sorting on specific columns

With the above goals, let's solve each problem one by one.

2. Problem Analysis

Let's analyze solutions for the above 4 objectives one by one.

  1. Mouse hover detection is quite simple and can be obtained in multiple ways.

To get this state, a very important property needs to be enabled: setMouseTracking(true);

a. If we're overriding the QTableWidget class, we can receive the cellEntered signal.

b. If we're overriding the QTableView class like in this article, we need to override the mouseMoveEvent function and use the indexAt function to get the current row.

void CustomTableView::mouseMoveEvent(QMouseEvent * event)
{
    QTableView::mouseMoveEvent(event);

    const QModelIndex & index = indexAt(event->pos());
    int row = -1;
    if (index.isValid())
    {
        row = index.row();
    }

    if (m_model->hoverRow() != row)
    {
        m_model->setHoverRow(index.row());
        viewport()->update();
    }
}

  1. Handling the unhover state

This problem indeed took a long time to solve, mainly because I wanted to find a more elegant and efficient solution.

To implement the immediate unhover state when not hovering over an item, I did two main things.

First thing

Override the leaveEvent function to restore the hover row to -1 when the mouse leaves.

void CustomTableView::leaveEvent(QEvent * e)
{
    if (m_model->hoverRow() != -1)
    {
        m_model->setHoverRow(-1);
        viewport()->update();
    }
    QTableView::leaveEvent(e);
}

Second thing

Overrode QHeaderView to emit a MouseMove signal when the mouse moves over the header, resetting the current hover row to -1.

void CustomHeader::mouseMoveEvent(QMouseEvent * event)
{
    emit MouseMove();

    QHeaderView::mouseMoveEvent(event);
}

auto callback = [this]{
    if (m_model->hoverRow() != -1)
    {
        m_model->setHoverRow(-1);
        viewport()->update();
    }
};
connect(verticalHeader, &CustomHeader::MouseMove, this, callback);
connect(horizontalHeader, &CustomHeader::MouseMove, this, callback);

This approach was a last resort. If anyone has a better solution, please leave a comment.

  1. Setting the current row's highlight background color when clicking an item

At the end of the first section, we also mentioned that the selected row can be completed in the data() function. However, since our items have the selectable property, the background and foreground colors of the currently selected item or row are obtained from the following two properties, so we just need to set these two property color values.

QPalette::Highlight
QPalette::HighlightedText

The implementation for setting the selected row color is as follows:

void CustomTableView::setSelectedColor(const QColor & color)
{
    m_selectedColor = color;

    QPalette palette = this->palette();
    palette.setBrush(QPalette::Inactive, QPalette::Highlight, m_selectedColor);
    palette.setBrush(QPalette::Inactive, QPalette::HighlightedText, m_selectedColor.lighter(255));
    setPalette(palette);
}

If you think carefully, you might notice some issues. How do we control whether it's a cell background color or a full row background color?

Don't worry, just add these two properties, and the interface is really simple. I won't explain what they mean. If you don't know how to implement this, please leave a comment.

setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QTableView::SingleSelection); // Cannot multi-select

  1. Sorting on specific columns

First, we need to understand Qt's built-in sorting logic, and then we can apply the right medicine. Here, I followed Qt's own code and analyzed it to come up with this solution.

I won't discuss Qt's code analysis here. If you need it, please private message me.

After various code tracking, I found that when we set setSortingEnabled to true, Qt supports sorting, and after sorting is complete, it sends us a sortIndicatorChanged signal, which is mainly used to display the sorting triangle.

If we don't want to show the sorting triangle but just want to sort, we can call the setSortIndicatorShown interface to hide the triangle after receiving this signal.

Why go through this trouble? Because I followed Qt's code and found that if sorting is enabled, Qt's triangle must be drawn.

void QHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
    ...
    if (isSortIndicatorShown() && sortIndicatorSection() == logicalIndex)
        opt.sortIndicator = (sortIndicatorOrder() == Qt::AscendingOrder)
                            ? QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;
                            
    ...
}

Isn't it exciting? Qt is so powerful, it handles everything for us.

In this article, we mainly want to implement the feature where specific columns cannot be sorted, so the handling function works like this: when receiving a sort signal for a non-sortable column, call the setSortingEnabled interface to disable sorting.

void CustomTableView::sortColumnChanged(int logicalIndex, Qt::SortOrder order)
{
    if (m_sortIndicators.contains(logicalIndex) && m_sortIndicators[logicalIndex] == false)
    {
        if (isSortingEnabled())
        {
            setSortingEnabled(false);
        }
    }
    else
    {
        if (isSortingEnabled() == false)
        {
            setSortingEnabled(true);
        }
    }
}

VII. Testing

  1. View configuration
CustomTableView table;

// First two columns not sortable
table.setSortIndicatorVisible(0, false);
table.setSortIndicatorVisible(1, false);

// Set maximum column width
table.setColumnMaxWidth(50);

// Set minimum column width
table.setColumnMinWidth(150);

table.setHoverColor(Qt::red);

  1. Model configuration
CustomTableModel * model = table.model();

QStringList headers;
headers << "Code" << "Name" << "Industry" << "Price" << "Change" << "Turnover";
model->setHorizontalHeaderLabels(headers);

Data addition is similar to how we normally add data to QStandardItemModel; the code is too long to include here.

VIII. Related Articles

  1. Qt Table Widget Implementation - Supporting Multi-level Headers, Multi-row Headers, Cell Merging, and Font Settings
  2. Qt Excel-like Table Component - Supporting Frozen Columns, Frozen Rows, Content Adaptation, and Merged Cells
  3. Property Browser Widget QtTreePropertyBrowser Compiled as Dynamic Library (Designer Plugin)
  4. Super Practical Property Browser Widget - QtTreePropertyBrowser
  5. Qt Table Widget Ant Trail

That's all for this article, a simple table with full-row hover and full-row selection support.

If you think the article is good, you might consider giving a tip. Writing is not easy, and I appreciate everyone's support. Your suppport is my greatest motivation, thank you!!! | | | |---|---|

Important - Reposting Notice

  1. Unless otherwise specified, all articles on this site are original, and the copyright belongs to the author. When reposting, please use a link to the original source and mention the author: Morning Ten Evening Eight or Twowords.
  2. If you want to repost, please repost the original article. If you modify this article when reposting, please inform me in advance. Reposting by modifying the article to benefit the reposter is strictly prohibited.

Tags: Qt QTableView Custom Widget Table Sorting

Posted on Tue, 04 Aug 2026 15:59:48 +0000 by Hobgoblin11