Paint widget directly on QListView with QStyledIte

2020-02-14 20:02发布

问题:

After hours of work, I'm able to paint a widget on QListView. However, the painting is done through a QPixmap. The widget appears, and I can see a progress bar. However, it's a little "pixelated" (due to using QPixmap). Is it possible to paint directly as a normal widget? That's my question.

The following is what I do:

void FileQueueItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QPaintDevice* original_pdev_ptr = painter->device();

    FileQueueListItem* itemWidget = reinterpret_cast<FileQueueListItem*>(index.data(Qt::UserRole).value<void*>());

    itemWidget->setGeometry(option.rect);
    painter->end();

    QPixmap pixmap(itemWidget->size());
    if (option.state & QStyle::State_Selected)
        pixmap.fill(option.palette.highlight().color());
    else
        pixmap.fill(option.palette.background().color());
    itemWidget->render(&pixmap,QPoint(),QRegion(),QWidget::RenderFlag::DrawChildren);

    painter->begin(original_pdev_ptr);
    painter->drawPixmap(option.rect, pixmap);
}

I learned how to do what I did with the hints from here. There, the painting is done directly on QListView, which is what I'm looking to achieve. What am I doing wrong for the following attempt not to work:

void FileQueueItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    std::cout<<"Painting..."<<std::endl;
    QPaintDevice* original_pdev_ptr = painter->device();

    FileQueueListItem* itemWidget = reinterpret_cast<FileQueueListItem*>(index.data(Qt::UserRole).value<void*>());

    itemWidget->setGeometry(option.rect);
    painter->end();

    if (option.state & QStyle::State_Selected)
        painter->fillRect(option.rect, option.palette.highlight());
    else
        painter->fillRect(option.rect, option.palette.background());

    itemWidget->render(painter->device(),
                       QPoint(option.rect.x(), option.rect.y()),
                       QRegion(0, 0, option.rect.width(), option.rect.height()),
                       QWidget::RenderFlag::DrawChildren);
    painter->begin(original_pdev_ptr);
}

The list just remains empty, and nothing happens. Though the selection can be seen, but the widget doesn't show up.

回答1:

Let's make a few things clear:

  1. You're not supposed to create widgets and put them in a model. There's a very good reason for this. Widgets are involved in the Qt event loop, which means that having too many widgets will significantly slow down your program.

  2. Widgets are not simply a bunch of controls (which seems to be how you see them). They take part in the event loop, which is why you should not have a widget that's a part of a data model.

  3. If you're using a multithreaded program and you have our model separated from the view, memory management will become a nightmare. Qt will never tolerate trying to construct or delete any widgets from other threads (which makes sense, since detaching threads from the event loop is not generally thread-safe).

Given this information, what's the right way to do what you're trying to do? Sadly the only correct way is to draw the controls yourself. If your widget is simple, that's easy to do. If your widget is complicated, you're gonna need lots of math to calculate the positions of every widget.

In the Qt Torrent Example, you'll see how a progress bar is drawn. All you have to do to draw your controls, is calculate the position, and use the rect member variable as the containing rectangle of the controls, and then draw them (of course, after setting their values). The function paint() has an option.rect parameter in it, which is the rectangle of the whole item. All you have to do, is use some math to calculate the positions inside this rect for every widget.

PS: NEVER USE ABSOLUTE VALUES FOR THE POSITIONS. You will never get it right, especially for different DPIs.

That will draw the controls without widgets, and will guarantee the speed you need even for thousands of elements.