2

I have got a QTableView component displaying several types of data in the rows. What I need is to display each type of row by different color. My stylesheet looks like this:

RecordSheet::item {
        border: 0px;
        color: black;
        padding: 1px 0px 0px 3px;
}
RecordSheet::item:selected, RecordSheet::item:selected:!active {
        background-color: #e8b417;
        color: black;
}

I have two ideas how to achieve this:

  1. Use data() method in the model and respond to the Qt::BackgroundColorRole. Unfortunately when I do it, the background color is ignored until I remove the border: 0px; from the stylesheet and when I remove the border, the styleshhet's padding is ignored. Strange...

  2. Setup a CSS/QSS class for each type of row and set their colors in the stylesheet. Then use the model to assign a proper class for each type of row. So the stylesheet would look like this:

    RecordSheet::item {
        border: 0px;
        color: black;
        padding: 1px 0px 0px 3px;
    }
    RecordSheet::item[class=green_row] {
            background-color: green;
    }
    RecordSheet::item[class=red_row] {
            background-color: red;
    }
    

    I like this approach more because it separates content from the appearance, but I don't have any idea how to do it. Maybe using an ItemDelegate?

Please, does anybody know a nice and simple solution?

Kind regards and many thanks.

Jan

Honza Vojtěch
  • 685
  • 1
  • 7
  • 27

4 Answers4

5

You don't need stylesheet to do this, styleshhet is not so powerful to do all things that developer wants. Use more powerful thing - delegate. I will show you main idea and working example. Header:

#ifndef ITEMDELEGATEPAINT_H
#define ITEMDELEGATEPAINT_H

#include <QStyledItemDelegate>

class ItemDelegatePaint : public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit ItemDelegatePaint(QObject *parent = 0);
    ItemDelegatePaint(const QString &txt, QObject *parent = 0);


protected:
    void paint( QPainter *painter,
                const QStyleOptionViewItem &option,
                const QModelIndex &index ) const;
    QSize sizeHint( const QStyleOptionViewItem &option,
                    const QModelIndex &index ) const;
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData(QWidget * editor, const QModelIndex & index) const;
    void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const;
    void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const;

signals:

public slots:

};

#endif // ITEMDELEGATEPAINT_H

There are many methods here but I will show you only paint, because it is the most important thing for you. Description about another methods you can find in web

cpp:

void ItemDelegatePaint::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QString txt = index.model()->data( index, Qt::DisplayRole ).toString();

    if(index.row() == 0)//green row
        painter->fillRect(option.rect,QColor(0,255,0));
    else
        if(index.row() == 1)//blue row
            painter->fillRect(option.rect,QColor(0,0,255));
    else
        if(index.row() == 2)//red row
            painter->fillRect(option.rect,QColor(255,0,0));
    //and so on

    if( option.state & QStyle::State_Selected )//we need this to show selection
    {
        painter->fillRect( option.rect, option.palette.highlight() );
    }


    QStyledItemDelegate::paint(painter,option,index);//standard processing
}

Usage:

ui->tableView->setItemDelegate(new ItemDelegatePaint);

Result:

enter image description here

Jablonski
  • 18,083
  • 2
  • 46
  • 47
2

There is a way to use CSS for this as per approach 2. The following code shows how to do per cell styling based on the content of the cell. But I'm sure you can work out how to extend it to setting styling for each cell in a row. Likewise, it is written in python using pyqt, but it's still close.

#!/usr/bin/python3

from PyQt5 import QtWidgets, QtGui, QtCore

class_values = ["zero", "one", "two"]

class Cell(QtWidgets.QWidget):
    def initFromItem(self, item):
        self.setProperty('dataClass', class_values[int(item.text())])

class TDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, *a):
        super(TDelegate, self).__init__(*a)
        self.cell = Cell(self.parent())

    def paint(self, painter, option, index):
        item = index.model().itemFromIndex(index)
        self.cell.initFromItem(item)
        self.initStyleOption(option, index)
        style = option.widget.style() if option.widget else QtWidgets.QApplication.style()
        style.unpolish(self.cell)
        style.polish(self.cell)
        style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter, self.cell)

class TTableModel(QtGui.QStandardItemModel):
    def __init__(self, parent=None):
        super(TTableModel, self).__init__(parent)
        for i in range(5):
            self.appendRow([QtGui.QStandardItem(str((x+i) % 3)) for x in range(5)])

class TTableView(QtWidgets.QTableView):
    def __init__(self, parent=None):
        super(TTableView, self).__init__(parent)
        self.setItemDelegate(TDelegate(self))

class Main(QtWidgets.QMainWindow):
    def __init__(self):
        super(Main, self).__init__()
        self.table = TTableView(self)
        self.model = TTableModel(self)
        self.table.setModel(self.model)
        self.setCentralWidget(self.table)

if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    app.setStyleSheet("""
Cell[dataClass=zero]::item { background-color: gray; }
Cell[dataClass=one]::item { background-color: green; }
Cell[dataClass=two]::itemn { background-color: cyan; }
""")
    mainWin = Main()
    mainWin.show()
    sys.exit(app.exec_())

The key is to pass a widget with the appropriate properties to drawControl. Also the CSS must reference the ::item for anything to happen.

0

Although QItemDelegate is a good option, sometimes its just a over kill when all you want to do is color some cells. You can simply do this:

QStandardItem *item = new QStandardItem("");
item->setData(Qt::gray, Qt::BackgroundColorRole);

This works perfectly for me.

Zaid
  • 678
  • 6
  • 13
0

It would be a good idea to do things as simple and fast as possible. if data appearance is your concept, you don't need to add extra item delegate class just change data() method with it's specific roles like the following example from QT tutorials. This method works for scenarios which you don't want to change data like a read only table but , for display and editing facilities @Chernobyl provided a nice solution QStyledItemDelegate.

#ifndef MYMODEL_H
#define MYMODEL_H

#include <QAbstractTableModel>

class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    MyModel(QObject *parent);
    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;
};

#endif // MYMODEL_H


#include <QFont>
#include <QBrush>
#include "mymodel.h"
#include <QDebug>

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

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

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

//! [Quoting ModelView Tutorial]
// mymodel.cpp
QVariant MyModel::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    int col = index.column();
    // generate a log message when this method gets called
    qDebug() << QString("row %1, col%2, role %3")
            .arg(row).arg(col).arg(role);

    switch(role){
    case Qt::DisplayRole:
        if (row == 0 && col == 1) return QString("<--left");
        if (row == 1 && col == 1) return QString("right-->");

        return QString("Row%1, Column%2")
                .arg(row + 1)
                .arg(col +1);
        break;
    case Qt::FontRole:
        if (row == 0 && col == 0) //change font only for cell(0,0)
        {
            QFont boldFont;
            boldFont.setBold(true);
            return boldFont;
        }
        break;
    case Qt::BackgroundRole:

        if (row == 1 && col == 2)  //change background only for cell(1,2)
        {
            QBrush redBackground(Qt::red);
            return redBackground;
        }
        break;
    case Qt::TextAlignmentRole:

        if (row == 1 && col == 1) //change text alignment only for cell(1,1)
        {
            return Qt::AlignRight + Qt::AlignVCenter;
        }
        break;
    case Qt::CheckStateRole:

        if (row == 1 && col == 0) //add a checkbox to cell(1,0)
        {
            return Qt::Checked;
        }
    }
    return QVariant();
}
//! [Quoting ModelView Tutorial]


enter image description here

saeed
  • 2,477
  • 2
  • 23
  • 40