Solved Paint widget directly on QListView with QStyledItemDelegate::paint()
-
@SGaist I have to be honest and say that I don't know what I'm doing there. Notice that
end()
comes before the paint command andbegin()
comes after it's finished. I'm following the recipe in the link I provided in the question. Besides, I tried all kinds of combinations. I couldn't understand the painting mechanism from the manual, and I'd appreciate pointing me in the right direction, or assisting me in anyway to get this to work. -
I noticed that and it doesn't make sense, the painter is already active and your just stopping it. Did you confuse these method with the save and restore methods ?
In any case, did you already saw the Star Delegate Example ? It shows a fully customised paint method.
-
@SGaist I succeeded in painting polygons a long time ago. That's easy. In fact, like I said in the question, the selection painting works fine (in addition to polygons). The guy here got +9 for this way. He explains that the reason for stopping is that "2 painters cannot work at the same time". Check it here:
-
I would like to point out that the problem is still there, and I'd appreciate the help of an expert.
-
you can use this as a base class, it's far from perfect (it does not support stylesheet and it's quite slow) but does its job, it just requires you to reimplement the
setEditorData
method, the paint is handled already:#include <QStyledItemDelegate> #include <QPainter> template <class T> class WidgetDelegate : public QStyledItemDelegate{ #ifdef Q_COMPILER_STATIC_ASSERT static_assert(std::is_base_of<QWidget,T>::value,"Template argument must be a QWidget"); #endif Q_DISABLE_COPY(WidgetDelegate) public: explicit WidgetDelegate(QObject* parent = Q_NULLPTR) :QStyledItemDelegate(parent) , m_baseWid(new T) {} virtual ~WidgetDelegate(){ delete m_baseWid; } virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,const QModelIndex &index) const Q_DECL_OVERRIDE{ setEditorData(m_baseWid,index); m_baseWid->resize(option.rect.size()); QPixmap pixmap(option.rect.size()); m_baseWid->render(&pixmap); painter->drawPixmap(option.rect,pixmap); } virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE{ Q_UNUSED(option); setEditorData(m_baseWid,index); return m_baseWid->sizeHint(); } virtual void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE =0; private: T* m_baseWid; };
An example usage would be:
class TestDelegate : public WidgetDelegate<QLabel>{ Q_OBJECT Q_DISABLE_COPY(TestDelegate) public: explicit TestDelegate(QObject* parent=Q_NULLPTR) :WidgetDelegate<QLabel>(parent) {} virtual void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE{ QLabel* const lab = qobject_cast<QLabel*>(editor); Q_ASSERT(lab); lab->setText("Content: <b>"+index.data().toString()+"</b>"); } };
int main(int argc, char *argv[]) { QApplication app(argc,argv); QListWidget w; w.model()->insertColumns(0,2); w.model()->insertRows(0,2); w.model()->setData(w.model()->index(0,0),"0,0"); w.model()->setData(w.model()->index(1,0),"1,0"); TestDelegate* tempDelegate = new TestDelegate(&w); w.setItemDelegate(tempDelegate); w.show(); return app.exec(); }
-
@VRonin First, sorry for the late response. I moved to another city and things were messy.
Thanks for working out a viable solution, but I don't understand how this is an improvement to what I provided already. I provided in my question a solution that uses a pixmap without needing an editor, and I was complaining that a pixmap was needed. Your solution needs a pixmap + editor.
A pixmap makes the widget look ugly, and dynamic widgets (like progress bar that glows on Windows from left to right) is always rendered at the starting time, making the glow show only at one place repeatedly. If using a pixmap is the only way to go, could you please explain how to make the rendering time-dependent? (so that a glowing progress bar will glow over time correctly).
-
You are right in pointing out this is not the ultimate solution but just a "make-it-work" one.
If, for example, you want a glowing progressbar you'd need something like:
#include <QStyledItemDelegate> #include <QPainter> #include <QVariantAnimation> #include <QLinearGradient> class TestDelegate : public QStyledItemDelegate { Q_OBJECT Q_DISABLE_COPY(TestDelegate) public: TestDelegate(QObject* parent=Q_NULLPTR) : QStyledItemDelegate(parent) , m_glowAnimation(new QVariantAnimation(this)) { m_glowAnimation->setEasingCurve(QEasingCurve(QEasingCurve::Linear)); m_glowAnimation->setDuration(1000); m_glowAnimation->setLoopCount(-1); m_glowAnimation->setStartValue(0.0); m_glowAnimation->setKeyValueAt(0.5,1.0); m_glowAnimation->setEndValue(0.0); connect(m_glowAnimation,&QVariantAnimation::valueChanged,this,&TestDelegate::requestRepaint,Qt::QueuedConnection); m_glowAnimation->start(); } Q_SIGNAL void requestRepaint(); virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE{ painter->save(); QRect progressRect( option.rect.topLeft() ,QSize(static_cast<double>(option.rect.width()) *index.data(Qt::UserRole).toDouble() /100.0,option.rect.height()) ); QLinearGradient glowGradient; const double currGlow =m_glowAnimation->currentValue().toDouble(); glowGradient.setColorAt(0.0,Qt::green); glowGradient.setColorAt(1.0,Qt::green); glowGradient.setColorAt(currGlow,Qt::white); glowGradient.setStart(progressRect.topLeft().x(),progressRect.topLeft().y()/2.0); glowGradient.setFinalStop(progressRect.bottomRight().x(),progressRect.topLeft().y()/2.0); painter->fillRect(progressRect,glowGradient); painter->restore(); QStyledItemDelegate::paint(painter,option,index); } private: QVariantAnimation* m_glowAnimation; double m_glowPosition; };
-
@VRonin Thank you for the another example. I have tried for a few hours to get this to work, but it wouldn't work. There are many problems in getting this to work. The first problem is that, even if this works, it'll be generic and all progress bars will be glowing at the same vertical position, since the delegate uses the same glowing position for all list widgets. Now assuming we're OK with that last issue, the example you provided draws directly on the rectangle of the widget, not the progress bar, which takes us back to square-one, where I completely fail at painting without a pixmap. So the next attempt for me was to draw this on the pixmap, which means that I have to do the math to calculate the position of the progress bar with respect to the widget, and given that I'm not an expert in QPainter, there's no feedback mechanism to know whether what I'm doing makes sense, except to just compile and try again and again, until I gave up.
So, could you please provide the same code with something compatible with the working version of my code that uses a QPixmap from my original question? I provide the code below again for convenience. As this is incomplete, just assume that
itemWidget
has a memberprogressBar
of typeQProgressBar*
that we have to make glow.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); }
-
@SamerAfach said in Paint widget directly on QListView with QStyledItemDelegate::paint():
The first problem is that, even if this works, it'll be generic and all progress bars will be glowing at the same vertical position
This is easily solved by just adding the glow position as another role in your model.
@SamerAfach said in Paint widget directly on QListView with QStyledItemDelegate::paint():
So the next attempt for me was to draw this on the pixmap
The problem is that you are trying to take an existing widget and paint it. What you should do is, in the case of a progressbar, is fill a QStyleOptionProgressBar struct with what you want to draw, then call
qApp->style()->drawControl(QStyle::CE_ProgressBar, optionProgressBar, painter)
-
@VRonin Thanks for the response, and I'd like to say that I really appreciate your patience and helpfulness.
Actually, I know this call/solution (QStyleOptionProgressBar) from the torrent example in Qt. However, this has the problem that it paints a progress bar only. Is it possible to use this and draw a whole widget that includes a progress bar as a part of it? I would appreciate an example on that.
-
@SamerAfach said in Paint widget directly on QListView with QStyledItemDelegate::paint():
Is it possible to use this and draw a whole widget that includes a progress bar as a part of it?
It is, if you lay out things vertically or horizontally it's very easy. if you go to grid or anything more complex it gets hard really fast.
The trick btw is just to set the rect member of the relevant QStyleOption to the rectangle where you want the widget to be printed
-
@VRonin
Well, if I lay everything horizontally, then I could just use a QTableView :-)I assume you're saying that things get hard really fast because one has to calculate the xy coordinates of the controls before painting them. If this is correct, it tells me that it's possible to use
drawControl()
multiple times in the samepaint()
to draw in different positions. Is there an example that shows how to do this and draw different controls in different positions (rect
s)? If such an example exists, I can imagine a viable solution of getting the rect of every control in the widget, and drawing it myself in the delegate'spaint()
usingdrawControl()
. What do you think? -
All QStyleOption have a rect member that determines where the control is printed
-
@VRonin Thanks. I'll try to paint everything with
drawControl()
and provide feedback. -
@VRonin I managed to paint everything manually... I had to calculate the coordinates of every widget and draw it, and it worked. I have to say it's quite depressing that Qt doesn't have a solution to paint a widget as is.
Thanks for the help, and have a nice day :-)