Question

I am trying to restrict selection of a tree to a particular column.

I am using delegates heavily to create custom per-item-per-column behaviors, editors, etc. I was hoping I could somehow do this from a delegate by blocking an event or something similar. The problem is, I think i would have to create an entirely custom solution that mimics extended selection.

However, after a lot of searching and very few examples, it sounds like I want a custom QItemSelectionModel on my tree view. Is this assumption correct?

How do I create a custom QItemSelectionModel that will use the Extended Selection Mode but allow me to ignore or revert a selection if not in a particular column. In other words, clicking on another column should not change the selection (should not select or deselect)

I know how to add the selection model once I have it. I am asking for help implementing the derived class (unless this can be done with a connected signal).

I am using Python, but would value any help.

Thank you,

[EDIT:] I found these similar questions: http://lists.qt.nokia.com/pipermail/qt-interest/2010-September/027647.html

"Subclass QItemSelectionModel and reimplement both select methods to have the behaviour you want. Just ignore the parts of ranges with column > 0. ... Or maybe just reimplement flags() to make the item not selectable. I don't know if that will have any side effects."

I tried reimplementing flags on my QTreeWidgetItem, but it never got called:

def flags(self, index):
    print index.column()
    return super(DDOutlinerBaseItem, self).flags(index)
Was it helpful?

Solution

The following adjustment should work, in theory.

The above solution could use two separate methods and @pyqtSlot decorators to disambiguate the overloaded method names:

@pyqtSlot(QModelIndex, QItemSelectionModel.SelectionFlags)
  def select(self, index, command):
    # ...

@pyqtSlot(QItemSelection, QItemSelectionModel.SelectionFlags)
  def select(self, selection, command):
    #...

This avoids the need to check for instances of certain classes in the method implementations.

OTHER TIPS

The first interesting thing is that since Python can't overload a method, my select method seems to simply be called twice, once for each type in argument 0. Here is an example to illustrate this as well as the basic setup. My QTreeWidget's tree it called 'tree' (self.tree)

    # in __init__ of my QTreeWidget:
    sel_model = ColumnSelectionModel(self.tree.model())
    self.tree.setSelectionModel(sel_model)

class ColumnSelectionModel(QtGui.QItemSelectionModel):
    def select(self, selection, selectionFlags):
    """
    Runs both QItemSelectionModel.select methods::

        1. select(QtCore.QModelIndex, QItemSelectionModel.SelectionFlags)
        2. select(QtGui.QItemSelection, QItemSelectionModel.SelectionFlags)

    The first seems to run on mouse down and mouse up.
    The second seems to run on mouse down, up and drag
    """
    print("select(%s,  %s)" % (type(selection), type(selectionFlags)))

    if isinstance(selection, QtGui.QItemSelection):
        infos = []
        for index in selection.indexes():
            infos.append(("index=%s row=%s column=%s" 
                                    % (index, index.row(), index.column())))

        print ", ".join(infos)
    elif isinstance(selection, QtCore.QModelIndex):
        index = selection
        print("index=%s row=%s column=%s" % (index, index.row(), index.column()))
    else:
        raise Exception("Unexpected type for arg 0: '%s'" % type(selection))

    super(ColumnSelectionModel, self).select(selection, selectionFlags)

This appears to solve my problem:

class ColumnSelectionModel(QtGui.QItemSelectionModel):
    def __init__(self, model):
        super(ColumnSelectionModel, self).__init__(model)

        self.selectable_columns = [0]
        """ Set the columns that are allowed to be selected """


    def select(self, selection, selectionFlags):
        """
        Ignores any selection changes if an item is not in one of the columns
        in the self.selectable_columns list.

        Is run by both QItemSelectionModel.select methods::

            1. select(QtCore.QModelIndex, QItemSelectionModel.SelectionFlags)
            2. select(QtGui.QItemSelection, QItemSelectionModel.SelectionFlags)

        The first seems to run on mouse down and mouse up.
        The second seems to run on mouse down, up and drag
        """
        if isinstance(selection, QtGui.QItemSelection):
            # This is the overload with the QItemSelection passed to arg 0
            # Loop over all the items and if any are not in selectable_columns
            #   ignore this event. This works because it is run for every change
            #   so the offending selection index will always be the newest
            indexes = selection.indexes()
            for i in xrange(len(indexes)):
                index = indexes[i]
                if not index.column() in self.selectable_columns:
                    return
        elif isinstance(selection, QtCore.QModelIndex):
            # This is the overload with the QModelIndex passed to arg 0
            # If this index isn't in selectable_columns, just ignore this event
            index = selection
            if not index.column() in self.selectable_columns:
                return
        else:  # Just in case
            raise Exception("Unexpected type for arg 0: '%s'" % type(selection))

        # Fall through. Select as normal
        super(ColumnSelectionModel, self).select(selection, selectionFlags)

In my final implementation, I plan to delegate the decision to my delegate system, making this generic and, in theory, able to dynamically ignore any index I want.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top