11

I am using QTableView's checkbox flag of Qt::ItemIsUserCheckable to display a checkbox in a table cell.

After reading some things on alignment in an attempt to center the checkbox within the cell, I am returning the Qt::AlignCenter as the TextAlignmentRole from the models data() function.

QVariant ExampleModel::data(const QModelIndex &index, int role) const 
{
  if(!index.isValid())
     return QVariant();

  if (role == Qt::TextAlignmentRole) 
       return Qt::AlignCenter | Qt::AlignVCenter;
}

This however is not aligning my checkbox.

Does anyone know how to align checkboxes is this mode?

cweston
  • 11,297
  • 19
  • 82
  • 107

7 Answers7

4

After further investigation into delegate options I found a nice reference (unfortunately no longer available) and came up with the following hybrid using a QItemDelegate and IsUserCheckable.

Essentially, you need to extend QItemDelegate, and reimplement, using the drawCheck function to center and use the editorEvent to handle mouse and keyboard events while setting the model with the appropriate state.

void drawCheck(QPainter* painter, QStyleOptionViewItem const& option, QRect const& rect, Qt::CheckState state) const

and

bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)

Also see this similar question here...

Community
  • 1
  • 1
cweston
  • 11,297
  • 19
  • 82
  • 107
3

Solution for Python (PySide, PyQt) to center the checkbox and with editable allowed:

class BooleanDelegate(QItemDelegate):

    def __init__(self, *args, **kwargs):
        super(BooleanDelegate, self).__init__(*args, **kwargs)

    def paint(self, painter, option, index):
        # Depends on how the data function of your table model is implemented
        # 'value' should recive a bool indicate if the checked value.
        value = index.data(Qt.CheckStateRole)  
        self.drawCheck(painter, option, option.rect, value)
        self.drawFocus(painter, option, option.rect)

    def editorEvent(self, event, model, option, index):
        if event.type() == QEvent.MouseButtonRelease:
            value = bool(model.data(index, Qt.CheckStateRole))
            model.setData(index, not value)
            event.accept()
        return super(BooleanDelegate, self).editorEvent(event, model, option, index)

In your table model, make sure that the flags allow the user to check/uncheck the cell.

class MyTableModel(QAbstractTableModel):

    ...

    def flags(self, index):
        if not index.isValid():
            return Qt.ItemIsEnabled
        if index.column() in self.columns_boolean:
            return Qt.ItemIsEnabled | Qt.ItemIsUserCheckable
        return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable)

Finally, set the BooleanDelagate in your table

self.boolean_delegate = BooleanDelegate()
self.input_gui.setItemDelegateForColumn(5, self.boolean_delegate)
E.G. Cortes
  • 65
  • 12
2

Probably not the answer you're looking for, however I found it much easier to implement my own checkbox item delegate when using qtableviews.

Chris
  • 1,066
  • 8
  • 9
2

TextAlignmentRole really does mean what it says. Unfortunately, as you probably noticed, there doesn't seem to be any Icon/Widget alignment role available at all.

Bug report: http://bugreports.qt-project.org/browse/QTBUG-9047

Same question with some answers: http://lists.trolltech.com/qt-interest/2006-06/msg00476.html

rene
  • 41,474
  • 78
  • 114
  • 152
OliJG
  • 2,650
  • 1
  • 16
  • 15
1

This is the solution I came up with. This is assuming that you want the checkbox to be the only thing in the cell.

class CenteredCheckboxDelegate final : public QStyledItemDelegate
{
public:
    using QStyledItemDelegate::QStyledItemDelegate;

    void paint(QPainter * painter, const QStyleOptionViewItem & o, const QModelIndex & index ) const override
    {
        auto option2 = o;
        initStyleOption(&option2, index);

        auto * widget = option2.widget;
        auto * style = widget ? widget->style() : QApplication::style();

        // Turn off all features and just draw the background
        option2.state.setFlag(QStyle::State_HasFocus, false);
        option2.features.setFlag(QStyleOptionViewItem::HasDisplay, false);
        option2.features.setFlag(QStyleOptionViewItem::HasDecoration, false);
        option2.features.setFlag(QStyleOptionViewItem::HasCheckIndicator, false);
        style->drawControl(QStyle::CE_ItemViewItem, &option2, painter, widget);

        // Then just draw the a checkbox centred in the cell
        option2.rect = getCheckboxRect(option2);
        auto stateFlag = option2.checkState == Qt::Checked ? QStyle::State_On : QStyle::State_Off;
        option2.state.setFlag(stateFlag, true);
        style->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &option2, painter, widget);
    }

    bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override
    {
        auto flags = index.flags();
        if (!flags.testFlag(Qt::ItemIsUserCheckable) || !flags.testFlag(Qt::ItemIsEnabled))
        {
            return false;
        }

        if(event->type() == QEvent::MouseButtonRelease)
        {
            auto * mouseEvent = static_cast<QMouseEvent*>(event);
            bool mouseOverCheckbox = getCheckboxRect(option).contains(mouseEvent->pos());
            if(!mouseOverCheckbox) return false;
        }
        else if(event->type() == QEvent::KeyPress)
        {
            auto * keyEvent = static_cast<QKeyEvent*>(event);
            if(keyEvent->key() != Qt::Key_Space) return false;
        }
        else
        {
            return false;
        }

        auto checkState = index.data(Qt::CheckStateRole).value<Qt::CheckState>();
        auto toggledCheckState = checkState == Qt::Checked ? Qt::Unchecked : Qt::Checked;
        return model->setData(index, toggledCheckState, Qt::CheckStateRole);
    }

private:
    QRect getCheckboxRect(const QStyleOptionViewItem & option) const
    {
        auto * widget = option.widget;
        auto * style = widget ? widget->style() : QApplication::style();
        auto checkboxSize = style->subElementRect(QStyle::SE_CheckBoxIndicator, &option, widget).size();
        return QStyle::alignedRect(option.direction, Qt::AlignCenter, checkboxSize, option.rect);
    }
};
Parker Coates
  • 8,520
  • 3
  • 31
  • 37
0

PyQt5 / Pyside2 Version, based on the amazing C++ version of Parker Coates

class QTableviewEditableDelegate(QStyledItemDelegate):

def __init__(self, alignment: Qt.Alignment, parent: Optional[QWidget] = None):
    super().__init__(parent)
    self.alignment: Qt.Alignment = alignment
    self.parent = parent

    if self.parent:
        self.style = self.parent.style()
    else:
        self.style = QApplication.style()

def editorEvent(self, event: QMouseEvent, model: QAbstractItemModel, option: QStyleOptionViewItem,
                index: QModelIndex) -> bool:
    checkbox_data = index.data(Qt.CheckStateRole)
    flags = index.flags()
    if not (flags & Qt.ItemIsUserCheckable) or not (flags & Qt.ItemIsEnabled) or checkbox_data is None:
        return False
    else:
        if event.type() == QEvent.MouseButtonRelease:
            mouseover_checkbox: bool = self.get_checkbox_rect(option).contains(event.pos())
            if not mouseover_checkbox:
                return False
        elif event.type() == QEvent.KeyPress and event.key() != Qt.Key_Space:
            return False
        else:
            return False
        if checkbox_data == Qt.Checked:
            checkbox_toggled: int = Qt.Unchecked
        else:
            checkbox_toggled: int = Qt.Checked
        return model.setData(index, checkbox_toggled, Qt.CheckStateRole)

def get_checkbox_rect(self, option: QStyleOptionViewItem) -> QRect:
    widget = option.widget
    if widget:
        style = widget.style()
    else:
        style = self.style()
    checkbox_size: QSize = style.subElementRect(QStyle.SE_CheckBoxIndicator, option, widget).size()
    return QStyle.alignedRect(option.direction, Qt.AlignCenter, checkbox_size, option.rect)

def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex):
    try:
        self.initStyleOption(option, index)
        painter.save()

        flags: Qt.ItemFlags = index.model().flags(index)
        widget: Optional[QWidget] = option.widget
        checkbox_data = index.data(Qt.CheckStateRole)
        if widget:
            style = widget.style()
        else:
            style = self.style()

        if option.HasCheckIndicator and checkbox_data is not None:
            option_checkbox = option
            self.initStyleOption(option_checkbox, index)
            option_checkbox.state = option_checkbox.state & ~QStyle.State_HasFocus
            option_checkbox.features = option_checkbox.features & ~QStyleOptionViewItem.HasDisplay
            option_checkbox.features = option_checkbox.features & ~QStyleOptionViewItem.HasDecoration
            option_checkbox.features = option_checkbox.features & ~QStyleOptionViewItem.HasCheckIndicator
            style.drawControl(QStyle.CE_ItemViewItem, option_checkbox, painter, widget)

            # Then just draw the a checkbox centred in the cell
            option_checkbox.rect = self.get_checkbox_rect(option_checkbox)
            if option_checkbox.checkState == Qt.Checked:
                state_flag = QStyle.State_On
            else:
                state_flag = QStyle.State_Off

            option_checkbox.state = option_checkbox.state | state_flag
            style.drawPrimitive(QStyle.PE_IndicatorViewItemCheck, option_checkbox, painter, widget)

        else:
            QStyledItemDelegate.paint(self, painter, option, index)
       
        painter.restore()

    except Exception as e:
        print(repr(e))
nuipek
  • 61
  • 1
  • 3