c++qtqicon

Why the icon in QListWidget is displayed only for the first element?


Target: I want the class object inherited from QGraphicsItem to be displayed as an icon in QListWidget.

Issue: In the list, the icon is displayed only for the first item.

how it looks

Tried to redefine the function QIconEngine:: pixmap, put a breakpoint on it, but the program does not go into it

Draw so

void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setBrush(myColor);
    painter->setPen(Qt::black);

    painter->drawRect(boundingRect());

    painter->drawText(QPointF(w / 2,h / 2),myStr);
}

For this I inherit from QIconEngine

class MyIconEngine : public QIconEngine
{
public:
    MyIconEngine(MyItem* item);

    // QIconEngine interface
public:
    void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
    QIconEngine *clone() const override;

private:
    MyItem* myItem;
};

Its implementation

MyIconEngine::MyIconEngine(MyItem* item): myItem(item)
{}

void MyIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
{
    myItem->paint(painter,nullptr,nullptr);
}

QIconEngine *MyIconEngine::clone() const
{
    return new MyIconEngine(myItem);
}

Use thus

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QListWidget* lw = new QListWidget();
    int w = 45;
    int h = 45;
    lw->setIconSize(QSize(w,h));

    MyItem* i1 = new MyItem(w,h,Qt::red,"red");
    MyItem* i2 = new MyItem(w,h,Qt::green,"green");
    MyItem* i3 = new MyItem(w,h,Qt::blue,"blue");

    MyIconEngine* ie1 = new MyIconEngine(i1);
    MyIconEngine* ie2 = new MyIconEngine(i2);
    MyIconEngine* ie3 = new MyIconEngine(i3);

    QIcon* icon1 = new QIcon(ie1);
    QIcon* icon2 = new QIcon(ie2);
    QIcon* icon3 = new QIcon(ie3);

    QListWidgetItem* lwi1 = new QListWidgetItem(*icon1,i1->Str(),lw);
    QListWidgetItem* lwi2 = new QListWidgetItem(*icon2,i2->Str(),lw);
    QListWidgetItem* lwi3 = new QListWidgetItem(*icon3,i3->Str(),lw);

    lw->show();

    return a.exec();
}

MyItem.h

class MyItem : public QGraphicsItem
{
public:
    MyItem(int width,int height, const QColor& color,const QString& text);

    const QString& Str() const
    {
        return mySrt;
    }

    // QGraphicsItem interface
public:
    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

private:

    QColor myColor;
    int w;
    int h;
    QString mySrt;
};

MyItem.cpp

MyItem::MyItem(int width, int height, const QColor &color,const QString& text):w(width),h(height),myColor(color),mySrt(text)
{}

QRectF MyItem::boundingRect() const
{
    return QRectF(0,0,w,h);
}

void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    painter->setBrush(myColor);
    painter->setPen(Qt::black);

    painter->drawRect(boundingRect());

    painter->drawText(QPointF(w / 2,h / 2),mySrt);
}

Solution

  • Cause

    You draw the rectangle and the text in the wrong place:

    painter->drawRect(boundingRect());
    painter->drawText(QPointF(w / 2,h / 2),mySrt);
    

    since you do not have information where to draw them.

    And you do not have this information, because QGraphicsItem::paint accepts a QStyleOptionGraphicsItem as a second argument, but you pass a nullptr in your call:

    myItem->paint(painter,nullptr,nullptr);
    

    Solution

    Create an object of type QStyleOptionGraphicsItem, set it up and pass it to the paint method. Then use the passed information to draw with the painter in the correct places.

    This will allow you to make further adjustments of how the decoration looks like, depending on the QIcon::Mode and QIcon::State.

    Example

    Here is a minimal example I have created for you in order to demonstrate how you could implement the proposed solution:

    1. Change your implementation of MyIconEngine::paint like this:

      void MyIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
      {
          QStyleOptionGraphicsItem option;
      
          option.rect = rect;
      
          if (mode == QIcon::Selected)
              option.state = QStyle::State_Selected;
      
          myItem->paint(painter, &option, nullptr);
      }
      
    2. Change your implementation of MyItem::paint like this:

      void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
      {
          Q_UNUSED(option);
          Q_UNUSED(widget);
      
          painter->setBrush(myColor);
          painter->setPen(option->state == QStyle::State_Selected ? Qt::yellow : Qt::black);
      
          painter->drawRect(option->rect);
          painter->drawText(option->rect, mySrt, QTextOption(Qt::AlignCenter));
      }
      

    I took the liberty to extend the example a bit further than what you have asked in order to show you why using QStyleOptionGraphicsItem is the right solution and how you can utilize it to achieve more.

    This, i.e. painter->drawText(option->rect, mySrt, QTextOption(Qt::AlignCenter));, will fix the problem with the text positioning as well.

    Note: This example is created for demonstration purposes. You might want to make further adjustments to QStyleOptionGraphicsItem option in order to make it suit the specific needs of your application.

    Result

    As written, the given example produces the following result:

    Window with QListWidget with three custom decorated items