使用 QStyledItemDelegate::paint() 直接在 QListView 上绘制小部件

工作数小时后,我可以在 QListView 上绘制小部件.但是,绘制是通过QPixmap 完成的.小部件出现,我可以看到一个进度条.然而,它有点像素化"(由于使用了QPixmap).是否可以直接作为普通小部件进行绘制?这是我的问题.

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.

以下是我所做的:

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

我从此处学习了如何使用提示进行操作.在那里,绘画是直接在 QListView 上完成的,这正是我想要实现的.以下尝试不起作用,我做错了什么:

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.

推荐答案

让我们把一些事情说清楚:

Let's make a few things clear:

  1. 您不应该创建小部件并将它们放入模型中.这有一个很好的理由.Qt 事件循环中涉及小部件,这意味着拥有过多小部件会显着降低您的程序速度.

  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.

小部件不仅仅是一堆控件(这似乎是您看待它们的方式).它们参与事件循环,这就是为什么您不应该拥有属于数据模型一部分的小部件.

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.

如果您使用的是多线程程序并且我们的模型与视图分离,那么内存管理将成为一场噩梦.Qt 永远不会容忍尝试从其他线程构造或删除任何小部件(这是有道理的,因为从事件循环中分离线程通常不是线程安全的).

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.

在 Qt Torrent 示例中,您将看看进度条是如何绘制的.绘制控件所需要做的就是计算位置,并使用rect 成员变量作为控件的包含矩形,然后绘制它们(当然,在设置它们的值之后).paint() 函数中有一个 option.rect 参数,它是整个项目的矩形.您所要做的就是使用一些数学方法来计算每个小部件在此矩形内的位置.

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:永远不要对位置使用绝对值.你永远不会做对,尤其是对于不同的 DPI.

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.

相关文章