Qt StyleSheet for a QTreeView - c++

I'm currently facing a problem about stylesheets of Qt and QTreeView.
I simply want the current item selected in the tree to be written in bold.
I did research before and the solutions given in many forums are:
myView->setStyleSheet("QTreeView::item:selected {font: bold;}");
But this doesn't work for me. I tried QTreeView {font: bold;} which works (for every item) and QTreeView::item:selected {background-color:red;} works too. Why is font not working for the selected item ?
Thanks !

Pretty old question but maybe someone will find it useful.
I am having the same problems using Qt 5.3.
I found some info on qt-center that it cannot be done by stylesheet.
Is there a way to change the font-weight (or font-size minimum) of the
selected item in QListWidget? No, as such font style option applies
to QWidgets, so you could apply the font (or other like font-size)
style option to the entire QWidget (QListWidget in this case) and not
to special behavior of a derived QWidget i.e. QListWidget's selection
item.
I think that it applies also to QTreeView, because I was not able to change its style as well.
I can change border, color, background color but nothing regarding font.
First I thought that I have to reimplement paintEvent function because I am reimplementing QTreeView. This didn't work as well.
Workaround solution:
But I found some workaround using QStyledItemDelegate.
Basically you have to subclass QTreeView, then subclass QStyledItemDelegate.
Maybe it would work without subclassing QTreeView, but you have to somehow catch some signal with selected and deselected items and call setItemDelegateForRow and setItemDelegateForColumn (it should change only intersecting cell)
Reimplement QStyledItemDelegate::paint in this way:
QStyledItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItemV4 editedOpt = option;
editedOpt.font = QFont("Arial", 23, 500);
initStyleOption( &editedOpt, index );
QStyledItemDelegate::paint(painter, editedOpt, index);
}
Reimplement QTreeView::selectionChanged(or plain QTreeView and catch QItemSelectionModel::selectionChanged) :
void CustomTreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
foreach(const QModelIndex &index, selected.indexes()) {
setItemDelegateForRow(index.row(), new CustomDelegate(this));
setItemDelegateForColumn(index.column(), new CustomDelegate(this));
}
foreach(const QModelIndex &index, deselected.indexes()) {
setItemDelegateForRow(index.row(), new QStyledItemDelegate(this));
setItemDelegateForColumn(index.column(), new QStyledItemDelegate(this));
}
}

Related

Qt: How to draw (and use) lineEdit inside delegate?

I have a custom list, and on the view (with the QStyledItemDelegate) I want display many things, including a text edit
(think about an online shopping cart where you have the items (photos and infos of them) and next to them you can change the quantity, but within a text edit, and not a spinbox).
This text edit should be able to communicate with the model. Currently I can only draw an empty textEdit, but I don't know how to connect it properly to the editorEvent ( and createEditor, setEditorData).
void CustomDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &opt,
const QModelIndex &idx) const
{
// My other painting stuff (labels, shapes...)
QStyleOptionFrame panelFrame;
QLineEdit lineEdit;
panelFrame.initFrom(&lineEdit);
panelFrame.rect = rectLE;
panelFrame.state |= QStyle::State_Sunken;
QApplication::style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panelFrame, painter);
}
QWidget *CustomDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
auto editor = new QLineEdit(parent);
editor->setText("test");
return editor;
}
void CustomDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
auto lineEdit = dynamic_cast<QLineEdit*>(editor);
if(lineEdit)
{
lineEdit->setText("test2");
}
}
As a the result I can only see an empty lineEdit and can't really interact with it.
If I would have multpiple lineEdits inside one modelIndex delegate, how could I differentiate them in the setEditorData and createEditor?
Thanks
By default, editor in QAbstractItemView is created only by edit triggers, those are set to it. Refer to docs
To make a view interactive in sense of rendered rows as regular widgets you can use a workaround.
I've used this approach with QListView and QStyledItemDelegate.
When i faced with same problem, i've tracked a viewport's mouse move event, to get a QModelIndex under the mouse cursor and if it was valid and different from previous value, i close an editor (if one was opened) and open a new one using methods QAbstractItemView::closePersistentEditor and QAbstractItemView::openPersistentEditor.
In my QStyledItemDelegate derived class i've overrided createEditor, setEditorData and updateEditorGeometry methods.
In createEditor just create a widget, in updateEditorGeometry setGeometry to editor, in setEditorData get required data to render from QModelIndex and set it to the editor widget.
The same widget class i've used to render all rows in view in paint method.
I forgot several things in my implementation:
To "interact" with the lineEdit the Qt::ItemIsEditable flag must be set in the QAbstractListModel::flags(), otherwise the Editor functions in the Delegate won't get called.
To reimplement updateEditorGeometry() where you specify the lineEdit's position.
To reimplement setModelData() where you can communicate with the model.
To draw the text in the Delegate's paint() function after drawPrimitive (drawPrimitive just draws the frame, but you want to draw the text you wrote in lineEdit as well. You can get it from the model)

How to use custom widget with model/view/delegates in Qt5?

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).

Paint widget directly on QListView with QStyledItemDelegate::paint()

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.

Qt: How to grow a QTablewidget when rendering in a delegate paint event?

I have quite the headache.
Let's consider the following:
I have a QListView with a custom delegate derived from QStyledItemDelegate.
In the delegate's paint() event I use a custom widget that I render() in the tableview. It's just a statick rendering and it's fine because I only need to show something without interacting.
My custom widget contains a QTableWidget imbedded in a vertical layout and some other labels that I fill with data in the sizehint() of the delegate. I then "force update" the layout of the custom widget with this technique: Qt: How to force a hidden widget to calculate its layout? - see forceUpdate() code.
Everything seems really fine, except one thing: the tablewidget of my custom widget seems to grow vertically when needed (when I add rows in it), BUT the rows are not rendered!!! Shrinking is ok though, setting a very big height for the custom widget somehow fixes the problem but it's not elegant and just reports the problem.
As it is just rendered and therefore not interactive, I don't want scrollbars but I need the QTableWidget to shrink/grow to show the added data. No more, no less.
The custom widget's GUI is made with the designer, everything is set to be dynamically growing and shrinking. Where's the catch? Has anyone seen such a behaviour? If yes, what is the magic parameters combination?
Some code for the eyes:
QSize ResultsRunDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
// updating custom widget's data
item_widget->UpdateDisplay(index.row()+1);
forceUpdate(item_widget); //updating the layout
return item_widget->sizeHint(); }
void ResultsRunDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QPaintDevice* originalPaintDev = painter->device();
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
painter->end();
forceUpdate(item_widget);
item_widget->render(painter->device(), QPoint(option.rect.x(), option.rect.y()), QRegion(0, 0, item_widget->rect().width()/*item_widget->sizeHint().width()*/, /*item_widget->rect().height()*/item_widget->sizeHint().height()), QWidget::DrawChildren);
painter->begin(originalPaintDev);
}
Any help would be greatly appreciated. Thx in advance!
I hope the question title is good, comments welcome.
the sizeHint() of a QTableView does not depend on its contents unfortunately. After the layout is calculated, you can get the ideal dimensions of the table from
int width = view->verticalHeader()->width() + view->horizontalHeader()->width() + view->frameWidth()*2;
int height= view->horizontalHeader()->height() + view->verticalHeader()->height() + view->frameWidth()*2;
and resize your widget accordingly.

QT Why dont see a customwidget painted by a delegates?

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.