Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to color a single row in QTableView



  • Hi everybody,
    I need to color a QTableView row with a different color than the one set as style.

    void MyClass::show(QString filter)
    {
      model = new QSqlRelationalTableModel(this);
      model->setTable(QString("mytable"));
      model->setFilter(filter);
      model->setRelation(2, QSqlRelation("customer", "id_customer", "name"));
      model->setHeaderData(3, Qt::Horizontal, tr("Number"));
      model->setHeaderData(4, Qt::Horizontal, tr("Date"));
      model->setHeaderData(5, Qt::Horizontal, tr("Customer"));
      model->setHeaderData(6, Qt::Horizontal, tr("Total"));
      model->select();
    
      ui->view->setModel(model);
      ui->view->resizeColumnsToContents();
      ui->view->horizontalHeader()->setStretchLastSection(true);
      ui->view->setItemDelegateForColumn(3, new Custom_Num_Delegate(this));  // Center aligned numbers
      ui->view->setItemDelegateForColumn(4, new Custom_Date_Delegate(this));  // Date Format dd/MM/yyyy
      ui->view->setItemDelegateForColumn(5, new Custom_Two_Delegate(this));  //  Total formatted in € 102,25
    }
    

    So far everything works. I have all the data visible on the QTableView.
    Using my Custom Delegates I get all the required formatting. Numbers, Date and Total.
    Using color_row() I wanted to paint only a few red lines.

    void MyClass::color_row()
    {
      for(int row = 0; row < model->rowCount(); row++)
        {
          QSqlRecord record = model->record(i);
    
          if (mycheck)
            {
              for(int column = 0; column < model->columnCount(); column++)
                {
                  model->setData(model->index(row,column),QColor(Qt::red), Qt::BackgroundRole); // Not Work
                }
            }
        }
    }
    

    But it does not work.
    Would anyone know how to give me some advice on how I could solve?
    Thank you in advance
    blackout69



  • @blackout69 As I suspected: your model only supports role display. A possible solution is that the delegate has the rows that should have the color and then apply it. I have implemented a delegate that should replace all delegates:

    #ifndef DELEGATE_H
    #define DELEGATE_H
    
    #include <QStyledItemDelegate>
    #include <QDateTime>
    #include <QVector>
    
    class Delegate : public QStyledItemDelegate
    {
    public:
        using QStyledItemDelegate::QStyledItemDelegate;
        QVector<int> rows() const
        {
            return m_rows;
        }
        void setRows(const QVector<int> &rows)
        {
            m_rows = rows;
        }
        
    protected:
        void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override{
            QStyledItemDelegate::initStyleOption(option, index);
            QVariant value = index.data();
            if(index.column() == 3){
                option->displayAlignment = Qt::AlignCenter;
            }
            else if(index.column() == 4){
                option->text = option->locale.toString(value.toDateTime(), "dd/MM/yyyy");
            }
            else if(index.column() == 5){
                option->text = option->locale.toString(value.toDouble(), 'f', 2);
                option->displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
            }
            if(m_rows.contains(index.row())){
                option->backgroundBrush = QBrush(QColor("red"));
            }
        }
    private:
        QVector<int> m_rows;
    };
    
    #endif // DELEGATE_H
    

    Then:

    *.h

    Delegate *m_delegate = nullptr;
    

    *.cpp

    m_delegate = new Delegate;
    ui->view->setItemDelegate(m_delegate);
    

    and:

    m_delegate->setRows({1, 2});
    ui->view->update();
    

  • Lifetime Qt Champion

    Hi,

    Are you doing custom painting in your delegates ?



  • Hi SGaist,
    Yes. This one my custom delegates:

    class Custom_Two_Delegate : public QItemDelegate
    {
      Q_OBJECT
    
    public:
      Custom_Two_Delegate (QObject *parent);
      void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    };
    
    Custom_Two_Delegate::Custom_Two_Delegate(QObject *parent) : QItemDelegate(parent)
    {
    
    }
    
    void Custom_Two_Delegate::paint(QPainter *painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
    {
      double number = index.model()->data(index, Qt::DisplayRole).toDouble();
      QString text = QString("%L1").arg(number,13,'f',2);
      QStyleOptionViewItem myOption = option;
      myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
      drawDisplay(painter, myOption, myOption.rect, text);
      drawFocus(painter, myOption, myOption.rect);
    }
    

  • Lifetime Qt Champion

    Then you have to also handle the BackgroundRole as well there. Usually it would be the first thing you paint and then the rest on top of it.



  • I tried to create a custom delegate for coloring, but applying the coloring, I lose the formatting.
    I understood that if I apply a custom delegate to the columns, if I apply another one to the rows, it overrides the one applied to the columns

    class Color_Delegate_Red : public QItemDelegate
    {
      Q_OBJECT
    
    public:
      Color_Delegate_Red (QObject *parent);
      void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
      void drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    };
    
    
    Color_Delegate_Red::Color_Delegate_Red(QObject *parent) : QItemDelegate(parent)
    {
    
    }
    
    void Color_Delegate_Red::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
      drawBackground(painter, option, index);
      QItemDelegate::paint(painter, option, index);
    }
    
    void Color_Delegate_Red::drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
      Q_UNUSED(index);
      painter->fillRect(option.rect, QBrush(QColor(Qt::red)));
    }
    
          if (mycheck)
                  {
                     ......
                    ui->view->setItemDelegateForRow(i, new Color_Delegate_Red(this)); // It works but I lose the formatting 
          	      }
    


  • @blackout69 You cannot have 2 delegates for the same item, the last one will replace the previous one. A possible solution is not to modify the paint method but initStyleOption and displayText:

    class Custom_Two_Delegate: public QStyledItemDelegate
    {
    public:
        using QStyledItemDelegate::QStyledItemDelegate;
        QString displayText(const QVariant &value, const QLocale &locale) const override{
            return locale.toString(value.toDouble(), 'f', 2);
        }
    protected:
        void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override{       
            QStyledItemDelegate::initStyleOption(option, index);
            option->displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
        }
    
    };
    

  • Lifetime Qt Champion

    You should subclass QStyledItemDelegate. That said, you can't stack them on top of each other. Do the background handling in each of your delegates.



  • @eyllanesc
    Hi eyllanesc
    As you write, column formatting works.
    But if I apply the coloring, I lose the formatting.

    ui->view->setItemDelegateForRow(i, new Color_Delegate_Red(this));
    

    How do you paint the entire line that interests me?



  • @blackout69 It seems that you have not understood my answer, with my solution you should not change anything of your initial logic(model->setData(model->index(row,column),QColor(Qt::red), Qt::BackgroundRole);) only change the implementation of the delegate that I proposed. Don't use setItemDelegateForRow!!!



  • @eyllanesc
    I write:

    ui->view->setItemDelegateForColumn(5, new Custom_Two_Delegate(this));
    
    void MyClass::color_row()
    {
      for(int row = 0; row < model->rowCount(); row++)
        {
          QSqlRecord record = model->record(i);
    
          if (mycheck)
            {
              for(int column = 0; column < model->columnCount(); column++)
                {
                  model->setData(model->index(row,column),QColor(Qt::red), Qt::BackgroundRole);
                }
            }
    
        }
    }
    

    Formatting works, but the lines I am interested in are not colored.
    mycheck is true!


  • Lifetime Qt Champion

    I must insiste:

    @SGaist said in How to color a single row in QTableView:

    Do the background handling in each of your delegates.



  • @SGaist
    I didn't understand how to apply the coloring in the custom delegates. For example on Custom_Two_Delegate which but posted @eyllanesc



  • @blackout69 mmm, I assumed that the model you use handled role Qt::BackgroundRole but it seems that this is not true.

    1. What model do you use?
    2. do you want to change the color of all the rows? Or just from a particular row? if it is a particular row then what does it depend on?
    3. Is the color fixed or does it also depend on another condition?


  • @eyllanesc

    1. I use QSqlRelationalTableModel
    2. I just want to color a few lines. The coloring depends on the value of a condition. For simplicity I called it mycheck.
    3. The color is fixed.


  • @blackout69
    color all items in that row like the following.
    item->setData( Qt::BackgroundRole, QVariant( Qt::red ) );
    no delegate is needed.



  • @blackout69 As I suspected: your model only supports role display. A possible solution is that the delegate has the rows that should have the color and then apply it. I have implemented a delegate that should replace all delegates:

    #ifndef DELEGATE_H
    #define DELEGATE_H
    
    #include <QStyledItemDelegate>
    #include <QDateTime>
    #include <QVector>
    
    class Delegate : public QStyledItemDelegate
    {
    public:
        using QStyledItemDelegate::QStyledItemDelegate;
        QVector<int> rows() const
        {
            return m_rows;
        }
        void setRows(const QVector<int> &rows)
        {
            m_rows = rows;
        }
        
    protected:
        void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override{
            QStyledItemDelegate::initStyleOption(option, index);
            QVariant value = index.data();
            if(index.column() == 3){
                option->displayAlignment = Qt::AlignCenter;
            }
            else if(index.column() == 4){
                option->text = option->locale.toString(value.toDateTime(), "dd/MM/yyyy");
            }
            else if(index.column() == 5){
                option->text = option->locale.toString(value.toDouble(), 'f', 2);
                option->displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
            }
            if(m_rows.contains(index.row())){
                option->backgroundBrush = QBrush(QColor("red"));
            }
        }
    private:
        QVector<int> m_rows;
    };
    
    #endif // DELEGATE_H
    

    Then:

    *.h

    Delegate *m_delegate = nullptr;
    

    *.cpp

    m_delegate = new Delegate;
    ui->view->setItemDelegate(m_delegate);
    

    and:

    m_delegate->setRows({1, 2});
    ui->view->update();
    


  • @JoeCFD What kind of object is "item"? Remember that not all models support role Qt::BackgroundRole.



  • @eyllanesc
    Sorry I am using QTableWidget.

    Try this one for all items in one row:
    bool QAbstractItemModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles);

    in case you have issues, check this out.
    https://stackoverflow.com/questions/13265992/qabstractitemmodel-setitemdata-returns-false-when-inserting-qtuserrole



  • @eyllanesc
    OK well. Now works!
    Thanks you have been a great help.

    For the sake of completeness, I changed the currency formatting to:
    In order to correctly write even the thousands in Euro. Example 1.473,56

    double value = index.model()->data(index, Qt::DisplayRole).toDouble();
    QString currency = QString("%L1").arg(value,13,'f',2);
    option->text = currency;
    option->displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
    

Log in to reply