QStyledItemDelegate highlighting Indentation space on focus?
-
@Chris-Kawa Thank you, i tried this approach but im getting quite wierd looking results when i group items below each other, like that:
The group "headder" is selected:
The grouped object is selected:
Here is my code:
#include "ViewLayerList.h" #include <QHBoxLayout> #include <QCheckBox> #include <QLabel> #include "ViewLayerLineEdit.h" #include <QMouseEvent> #include "resizablepixmapitem.h" #include "SignalManager.h" #include <QHeaderView> #include <QPushButton> #include "ViewLayerCustomItem.h" ViewLayerList::ViewLayerList(CustomGraphicsScene *scene, QWidget *parent) : QTreeView{parent}, scene_durchgereicht(scene) { setHeaderHidden(true); setRootIsDecorated(true); mydelegate = new ViewLayerItemDelegate(this); model = new QStandardItemModel(10,1,this); for(int row = 0; row < 10; ++row) { for(int col = 0; col < 1; ++col) { QModelIndex index = model->index(row, col, QModelIndex()); model->setData(index, ""); } } this->setModel(model); this->setItemDelegate(mydelegate); //QModelIndex index = model->index(0, 0); // this->openPersistentEditor(index); this->setDragDropMode(QAbstractItemView::InternalMove); this->setSelectionMode(QAbstractItemView::ExtendedSelection); this->setDragEnabled(true); this->setAcceptDrops(true); this->setDropIndicatorShown(true); } void ViewLayerList::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const { QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); for (const QModelIndex& selectedIndex : selectedIndexes) { if (selectedIndex.row() == index.row()) { QRect indentRect = visualRect(index); indentRect.setRight(options.rect.left()); painter->fillRect(indentRect, QColor(173, 216, 230)); break; } } QTreeView::drawRow(painter, options, index); }
Thanks for helping me thus far, hope you can give me another hint. :)
Best regards -
@StudentScripter This condition is wrong:
if (selectedIndex.row() == index.row())
Note that the first top level item has row == 0 and every first child of any item also has row == 0 ans so on, so that's why you see multiple items colored. You don't need to retrieve the entire list of selected items and loop over them. That's wasteful. It's enough to check if the index you're given is selected:
void ViewLayerList::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const { if (selectionModel()->isSelected(index)) { ...
Also instead of having the delegate color selected item and the view color the remainder of the space you can just remove selection painting from the delegate and let the view paint it for entire row. You wouldn't have to get the visualRect and do the painting twice in parts. A lot of unnecessary calculations omitted.
-
@Chris-Kawa said in QStyledItemDelegate highlighting Indentation space on focus?:
First of all: Thank you very much, made me instantly happy this morning when i saw that i received an answer. :)
I improved the if statement and it works perfectly fine, however i don't get the second part of your answer.
I tried removing this part from my delegates paint method:/* // Passen Sie das Aussehen des Widgets basierend auf der QStyleOptionViewItem an if (opt.state & QStyle::State_Selected) { widget.setStyleSheet("background-color: lightblue;"); }else{ widget.setStyleSheet("background-color: white"); } */
However this way my delegate only appears with a grayish background (i want it to be white) and does not get the selected blueish color.
Here is a picture visualizing what i mean:
My draw row statement looks like this now:
void ViewLayerList::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const { if (selectionModel()->isSelected(index)) { // Zeichnen Sie den Einzugsbereich (Indentation) mit Ihrer gewünschten Farbe QRect indentRect = visualRect(index); indentRect.setRight(options.rect.left()); painter->fillRect(indentRect, QColor(173, 216, 230)); } QTreeView::drawRow(painter, options, index); // Rufen Sie die Basisimplementierung auf, um die Standardzeichnung durchzuführen }
Note: i also tried leaving out:
indentRect.setRight(options.rect.left());
but this did not work.
-
@StudentScripter Have you tried a simple stylesheet such as this:
QTreeView::item:selected { background-color: red; } QTreeView::branch:selected { background-color: red; }
No need to handle the painting manually.
-
@qwasder85 Yes thank you, i tried it but you have to know that i have implemented a paint methode for my delegate in order to display the controls as pixmap, even when the editor is not open:
delegate.cpp:
void ViewLayerItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); LineEditCheckBoxWidget widget; QString lineEditvalue = index.model()->data(index, Qt::EditRole).toString(); bool checkBoxValue = index.model()->data(index, Qt::CheckStateRole).toBool(); widget.lineEdit->setText(lineEditvalue); widget.checkBox->setChecked(checkBoxValue); QPixmap iconPixmap("://resource/quick.png"); // Ersetzen Sie dies durch den Pfad zu Ihrer Icon-Datei QPixmap scaledPixmap = iconPixmap.scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation); widget.iconLabel->setPixmap(scaledPixmap); if (opt.state & QStyle::State_Selected) { widget.setStyleSheet("background-color: lightblue;"); }else{ widget.setStyleSheet("background-color: white"); } widget.resize(option.rect.size()); QPixmap pixmap(widget.size()); widget.render(&pixmap); painter->drawPixmap(option.rect.topLeft(), pixmap); }
implementing a stylesheet for branch highlighting in the treeview like this:
QTreeView.cpp
ViewLayerList::ViewLayerList(CustomGraphicsScene *scene, QWidget *parent) : QTreeView{parent}, scene_durchgereicht(scene) { // Setzen Sie das Stylesheet für den ausgewählten Zweig hier setStyleSheet("QTreeView::item:selected { background-color: yellow; }"); }
(i also tried branch, row .... isn't cutting it) always ends up looking like:
vs. this with my delegate code: (so it looks like it should look like) but as @Chris-Kawa mentioned, thats not a good way to do it, so im looking for a better way instead of painting the highlight twice, once in my delegate and once in my QTreeView
-
@StudentScripter For painting the selection I simply meant this, without adjusting the area at all:
if (selectionModel()->isSelected(index)) { painter->fillRect(options.rect, QColor(173, 216, 230)); }
The widget (or the picture of it) in the item is displayed "above" the row, so it covers whatever the view painted for the row. If you'd like to go with the approach that only view paints selection then make sure that the widget (and the picture of it) has transparent background, so the underlying row is visible.
-
@Chris-Kawa Yes thank you very much, i have done this now. Sadly and i don't know why, suddenly the intendation space isn't painted highlighted anymore:
void ViewLayerList::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const { if (selectionModel()->isSelected(index)) { // Zeichnen Sie den Einzugsbereich (Indentation) mit Ihrer gewünschten Farbe QRect indentRect = visualRect(index); painter->fillRect(indentRect, QColor(173, 216, 230)); // "lightblue" Hervorhebungsfarbe*/ } QTreeView::drawRow(painter, options, index); // Rufen Sie die Basisimplementierung auf, um die Standardzeichnung durchzuführen }
it only works when adding the stylesheet solution to the treeview constructor, but i guess thats not what you meant:
ViewLayerList::ViewLayerList(CustomGraphicsScene *scene, QWidget *parent) : QTreeView{parent}, scene_durchgereicht(scene) { // Setzen Sie das Stylesheet für den ausgewählten Zweig hier setStyleSheet("QTreeView::item:selected { background-color: lightblue; }");
-
@StudentScripter You're painting the wrong rectangle. Look at the code you posted and the one I did.
-
@Chris-Kawa Well, what a pitty, i indeed missed this. 😅 Thanks for your patience with me.
May can i bother you again with another question related to this widget:
I know about the setEditTriggers() but still when i set:setEditTriggers(QAbstractItemView::AllEditTriggers);
Like it is now i have to doubleclick to create the editor and click a third time again to focus the line edit, thats pretty user unfriendly and wierd.Instead i want to double click onto the position where my lineedit is to select and focus/write into it.
Also with the checkbox i would like to click on it and it should be ticked instantly on the first click. I guess i have to implement this logic somehow myself into QTreeView mousePressEvent, but i have no good idea on how to do that? Maybe with openpersitentEditor and than somehow set focus to the widget depending on what is under the clicked mouse position?Also can't say it often enough thanks for your help and the help of the others. I really doubt i would have gotten this far without. :D Also have to admit this photoeditor clone is my first big project that i set myself, of course only with basic features, but i really want to tweak them to be useful in a real world scenario.
-
No, check the editorEvent function.
-
@SGaist thanks that seems the right direction, however i cant get the mouse position to be into the checkbox. Visually it looks like i click into the check box but my debug says its not clicking into the checkbox. I tried mapping to global in order to align both positions but i had no luck with this:
bool ViewLayerItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { if(event->type() == QEvent::MouseButtonPress) { QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); QWidget *parentWidget = qobject_cast<QWidget*>(parent()); if(parentWidget) { QWidget *editor = createEditor(parentWidget, option, index); LineEditCheckBoxWidget *widget = qobject_cast<LineEditCheckBoxWidget*>(editor); if(widget) { QPoint mousePosition = widget->checkBox->mapFromGlobal(mouseEvent->globalPos()); QRect checkboxGeometry = widget->checkBox->geometry(); QRect globalCheckboxGeometry; globalCheckboxGeometry.setTopLeft(widget->checkBox->mapToGlobal(checkboxGeometry.topLeft())); globalCheckboxGeometry.setBottomRight(widget->checkBox->mapToGlobal(checkboxGeometry.bottomRight())); qDebug() << "Checkboxdata: " << globalCheckboxGeometry; qDebug() << "MousePostion: " << mousePosition; /* //IGNORE: later i want to check here if the positon //is the same in order to perform further actions bool checked = widget->checkBox->isChecked(); widget->checkBox->setChecked(!checked); setModelData(editor, model, index); return true; */ } } } return QStyledItemDelegate::editorEvent(event, model, option, index); }
I get for example:
Checkboxdata: QRect(1095,302 100x30)
MousePostion: QPoint(215,21) -
@StudentScripter There's multiple problems in your code.
First this is wrong:
QWidget *parentWidget = qobject_cast<QWidget*>(parent());
.
A delegate can be shared between multiple views and there's no requirement that any of those views was a parent of that delegate. There's also no requirement that the parent of the delegate is a QWidget or that the delegate even has any parent. In case of sharing delegate between views the parent might not even be the widget the event occurred in.
The proper way to retrieve the widget the event happened in is throughoption.widget
parameter.Next, you should not call
createEditor
. This is a callback method that the view calls when editing of an item is requested viaview->edit(index)
. The way you have it you create a new widget every time an item is clicked. you never show or delete that widget, but you do give it a parent, so after 1000 clicks you have 1000 instances of that widget created, invisible and living until you the view is destroyed.The event gives you press position in view's viewport coordinates. To convert it to item's coordinates you can get the item coords in the viewport and do the math e.g.
if(event->type() == QEvent::MouseButtonPress) { QMouseEvent* mouseEvt = static_cast<QMouseEvent*>(event); if ( const QTreeView* view = qobject_cast<const QTreeView*>(option.widget)) { const QRect itemRect = view->visualRect(index); QPointF itemPos = mouseEvt->position() - itemRect.topLeft(); // press position in item's coords
You should not create and destroy a widget every time you want to paint or check a position in it. That's an enormous overhead that defeats the whole point of delegates.
Calculate where your checkbox is without instantiating a widget. For example you can assume it's always in a right aligned square of your item, so you can calculate its position from the item dimensions.
When painting the item you also shouldn't instantiate a widget and take a screenshot of it like you're doing now. Use
style()->drawControl(...)
to draw a picture of a checkbox or line edit without actually instantiating them. That's the idea of delegates - provide a lightweight proxy for the items and only instantiating an actual widget when editing of an item starts.When painting an item don't load any resources e.g.
QPixmap iconPixmap("://resource/quick.png");
in the paint event. That is reading from disk and can stall and make your app unresponsive if you have many items or slow disk access. Load the image once e.g. in the delegate's constructor and store it in a class member. Also if you're doing any transformation on it, likescaled(...)
do it once on load and store and reuse the result. Keep in mind that the paining is called every time a user moves a mouse over an item, so it's potentially hundreds of events a second. Painting should be as speedy as possible.Don't use stylesheets to draw a colored rectangle. It's like shooting a fly with a canon. Just use a QPainter and paint a rectangle.
-
This post is deleted!
-
@Chris-Kawa thank you very much. That definitely helped a lot. :)
I have done the manual positioning now, so all delegates line up with the actual widget. :)
void ViewLayerItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // Überprüfen Sie, ob der aktuelle Index bearbeitet wird if (index == currentlyEditedIndex) { return; } // Setzen Sie die Werte der SpinBox und CheckBox basierend auf den Modellwerten QString lineEditvalue = index.model()->data(index, Qt::EditRole).toString(); bool checkBoxValue = index.model()->data(index, Qt::CheckStateRole).toBool(); // Laden Sie das Icon und skalieren Sie es QPixmap iconPixmap("://resource/quick.png"); // Ersetzen Sie dies durch den Pfad zu Ihrer Icon-Datei QPixmap scaledPixmap = iconPixmap.scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation); // Berechnen Sie die Position für das Icon int centerY = option.rect.top() + option.rect.height() / 2; int iconY = centerY - scaledPixmap.height() / 2; QPoint iconPos = QPoint(option.rect.left() + 10, iconY); // Zeichnen Sie das Pixmap mit dem QPainter painter->drawPixmap(iconPos, scaledPixmap); // Berechnen Sie die Position und Größe für das LineEdit QRect lineEditRect = option.rect; lineEditRect.setLeft(iconPos.x() + scaledPixmap.width() + 10); // Adjust as needed lineEditRect.setRight(option.rect.right() - 10); // Adjust as needed // Erstellen Sie ein QStyleOptionFrame für das LineEdit QStyleOptionFrame lineEditOption; lineEditOption.lineWidth = 1; // Setzen Sie die Liniendicke auf 1 lineEditOption.rect = lineEditRect; // Zeichnen Sie das LineEdit QApplication::style()->drawControl(QStyle::CE_ShapedFrame, &lineEditOption, painter); // Zeichnen Sie den Text des LineEdits painter->drawText(lineEditOption.rect.adjusted(2,0,0,0), Qt::AlignLeft | Qt::AlignVCenter, lineEditvalue); // Berechnen Sie die Position und Größe für die CheckBox QRect checkBoxRect = option.rect; checkBoxRect.setLeft(lineEditRect.right() - 22); // Adjust as needed checkBoxRect.setRight(option.rect.right() - 10); // Adjust as needed // Erstellen Sie ein QStyleOptionButton für die CheckBox QStyleOptionButton checkBoxOption; checkBoxOption.state = checkBoxValue ? QStyle::State_On : QStyle::State_Off; checkBoxOption.rect = checkBoxRect; // Zeichnen Sie die CheckBox QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkBoxOption, painter); }
-
This post is deleted!
-