Search code examples
c++qtmodel-view-controller

How can I make one view in Qt display checkboxes while the other does not?


I have two views in Qt. Both use a model derived from QAbstractListModel.

I would like to show a checkbox in only one of the two views. Is this possible without creating an entirely new view class? I use C++.

I tried creating a new view class that is derived from my main view class and where only the flags() function is overridden. The idea was that both views would use the same model object but one view would call flags() in the base class whereas the other would call flags() in the derived class. But the derived class ends up being used in both cases.


Solution

  • You cannot control how a view accesses flags() (nor you should try to).

    As suggested in the comments, you could use a QIdentityProxyModel, but overriding flags() won't suffice, and data() must be overridden too.

    When flags() returns the source flags without the Qt::ItemIsUserCheckable it will only prevent the user to check the item, but the check box will still be visible as long as a CheckStateRole value is set, which is reasonable: as the name suggests, that flag only tells if the item is "checkable by the user", but the model may still need to show check boxes for displaying purposes.

    In order to completely hide the check box, data() should return QVariant() whenever the role is Qt::CheckStateRole.

    In reality, the implementation of data() alone should be enough, but overriding flags() would be more safe, appropriate and consistent.

    I won't attempt to write bad C++ code, but the following Python example should be clear enough:

    class Proxy(QIdentityProxyModel):
        def data(self, index, role=Qt.DisplayRole):
            if role == Qt.CheckStateRole:
                # in C++ this would be "return QVariant()", it's also possible
                # in PySide/PyQt but actually unnecessary as it is considered
                # implicit in this context.
                return
    
            # for any other role, return what the source model's data() would
            return super().data(index, role)
    
        def flags(self, index):
            # get the default result from flags() and return it by removing
            # the unwanted flag in the meantime
            return super().flags(index) & ~Qt.ItemIsUserCheckable
    

    Alternatively, you can use an item delegate that overrides initStyleOption() and unsets the HasCheckIndicator flag from the option's feature, which has an effect similar to what data() did above, but in this case we have the opposite problem: "hiding" the check state only prevents toggling it using the mouse (because there's no visible check indicator to click), but checkable items can also be toggled using the keyboard (with Space bar or the Select key that some keyboards and input devices have), so you should also ensure that editorEvent() doesn't handle keyboard events that may trigger the check state toggling anyway:

    The base implementation returns false (indicating that it has not handled the event).

    In this case, the space/select keys are eventually used by the view to start editing the index (assuming that the model is writable and the view's edit triggers allow it).

    As above, a basic example of how this can be achieved in Python:

    class NoCheckDelegate(QStyledItemDelegate):
        def initStyleOption(self, opt, index):
            # set up the option based on the index
            super().initStyleOption(opt, index)
            # remove the check indicator flag to prevent both displaying it and
            # possible mouse interaction normally used for toggling it
            opt.features &= ~opt.HasCheckIndicator
    
        def editorEvent(self, event, model, opt, index):
            if (
                event.type() == event.KeyPress and (
                    event.key() == Qt.Key_Space 
                    or event.key() == Qt.Key_Select
                )
            ):
                # "ignore" the event and let the view handle it if possible
                return False
            # just do anything else as expected from the delegate
            return super().editorEvent(event, model, opt, index)