Render QWidget in paint() method of QWidgetDelegate for a QListView
Asked Answered
S

3

13

i'm having difficulties implementing custom widget rendering in a QListView. I currently have a QListView displaying my custom model called PlayQueue based on QAbstractListModel.

This is working fine with simple text, but now I would like to display a custom widget for each element. So I subclassed a QStyledItemDelegate to implement the paint method like this:

void QueueableDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
    if (option.state & QStyle::State_Selected)
        painter->fillRect(option.rect, option.palette.highlight());
    QWidget *widget = new QPushButton("bonjour");
    widget->render(painter);
}

The selection background is properly rendered but no widget is displayed. I tried with simple QPainter commands like in Qt examples, and this is working fine:

void QueueableDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
    if (option.state & QStyle::State_Selected)
        painter->fillRect(option.rect, option.palette.highlight());
    if (option.state & QStyle::State_Selected)
        painter->setPen(option.palette.highlightedText().color());
    painter->setFont(QFont("Arial", 10));
    painter->drawText(option.rect, Qt::AlignCenter, "Custom drawing");
}

So I tried some changes like:

  • Changing QStyledItemDelegate to QItemDelegate
  • Adding painter->save() and painter->restore() around rendering
  • Setting the widget geometry to the available size

But i'm a bit stuck now, i searched for a while on the internet, but can't find any example doing what i want, they all talk about editing widget (which is a lot easier) or custom drawn control (predefined ones, like progress bars). But here I really need a custom widget I created, containing some layout, labels & pixmaps. Thanks for your help!

I'm using Qt 4.7.3 for GCC on Ubuntu 11.04.

Spiritism answered 23/6, 2011 at 10:50 Comment(0)
S
4

Ok I finally figured out how to do what I wanted. Here is what I did:

  1. Drop the delegate class
  2. Call QListView::setIndexWidget() in the data() method of my model to set the widget
  3. Ensure no widget is already present when setting by checking QListView::indexWidget()
  4. Handle the Qt::SizeHintRole role to return the widget's size hint
  5. Return a blank QVariant for the Qt::DisplayRole role

This way I have my custom widgets displayed in the QListView, and they are properly lazy-loaded (that's why i used the model/view pattern). But I don't see how can I unload them when not beeing displayed, well that's another problem.

Spiritism answered 1/7, 2011 at 13:14 Comment(1)
Did you ever figure out how best to do the unloading?Supersensitive
P
17

Just to complete the whole picture: further one can find the code to manage the QWidget as QListView item using delegates.

I finally found out how to make it work within the subclass of QStyledItemDelegate using its paint(...) method.

It seems more effective for performance than previous solution, but this statement one needs to verify =) by investigation what does setIndexWidget() do with created QWidget.

So finally, here is the code:

class PackageListItemWidget: public QWidget

.....

class PackageListItemDelegate: public QStyledItemDelegate

.....

void PackageListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{

// here we have active painter provided by caller

// by the way - we can't use painter->save() and painter->restore()
// methods cause we have to call painter->end() method before painting
// the QWidget, and painter->end() method deletes
// the saved parameters of painter

// we have to save paint device of the provided painter to restore the painter
// after drawing QWidget
QPaintDevice* original_pdev_ptr = painter->device();

// example of simple drawing (selection) before widget
if (option.state & QStyle::State_Selected)
    painter->fillRect(option.rect, option.palette.highlight());

// creating local QWidget (that's why i think it should be fasted, cause we 
// don't touch the heap and don't deal with a QWidget except painting)
PackageListItemWidget item_widget;

// Setting some parameters for widget for example
    // spec. params
item_widget.SetPackageName(index.data(Qt::DisplayRole).toString());     
    // geometry
item_widget.setGeometry(option.rect);

// here we have to finish the painting of provided painter, cause
//     1) QWidget::render(QPainter *,...) doesn't work with provided external painter 
//          and we have to use QWidget::render(QPaintDevice *,...)
//          which creates its own painter
//     2) two painters can't work with the same QPaintDevice at the same time
painter->end(); 

// rendering of QWidget itself
item_widget.render(painter->device(), QPoint(option.rect.x(), option.rect.y()), QRegion(0, 0, option.rect.width(), option.rect.height()), QWidget::RenderFlag::DrawChildren);   

// starting (in fact just continuing) painting with external painter, provided
// by caller
painter->begin(original_pdev_ptr);  

// example of simple painting after widget
painter->drawEllipse(0,0, 10,10);   
};
Prut answered 24/9, 2013 at 13:43 Comment(2)
After messing around with this a bit, I found that I had to manually apply the deviceTransform() to the offset point to get it to line up. Before painter->end(); I created QPoint mappedorigin = painter->deviceTransform().map(QPoint(option.rect.x(), option.rect.y())), then passed mappedorigin as the 2nd arg for QWidget::render(). This probably would not be necessary if I had created the widget in this function, but I created it as part of parent widget.Dogtooth
This is the only thing that worker for me, thanks. I had to add the mapping from @AndrewDomaszekDisembark
S
4

Ok I finally figured out how to do what I wanted. Here is what I did:

  1. Drop the delegate class
  2. Call QListView::setIndexWidget() in the data() method of my model to set the widget
  3. Ensure no widget is already present when setting by checking QListView::indexWidget()
  4. Handle the Qt::SizeHintRole role to return the widget's size hint
  5. Return a blank QVariant for the Qt::DisplayRole role

This way I have my custom widgets displayed in the QListView, and they are properly lazy-loaded (that's why i used the model/view pattern). But I don't see how can I unload them when not beeing displayed, well that's another problem.

Spiritism answered 1/7, 2011 at 13:14 Comment(1)
Did you ever figure out how best to do the unloading?Supersensitive
D
0

Here is an example for you. It seems that you need to use QStylePainter but this is just for drawing as far as I understand it does not act like a real button.

Deese answered 23/6, 2011 at 11:1 Comment(1)
Yeah I saw this method, but it only works to draw a predefined control, not a custom widget. By the way, I do not need a working push button, this was just to simplify the example, my widget is pretty static, so just drawing it could be fine if possible.Spiritism

© 2022 - 2024 — McMap. All rights reserved.