qtreeview qstyleitemdelegate

QTreeView Item Hover/Selected background color based on current color

Ask Question

Asked 7 years, 4 months ago

Modified 2 years, 1 month ago

Viewed 10k times

Report this ad

2

In my project I have several QTreeView widgets displaying data. The background color of the items in the QTreeView changes depending on the data's type and association with other items.

Here is how those background colors are set:

QColor warning;
warning.setRgb(255, 86, 86);
model->itemFromIndex(index)->setData(warning, Qt::BackgroundRole);

This works, but I also want to have different background colors when an item is selected/hovered. I opted to use a stylesheet.

QTreeView::item:selected{background-color: #bedcf0;} //light blue
QTreeView::item:hover:selected{background-color: #94c8ea;} //darker blue
QTreeView::item:hover:!selected{background-color: #e6e6e6;} //gray

This provides the look I want, but only for items that have a white default background. If an item has a custom background color (set via Qt::BackgroundRole) then these hover and selected colors completely override the current background color.

What I want to happen is have every item darken a set amount when hovered/selected, based on the current background color. This is tough because QStandardItem::setProperty() doesn't exist.

Thanks for your time!

Share

Improve this question

Follow

asked Mar 26, 2017 at 22:31

mrg95's user avatar

mrg95

2,4101212 gold badges5050 silver badges9090 bronze badges

  • could you give a runnable example of the problem ? 

    – Gabrielle de Grimouard

     CommentedApr 1, 2017 at 11:13
  • @GabrieldeGrimouard Let me summarize. QTreeView, 20 items all with different background colors, on mouse hover, the background color should get a little darker for the hovered item. 

    – mrg95

     CommentedApr 2, 2017 at 0:03

Add a comment

2 Answers

Sorted by:

                                              Highest score (default)                                                                   Trending (recent votes count more)                                                                   Date modified (newest first)                                                                   Date created (oldest first)                              

5

So I was able to solve this myself. (pointless bounty, idk why I handed over the 50 rep before checking if it worked.)

What I did was subclass QStyledItemDelegate and reimplement the paint() function.

.h

class MyStyledItemDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
      explicit MyStyledItemDelegate(QObject *parent = 0){}

      virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
} 

In this paint function I was able to check the index's UserRoles for a custom flag to decide the color I wanted. I can use QStyle::State_Selected and QStyle::State_MouseOver to check if the index is selected or hovered.Using that information, I was able to write the logic to determine the colors I wanted. After that I had to draw in the background, icon, and text manually.

.cpp

void MyStyledItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    //background
    QColor bgColor;
    int bgColorType(0);
    bgColorType = index.data(Qt::UserRole+9).toInt();//custom flag I set to determine which color i want

    //color logic
    if(bgColorType == 0)
        bgColor = QColor(Qt::transparent);//default is transparent to retain alternate row colors
    else if(bgColorType == 1)
        bgColor = qRgba(237, 106, 106, 255);//red
    else if(bgColorType == 2)
        bgColor = qRgba(241, 167, 226, 255);//pink
    //etc...

    QStyleOptionViewItem opt(option);

    if(option.state & QStyle::State_Selected)//check if item is selected
    {
        //more color logic
        if(bgColorType == 0)
            bgColor = qRgba(190, 220, 240, 255);
        else
            bgColor = qRgba(bgColor.red()-25, bgColor.green()-25, bgColor.blue()-25, 255);

        //background color won't show on selected items unless you do this
        opt.palette.setBrush(QPalette::Highlight, QBrush(bgColor));
    }

    if(option.state & QStyle::State_MouseOver)//check if item is hovered
    {
        //more color logic
        bgColor = qRgba(bgColor.red()-25, bgColor.green()-25, bgColor.blue()-25, 255);

        if(option.state & QStyle::State_Selected)//check if it is hovered AND selected
        {
            //more color logic
            if(bgColorType == 0)
            {
                bgColor = qRgba(148, 200, 234, 255);
            }

            //background color won't show on selected items unless you do this
            opt.palette.setBrush(QPalette::Highlight, QBrush(bgColor));
        }
    }


    //set the backgroundBrush to our color. This affects unselected items.
    opt.backgroundBrush = QBrush(bgColor);

    //draw the item background
    option.widget->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter);

    //icon
    QRect iconRect = option.rect;
    iconRect.setLeft(iconRect.left()+3);//offset it a bit to the right
    //draw in icon, this can be grabbed from Qt::DecorationRole
    //altho it appears icons must be set with setIcon()
    option.widget->style()->drawItemPixmap(painter, iconRect, Qt::AlignLeft | Qt::AlignVCenter, QIcon(index.data(Qt::DecorationRole).value<QIcon>()).pixmap(16, 16));

    //text
    QRect textRect = option.rect;
    textRect.setLeft(textRect.left()+25);//offset it a bit to the right
    //draw in text, this can be grabbed from Qt::DisplayRole
    option.widget->style()->drawItemText(painter, textRect, Qt::AlignLeft | Qt::AlignVCenter, option.palette, true, index.data(Qt::DisplayRole).toString());
}

Once that's done, I just apply the delegate to my QTreeView with myTreeView->setItemDelegate(new MyStyledItemDelegate(myTreeView));

No stylesheets, background role changes, or eventFilters required.

Share

Improve this answer

Follow

edited Jun 16, 2022 at 15:40

answered Apr 6, 2017 at 10:49

mrg95's user avatar

mrg95

2,4101212 gold badges5050 silver badges9090 bronze badges

  • I have to say that in hours an researches for you and years of Qt, I have never seen QStyledItemDelegate anywhere :D I am sorry for your bounty. I give you a +1 then you lost less ^^ 

    – Gabrielle de Grimouard

     CommentedApr 6, 2017 at 18:11
  • Lol it's no problem. I was just tired that's all. :) 

    – mrg95

     CommentedApr 6, 2017 at 21:46 
  • Thanks for the answer. But in my case cannot make it done using Qt 4.8. :( 

    – Hareen Laks

     CommentedSep 11, 2018 at 7:47

Add a comment

1

+50

So I have an answer. Maybe you can tell me if it's ok for you and/or we can talk about it.

I created a custom QTreeView and QStandardItem, overwritemouseMoveEvent(QMouseEvent *event) and set setMouseTracking(true); of my tree.

I got the item under the mouse with: static_cast<QStandardItemModel*>(model())->itemFromIndex(indexAt(event->pos()))

With that I can get with item is hovered. Then in my custom item I have a function hovered() and normal(). When the item is hovered, the method hovered is called. When the mouse move, it put the item back to normal and rehovered it if it's still on it. The code:

HoveredTreeView.cpp:

#include "HoverTreeView.h"

#include <QDebug>
#include <QMouseEvent>
#include <QStandardItemModel>

HoverTreeView::HoverTreeView(QWidget *parent)
    : QTreeView(parent)
{
    setMouseTracking(true);
}



void HoverTreeView::mouseMoveEvent(QMouseEvent *event)
{
    while (!_hoveredItems.empty())
    {
       HoverStandardItem* oldItem = _hoveredItems.pop();
       oldItem->normal();
    }
    auto *item = static_cast<QStandardItemModel*>(model())->itemFromIndex(indexAt(event->pos()));
    HoverStandardItem* realItem = static_cast<HoverStandardItem*>(item);
    if (item) {
        realItem->hovered();
        _hoveredItems.push(realItem);
    }
}

HoveredTreeView.h:

#ifndef HOVERTREEVIEW_H
#define HOVERTREEVIEW_H

#include <QStack>
#include <QTreeView>
#include "HoverStandardItem.h"

class HoverTreeView : public QTreeView
{
public:
    HoverTreeView(QWidget *parent = nullptr);

public slots:
    void    mouseMoveEvent(QMouseEvent *event);
    QStack<HoverStandardItem*> _hoveredItems;
};

#endif // HOVERTREEVIEW_H

HoveredStandardItem.cpp:

#include "HoverStandardItem.h"

HoverStandardItem::HoverStandardItem(QColor const& backgroundColor, const QString &text)
    : QStandardItem(text)
    , _backgroundColor(backgroundColor)
{
    setData(backgroundColor, Qt::BackgroundColorRole);
}

void HoverStandardItem::hovered()
{
    QColor hoveredColor(_backgroundColor);
    unsigned int darker = 20;
    hoveredColor.setRgb(hoveredColor.red() - darker, hoveredColor.green() - darker, hoveredColor.blue() - darker);
    setData(hoveredColor, Qt::BackgroundColorRole);
}

void HoverStandardItem::normal()
{
    setData(_backgroundColor, Qt::BackgroundColorRole);
}

HoveredStandardItem.h:

#ifndef HOVERSTANDARDITEM_H
#define HOVERSTANDARDITEM_H

#include <QStandardItem>

class HoverStandardItem : public QStandardItem
{
public:
    HoverStandardItem(const QColor &backgroundColor, QString const& text = "");
    void hovered();
    void normal();
private:
    QColor  _backgroundColor;
};

#endif // HOVERSTANDARDITEM_H

I tested it in a MainWindow. Here the constructor:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);


    QStandardItemModel *model = new QStandardItemModel(this);
    QColor warning[3] = {
    {QColor(255, 86, 86)},
        {QColor(86, 255, 86)},
        {QColor(86, 86, 255)}
    };
    for (int j = 0 ; j < 3 ; ++j) {
        QStandardItem *parentItem = model->invisibleRootItem();
        for (int i = 0; i < 4; ++i) {
            QStandardItem *item = new HoverStandardItem(warning[j], QString("item %0 %1").arg(j).arg(i));
            parentItem->appendRow(item);
            parentItem = item;
        }
    }
    ui->treeView->setModel(model);

}

Share

Improve this answer

Follow

answered Apr 2, 2017 at 8:03

Gabrielle de Grimouard's user avatar

Gabrielle de Grimouard

1,9951616 silver badges2222 bronze badges

  • Thanks for your answer! I found several quirks with this however. First, I had to add if(model() != nullptr) to the mouseMoveEvent function to check if a model isn't set. Second, I dont specify a color upon item creation, so I removed that from the item constructor. This means the Qt::BackgroundColorRole is invalid by default so setRgb turns it black, and then can't be returned to "normal" because it defaults as invalid. Defaulting to white won't work cus I have alternateRowColors enabled. I opted to default it to transparent and then custom set the item background if transparent. 

    – mrg95

     CommentedApr 5, 2017 at 16:31
  • Third, the default "hover" effect provided by Qt is still present even though the background color is changed. How do I get rid of that default hover look? Prior to this method, I used stylesheets to set the background colors and that removed the default effect. But not anymore. Fourth, Qt::BackgroundColorRole is deprecated, so I switched to Qt::BackgroundRole

    – mrg95

     CommentedApr 5, 2017 at 16:31
  • Fifth, if the background color is changed POST item creation (which it does often for my usage), then _backgroundColor doesn't change to reflect the new color resulting in the color reverting to the previous color on next mouse hover event. My fix for this was to reimplement HoverStandardItem::setData() and update the _backgroundColor every time the background role is changed. I also had to add a bool to the HoverStandardItem class to check if setData was being called by the hovered() function, and if so, don't update _backgroundColor

    – mrg95

     CommentedApr 5, 2017 at 16:31
  • Basically, it's quite a mess and is way more work that I would have liked (especially since I had to change sooo many of my QStandardItem's to the new subclassed version, but it works. I'll accept this answer soon, but I still am having trouble getting the default hover effect to go away. 

    – mrg95

     CommentedApr 5, 2017 at 19:31
  • Okay after a ton of research and messing around, turns out that I can't edit the item's palette to remove the highlighting because I'm on Windows 7. It is using Window's default implementation and the only way I can change that is by using stylesheets. So not only is this method you showed me useless and has multiple other problems, but the solution I need must utilize stylesheets. I wish I had my 50 rep back. 

    – mrg95

     CommentedApr 6, 2017 at 9:02
  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值