QTreeView Item Hover/Selected background color bas

2020-07-24 04:49发布

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!

2条回答
▲ chillily
2楼-- · 2020-07-24 05:19

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. I've searched all over the internet for a solution and only found a lot of people with the same problem and no good answer. This is by far the simplest and most versatile way to achieve this I've come up with so I hope it helps anyone else who needs it :)

查看更多
神经病院院长
3楼-- · 2020-07-24 05:21

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);

}
查看更多
登录 后发表回答