Search code examples
c++qttableviewstylesheet

QTableView with stylesheet - indicators overlap text in Qt6


We had to port code to Qt6, and one of the things that broke was the QTableView.

  • In Qt 5,15 and earlier, applying a style sheet with custom checkbox indicators worked fine.
  • In Qt 6.5.3, the custom image indicators overlapped over the default checkboxes and item text.
  • In Qt 6.6.3 and 6.7.2, the custom image indicators show fine, the default indicators no longer show, but the indicators are still overlapping the text.

version comparison

It is definitely an improvement - but I don't know how to fix the overlapping of text, hidden under the indicator. The only options that are easy are also bad:

  • adding lots of spaces (text wrapping would still be affected).
  • inserting the checkbox as a separate column (I tried, but the header is formatted using QHeaderView::section:horizontal:first, QHeaderView::section:horizontal:only-one which I had trouble figuring out for the subsequent columns).
  • I tried to compare Qt versions, but I don't understand where the changes come from.
  • Currently, to have functionality, I ifdeffed out the style sheet. But it is desired by "stakeholders", among other reasons for consistency between desktop (upgraded) and embedded (not upgraded and needing large custom boxes).

This is a super simple example showing the behavior, if somebody can help me until Qt fixes it completely. It behaves the same as the real program, the images cover the text.

main.cpp:

#include "mymodel.h"
#include <QApplication>
#include <QTableView>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTableView tableView;
    MyModel myModel;
    tableView.setModel(&myModel);

    QString viewStyle = "QTableView::item { padding: 10px; }\n"
                        "QTableView::indicator { width: 30px; height: 30px; }\n"
                        "QTableView::indicator:unchecked { image: url(:/checkbox_unselected.png); }\n"
                        "QTableView::indicator:checked { image: url(:/checkbox_selected.png); } ";
    tableView.setStyleSheet(viewStyle);
    tableView.setColumnWidth(0, 100);
    tableView.setColumnWidth(1, 100);
    tableView.setColumnWidth(2, 100);
    tableView.setRowHeight(0, 100);
    tableView.setRowHeight(1, 100);

    tableView.show();
    return a.exec();
}

mymodel.h

#include <QAbstractTableModel>

class MyModel : public QAbstractTableModel
{
public:
    explicit MyModel(QObject *parent = nullptr);
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};

mymodel.cpp

#include "mymodel.h"

MyModel::MyModel(QObject *parent) : QAbstractTableModel(parent)  { }

int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
    return 2;
}

int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
    return 3;
}

QVariant MyModel::data(const QModelIndex &index, int role) const
{
    if (role == Qt::DisplayRole) {
        return QString("Row%1, Column%2").arg(index.row() + 1).arg(index.column() +1);
    }
    else if (role == Qt::CheckStateRole) {
        return (index.column() == 0) ? Qt::Checked : Qt::Unchecked;
    }
    return QVariant();
}

I reported this to Qt bug:

https://bugreports.qt.io/browse/QTBUG-129290


Solution

  • Thalia, among several options, I liked this very simple one.

    #include <QApplication>
    #include <QTableView>
    #include <QStandardItemModel>
    #include <QStyle>
    #include <QItemDelegate>
    #include <QPainter>
    
    class Delegate : public QItemDelegate
    {
    private:
        QPixmap checkedPix, uncheckedPix;
    
    public:
        Delegate(QObject *parent = nullptr):QItemDelegate(parent){
            checkedPix=qApp->style()->standardPixmap(QStyle::SP_DialogApplyButton);
            uncheckedPix=qApp->style()->standardPixmap(QStyle::SP_DialogCancelButton);
        }    
    
        void drawCheck(QPainter *painter,
                       const QStyleOptionViewItem &option,
                       const QRect &rect,
                       Qt::CheckState state) const{
    
            painter->drawPixmap(rect, state==Qt::Unchecked ? uncheckedPix:checkedPix);
        }
        
        /* you can also use: */ 
    
        // void drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QPixmap &pixmap) const{
        //  cell pixmap
        // }
    
        // void drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const{
        //  cell text
        // }
    
        // void drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const{
        //  cell background
        // }
    };
    
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        QTableView tableView;
        QStandardItemModel myModel;
        tableView.setModel(&myModel);
        myModel.setRowCount(5);
        myModel.setColumnCount(1);
        tableView.setStyleSheet(" QTableView::indicator { width: 30px; height: 30px; }");
    
        tableView.setItemDelegateForColumn(0, new Delegate);//setItemDelegate, setItemDelegateForRow
    
        for(int row=0; row<myModel.rowCount(); row++){
            auto item=new QStandardItem("item");
            item->setCheckable(true);
            item->setCheckState(row%2==0 ? Qt::Checked : Qt::Unchecked);
            myModel.setItem(row, 0, item);
        }    
    
        tableView.show();
        return a.exec();
    }