QTreeView Item Hover/Selected background color based on current color

Posted

技术标签:

【中文标题】QTreeView Item Hover/Selected background color based on current color【英文标题】: 【发布时间】:2017-08-19 12:13:56 【问题描述】:

在我的项目中,我有几个 QTreeView 小部件显示数据。 QTreeView 中项目的背景颜色会根据数据的类型和与其他项目的关联而变化。

以下是这些背景颜色的设置方式:

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

这可行,我还希望在选择/悬停项目时具有不同的背景颜色。我选择使用样式表。

QTreeView::item:selectedbackground-color: #bedcf0; //light blue
QTreeView::item:hover:selectedbackground-color: #94c8ea; //darker blue
QTreeView::item:hover:!selectedbackground-color: #e6e6e6; //gray

这提供了我想要的外观,但仅适用于具有白色默认背景的项目。如果项目具有自定义背景颜色(通过Qt::BackgroundRole 设置),则这些悬停颜色和选定颜色会完全覆盖当前背景颜色。

我想要发生的是让每个项目在悬停/选择时变暗设定数量,基于当前背景颜色。这很难,因为QStandardItem::setProperty() 不存在。

感谢您的宝贵时间!

【问题讨论】:

你能举一个问题的可运行示例吗? @GabrieldeGrimouard 让我总结一下。 QTreeView,20 个项目都有不同的背景颜色,鼠标悬停时,悬停项目的背景颜色应该变暗一些。 【参考方案1】:

所以我能够自己解决这个问题。 (毫无意义的赏金,我不知道为什么我在检查它是否有效之前交出了 50 个代表。)

我所做的是继承 QStyledItemDelegate 并重新实现 paint() 函数。

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

在这个绘制函数中,我能够检查索引的 UserRoles 是否有自定义标志来决定我想要的颜色。我可以使用QStyle::State_SelectedQStyle::State_MouseOver 检查索引是否被选中或悬停。使用该信息,我能够编写逻辑来确定我想要的颜色。之后,我不得不手动绘制背景、图标和文本。

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

完成后,我只需将代理应用到我的QTreeViewmyTreeView-&gt;setItemDelegate(new MyStyledItemDelegate(myTreeView));

不需要样式表、后台角色更改或事件过滤器。我在整个互联网上搜索了一个解决方案,只发现很多人有同样的问题并且没有好的答案。这是迄今为止我想出的最简单和最通用的方法,所以我希望它可以帮助其他需要它的人:)

【讨论】:

我不得不说,在为您和 Qt 多年的研究中,我从未在任何地方看到过 QStyledItemDelegate :D 我很抱歉您的赏金。我给你一个+1然后你失去的少^^ 哈哈,没问题。我只是累了,仅此而已。 :) 感谢您的回答。但就我而言,无法使用 Qt 4.8 完成。 :(【参考方案2】:

所以我有一个答案。也许你可以告诉我这是否适合你和/或我们可以谈谈。

我创建了一个自定义的QTreeViewQStandardItem,覆盖mouseMoveEvent(QMouseEvent *event) 并设置了我的树的setMouseTracking(true);

我得到了鼠标下的项目:static_cast&lt;QStandardItemModel*&gt;(model())-&gt;itemFromIndex(indexAt(event-&gt;pos()))

有了这个我可以得到与项目悬停。然后在我的自定义项目中,我有一个函数hovered()normal()。当项目悬停时,将调用悬停的方法。当鼠标移动时,它会将项目恢复正常,如果它仍然在它上面,它会重新悬停。 代码:

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

我在 MainWindow 中对其进行了测试。这里是构造函数:

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


【讨论】:

感谢您的回答!然而,我发现了几个怪癖。首先,我必须将if(model() != nullptr) 添加到mouseMoveEvent 函数以检查是否未设置模型。其次,我没有在项目创建时指定颜色,所以我从项目构造函数中删除了它。这意味着Qt::BackgroundColorRole 默认无效,因此setRgb 将其变为黑色,然后无法返回“正常”,因为它默认为无效。默认为白色将不起作用,因为我启用了alternateRowColors。我选择将其默认为透明,然后自定义设置项目背景(如果透明)。 第三,即使背景颜色改变,Qt 提供的默认“悬停”效果仍然存在。如何摆脱默认的悬停外观?在此方法之前,我使用样式表来设置背景颜色并删除了默认效果。但现在不是了。第四,Qt::BackgroundColorRole已弃用,所以我切换到Qt::BackgroundRole 第五,如果在创建项目后更改了背景颜色(它经常为我的使用所做),那么_backgroundColor 不会更改以反映新颜色,从而导致颜色恢复到以前的颜色下一个鼠标悬停事件的颜色。我对此的解决方法是重新实现HoverStandardItem::setData() 并在每次更改后台角色时更新_backgroundColor。我还必须在HoverStandardItem 类中添加一个布尔值,以检查setData 是否被hovered() 函数调用,如果是,请不要更新_backgroundColor 基本上,这是一团糟,而且我希望做更多的工作(特别是因为我不得不将我的很多 QStandardItem 更改为新的子类版本,但它可以工作。我会接受这个答案很快,但我仍然无法让默认悬停效果消失。 好吧,经过大量的研究和混乱,我无法编辑项目的调色板来删除突出显示,因为我在 Windows 7 上。它使用的是 Window 的默认实现,并且是唯一的我可以通过使用样式表来改变它。因此,您向我展示的这种方法不仅无用并且存在许多其他问题,而且我需要的解决方案必须使用样式表。我希望我能恢复 50 次。

以上是关于QTreeView Item Hover/Selected background color based on current color的主要内容,如果未能解决你的问题,请参考以下文章

Qt之使用QTreeView实现QQ好友列表

Qt之使用QTreeView实现QQ登录好友列表

QTreeView/QTableView中利用QStandardItem实现复选框三种形态变化

QTreeView setStyleSheet无效

Qt窗口及控件-QTreeview/QTableView排序问题

样式不适用于 QItemDelegate