Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QTreeView: Highlighting siblings of selected item



  • Hi,
    in a QTreeView, I'd like to highlight every item that is a sibling of the currently selected item with a special color, like so:
    0_1521479924710_problem.png

    Two questions:

    1. How do I properly set the background color of the sibling items in the view depending on the selection?
      Currently I set the color in a very hacky way with a custom QStyledItemDelegate with the following paint method:
    	virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
    
    		const QColor highlight = QColor(200, 255, 200);
    
    		// Check if not selected but on same level as selection
    		if (selectionModel &&
    			!(option.state & QStyle::State_Selected) &&
    			treeModel->getItem(index)->parent == treeModel->getItem(selectionModel->currentIndex())->parent)
    		{
    			painter->fillRect(option.rect, highlight);
    		
    			// Change background color
    			QStyleOptionViewItem optionNew = option;
    			optionNew.palette.setColor(QPalette::Base, highlight);
    			optionNew.palette.setColor(QPalette::AlternateBase, highlight);
    
    			QStyledItemDelegate::paint(painter, optionNew, index);
    		}
    		else {
    			QStyledItemDelegate::paint(painter, option, index);
    		}
    	}
    

    Is there a more elegant way? (setting the palette color or drawing the background rect alone did not work with alternating row colors)
    I do not want to set the color in the model, since it has nothing to do with the actual data and could be different for a second view.

    1. Using this approach, I have a problem with updates.
      When I select a different item, the view does not get redrawn and the selection highlights are not updated.
      How can I force a redraw of the view (without resetting which items are expanded)?


  • @Cutenator said in QTreeView: Highlighting siblings of selected item:

    selectionModel->currentIndex()

    currentIndex() ≠ selected. Basically if you deselect an item it will be deselected but currentItem will point to that item.

    The solution is not super elegant but it's doable without hacking the delegate.

    I'm assuming the model is editable

    QObject::connect(treeView->selectionModel(),&QItemSelectionModel::selectionChanged,treeView,[treeView](const QItemSelection &selected, const QItemSelection &deselected)->void{
    	const auto setBackGround= [](QAbstactItemModel* mdl, const QItemSelection& sele, const QVariant& backgData)->void{
    		const auto selectedIndexes = selected.indexes();
    		QSet<QModelIndex> alreadyDone;
    		int rowCount=0;
    		int colCount=0;
    		for(const QModelIndex& singleIdx : selectedIndexes){
    			Q_ASSERT(singleIdx.isValid());
    			Q_ASSERT(singleIdx.model() == mdl);
    			const QModelIndex parentIdx = singleIdx.parent();
    			if(alreadyDone.contains(parentIdx)) 
    				continue;
    			alreadyDone.insert(parentIdx);
    			rowCount=mdl->rowCount(parentIdx);
    			colCount=mdl->columnCount(parentIdx);
    			for(int i=0;i<rowCount;++i){
    				for(int j=0;j<colCount;++j)
    					mdl->setData(mdl->index(i,j,parentIdx),backgData,Qt::BackGroundRole);
    			}
    		}
    	}
    	setBackGround(treeView->model(),selected,QBrush(QColor(153,217,234)));
    	setBackGround(treeView->model(),deselected,QVariant());
    });
    

    I put a lambda inside your lambda



  • @VRonin
    Thanks for the answer and the clarification with selected != current.
    As for your proposed solution
    "I do not want to set the color in the model, since it has nothing to do with the actual data and could be different for a second view."
    If it turns out to be the only possible way, I'd do it that way, but I consider it an even worse hack since selection has nothing to do with the model.

    Are there any other options to update the view with the delegate approach?



  • I see your point. To avoid your issues, you could use a proxy model like this one (just commited, untested) to act as a mask for Qt::BackgroundRole

    The problem of the delegate is that it does not and should not interact with items other than the one passed to its methods.


Log in to reply