I have a model / view schema.
I’m developing a general multipurpose delegates, and I’d want to use some custom widgets I have.
The first I have tested does not work:
void A_delegates::paint(QPainter* painter, const QStyleOptionViewItem & option,
const QModelIndex& index) const
QRect the_rect(option.rect);
A_file_chooser file_chooser;
file_chooser.setGeometry(QRect(QPoint(0,0), the_rect.size()));
file_chooser.render(painter,the_rect.topLeft());
I see an empty cell ?
I have read the doc searching something usefull about this but without success.
Can anybody help me ? Thanks.
( the file chooser is as simple as a label plus a button. I can see it in other scenarios)
You're trying fit file chooser to the cell and force it to render into your widget. But file chooser isn't child of anything, is isn't initialized yet, so it will just skip the render. In paint function of the delegate you must use given painter to draw something. Do you want static button image in the cell? It can't be clicked! You need real file chooser in the cell, added as a child to get it working.
Related
In the proprietary code base I'm currently working with, there is a custom list view (derived form QListView).
Currently it has an issue when lots of items (>10000) make the main thread freeze.
Every item in the view is a custom widget designed in QtDesigner.
To render every row, we use setIndexWidget, which is called on QAbstractItemModel::rowsInserted signal. For every inserted row, from first to last custom widget is set for every index.
I tried to port this code to use QStyledItemDelegate, because disconnecting item widget from actual model seems to solve slow rendering.
Qt5 in that case can render items in view lazily, on demand. We will not need to create every widget for view before displaying the list.
I achieved initial results, using a class which is derived from QStyledItemDelegate. I create a list item widget in constructor and then override paint event like this.
void paint(QPainter *painter, const QStyleOptionViewItem &option,const QModelIndex &index) const override {
auto baseWid = getBaseWidget(); // Get's list item widget pointer
setSubEditorData(baseWid,index); // Set's it's state to display current item
baseWid->resize(option.rect.size());
QPixmap pixmap(option.rect.size());
baseWid->render(&pixmap);
painter->drawPixmap(option.rect, pixmap);
}
This is sufficient for static content, but my widget has checkboxes and can be selected.
I don't really understand how to make it interactive, while preserving benefits delegates provide (rendering on demand and so on).
My question is how to make delegate handle user events? Like mouse clicks, selection changes.
Qt5 examples covering delegates are too simple, I don't understand how to draw with delegate a custom widget.
The best workaround I tried to use involves dynamic switching between the static rendered QPixmap and real widget when mouse moves or delegate receives event.
First I override QAbstractItemDelegate::editorEvent, so when delegate receives any event it switches to real widget for that QModelIndex.
Switching to real widget is done using QAbstractItemView::openPersistentEditor. After that call QAbstractItemDelegate::createEditor is automatically invoked to obtain widget.
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
{
auto view = qobject_cast<NyView*>(parent());
view->openPersistentEditor(index);
return Base::editorEvent(event, model, option, index);
}
In my view I also enabled every described way to open editor.
this->setEditTriggers(QAbstractItemView::EditTrigger::AllEditTriggers);
this->setMouseTracking(true);
connect(this, &MyView::entered, this, &MyView::openPersistentEditor);
QAbstractItemView::entered signal is emitted when mouse is hovered on widget.
At user perspective nothing changes, before they can interact with list item it's already dynamically replaces with real widget.
Some garbage collection strategy seems to be necessary too, because if user hovers over many widgets they remain in memory even if they didn't interact with it for long time. For deleted rows editors are automatically destroyed, but this may be insufficient.
Open source Qt5 based software (Telegram Desktop) renders large list without using widgets. They render list manually overriding QWidget::paint method and implementing virtualization (drawing only what is seen on screen).
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.
Let's make a few things clear:
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.
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.
I have a QDockWidget:
I would like to alert the user to certain events by setting the background color of the title bar.
I have achieved this by setting the style sheet for my DockWidget:
void DockWidget::setCriticalAlert()
{
setStyleSheet("QDockWidget { background-color:red; }");
}
The result is this:
The problem is that the background-color doesn't get applied when the QDockWidget is docked:
How can I get the background color to be applied when the QDockWidget is docked?
This is a bug in Qt.
Issue 10537
Quoting from the linked issue:
The problem is that in QDockWidget::paintEvent, there is a
isFloating() condition before drawing PE_FrameDockWidget. We cannot
jsut remove this condition as it would break the other style (that
does not whish to draw frame when the dockwidget is docked) We cannot
either use PE_Widget to draw the frame as then it goes over the
dockwidget's title The solution is maybe to introduce a new
PE_FrameDockWidgetDocked primitive element. Or some
SH_DockWidget_DrawDockedFrame stylehint to draw the frame in every
cases.
a valid workaround seems to be to set the stylesheet of the parent, and use the class-and-id selector. Forgive the python formatted code but the concept is the same - in this case, 'dock' is a QDockWidget which has been given an object name using setObjectName(), and its parent, the QMainWindow, is 'self':
self.setStyleSheet("QDockWidget#"+str(dock.objectName())+"::title {background-color:red}")
In PyQt5.5, this works at runtime, i.e., can be changed on the fly.
I find a solution like this:
Firstly put a frame behind all the widgets of dockwidget's center widget, as the background.
Then set stylesheet for the frame.
By this way, we could change the background color of dockwidget.
Or you can extend the dockwidget and overwrite the function
void QDockWidget::setWidget(QWidget *widget)
using private/qdockwidget_h. and add a frame as this widget's father.
What I want to do is, when I place mouse pointer over any GUI component (like push button, radio buttons, etc.) and press the F1 key, the relevant help should be shown. I was able to get mouse coords, but couldn't get the GUI element underneath it dynamically. I am using Qt 4.7.4 and Qt Creator 2.4.
How can I achieve this?
There is a method in QApplication, that may be useful to you :
QWidget * QApplication::widgetAt ( const QPoint & point ) [static]
Then you should name every widget accordingly to your needs.
I want to have a list of items that need to be processed in a QListWidget. Similar to Windows Media Player CD import, there should be a progress bar for every item in the list.
Now there seems to be a way to do this by creating a regular progress bar, using QPixmap::grabWidget() to save its appearance in a QPixmap and then adding this QPixmap as Icon to the QListWidgetItem via QListWidgetItem::setIcon().
However, this seems to be horribly wacky.
Do you know a more elegant way to achieve a progress bar inside a list widget?
Each item in a QListWidget can be represented by a QWidget of your choice, rather than the default rendering (text). You can set this by calling QListWidget::setItemWidget(). In this case, I'd recommend using QProgressBar as the rendering widget -- you should get the desired result.
From the documentation of QListWidget::setItemWidget():
This function should only be used to
display static content in the place of
a list widget item. If you want to
display custom dynamic content or
implement a custom editor widget, use
QListView and subclass QItemDelegate
instead.
You could do it by converting your list widget into a model/view/delegate combo. Then you can set a delegate on the list view that overrides the paint functions and draws the progress bar wherever you want it. I don't know how easy it would be to get an actual QProgressBar widget into the drawing area, however.
Alternately, you could consider making your own list-widget like container that knows about the progress bars.