8

How to change the group icon in legend via Python in QGIS 2.6?

At version 1.x I did it this way:

self.legendTree = self.iface.mainWindow().findChild(QDockWidget,"Legend").findChild(QTreeWidget)
currGrp=self.legendTree.currentItem()
currGrp.setIcon(0,self.setMyIcon("folder.png")
  • 1
    Why do you want to change it? – Nathan W Feb 8 '15 at 5:34
  • 2
    I would want that group created by plugin differs from the group created by man. ;) – dimamix Feb 8 '15 at 7:15
1

In QGIS2 as well as in QGIS3, the group icon is set by the QgsLayerTreeModel, in its data method.

From QGIS source code (QGIS 2.18, QGIS 3.10):

QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const
{
   if ( !index.isValid() || index.column() > 1 )
     return QVariant();
   ...

   if ( role == Qt::DisplayRole || role == Qt::EditRole )
   {
     ...
   }
   else if ( role == Qt::DecorationRole && index.column() == 0 )
   {
     if ( QgsLayerTree::isGroup( node ) )
       return iconGroup();
     ...
   }
   ...
}

The iconGroup function returns a static theme icon. While it is possible to override these icons, it is not possible to have a custom icon per group whith this implementation.


Fortunately, it is fairly easy to extend QgsLayerTreeModel using PyQt to get the desired behavior.

class CustomLayerModel(QgsLayerTreeModel):
    def __init__(self, parent=None):
        super(CustomLayerModel, self).__init__(QgsProject.instance().layerTreeRoot(), parent)

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None
        node = self.index2node(index)
        if role == Qt.DecorationRole:
            if QgsLayerTree.isGroup(node):
                if node.customProperty("legend/icon"):
                    return QIcon(node.customProperty("legend/icon"))
        return super(CustomLayerModel, self).data(index, role)

This CustomLayerModel will behave exactly like the classic QgsLayerTreeModel, but for group nodes, it will check whether a custom property (arbitrarily called "legend/icon") exists, and return a custom QIcon accordingly.

The model of the QGIS layer tree view must now be replaced by our custom one.

def replace_original_model():
    model = CustomLayerModel()
    # Keep a reference to the model so it does not get garbage collected
    iface.custom_model = model
    # Save the original model 
    if not hasattr(iface, 'original_model'):
        iface.original_model = iface.layerTreeView().layerTreeModel()
    # Copy the flags used by the original model
    model.setFlags(iface.original_model.flags())
    # Replace the original model
    iface.layerTreeView().setModel(model)

We can also define another helper function, which restores the orignal model if needed

def restore_original_model():
    if hasattr(iface, 'original_model'):
        iface.layerTreeView().setModel(iface.original_model)

One last thing to do: define a helper function which will set the icon for a given group

def set_group_icon(group_name, icon_url):
    group = QgsProject.instance().layerTreeRoot().findGroup(group_name)
    if group:
        # Write custom property
        group.setCustomProperty("legend/icon", icon_url)
        # Trigger refresh
        index = iface.layerTreeView().layerTreeModel().node2index(group)
        iface.layerTreeView().layerTreeModel().dataChanged.emit(index, index)

Usage:

>>> replace_original_model()
>>> set_group_icon("group1", "path/to/my/icon.svg")
|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.