Центрируйте делегат флажка в QTableView с помощью PySide6

Я использовал делегата флажка в своем QTableView, первоначально созданном примерно во времена PyQt 4.8 или PySide 1.2.1. В PySide2 это тоже работало, но когда я попытался обновить свое приложение до PySide6, оно уже не работало (сейчас я уже не помню точно, в чем была проблема).

Более новая версия была опубликована для PyQt5 по адресу https://stackoverflow.com/a/50314085/224310 , которая в основном работала, за исключением того, что делегат был подклассом QItemDelegate вместо QStyledItemDelegate, флажки были нарисованы ужасно большими. У других, похоже, была такая же проблема, как я нашел на https://forum.qt.io/topic/142124/pyqt-checkbox-solves-for-dependent-scales, но на плакате было указано, что они в конечном итоге решили свою проблему. «путем установки флажков через Qt.ItemIsUserCheckable, а не через делегата». Я попробовал то же самое, и это сработало, но кажется, что флажки, реализованные с помощью Qt.ItemIsUserCheckable, не могут быть центрированы в столбце QTableView. Чтобы сосредоточить их, вам нужен... делегат.

Я часами исследовал и пробовал разные решения, и меня поражает то, что то, что кажется распространенным вариантом использования столбца центрированных флажков в QTableView, так сложно реализовать на Python. (Хорошо, я работал с Qt достаточно долго, поэтому меня это не особо удивляет.)

Есть ли у кого-нибудь предложения о том, как реализовать в PySide6 столбец флажков, которые центрируются в столбце и не выглядят ужасно при больших разрешениях?

(Я отвечу на свой вопрос и опубликую свое окончательное решение ниже, но буду рад и другим (лучшим) предложениям!)

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
160
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Читая посты с похожими проблемами других людей, я наткнулся на ссылку на решение C++ по адресу https://wiki.qt.io/Center_a_QCheckBox_or_Decoration_in_an_Itemview. Мне не удалось найти подобное решение, написанное непосредственно на Python. Я не знаю C++, но с помощью онлайн-инструмента конвертации мне удалось получить версию, похожую на Python, которую затем удалось переписать для работы с PySide6 и с чем-то более современным (я м, используя Python 3.12).


from PySide6 import QtCore, QtWidgets, QtGui

class StyledCheckboxDelegate(QtWidgets.QStyledItemDelegate):
    """Centered checkbox delegate to use in a QTableView.

    Adapted from https://wiki.qt.io/Center_a_QCheckBox_or_Decoration_in_an_Itemview
    """

    def __init__(self, parent=None):

        super().__init__(parent=parent)


    def paint(self, painter, option, index):

        self.initStyleOption(option, index)
        widget = option.widget
        style = widget.style() if widget else QtWidgets.QApplication.style()
        style.drawPrimitive(
                QtWidgets.QStyle.PrimitiveElement.PE_PanelItemViewItem,
                option,
                painter,
                widget
                )

        if (QtWidgets.QStyleOptionViewItem.ViewItemFeature.HasCheckIndicator
                in option.features):
            if option.checkState == QtCore.Qt.CheckState.Unchecked:
                option.state |= QtWidgets.QStyle.StateFlag.State_Off

            elif option.checkState == QtCore.Qt.CheckState.PartiallyChecked:
                option.state |= QtWidgets.QStyle.StateFlag.State_NoChange

            elif option.checkState == QtCore.Qt.CheckState.Checked:
                option.state |= QtWidgets.QStyle.StateFlag.State_On

            rect = style.subElementRect(
                QtWidgets.QStyle.SubElement.SE_ItemViewItemCheckIndicator,
                option,
                widget
                )
            option.rect = QtWidgets.QStyle.alignedRect(
                option.direction,
                QtCore.Qt.AlignmentFlag(
                        index.data(QtCore.Qt.ItemDataRole.TextAlignmentRole).value
                        ),
                rect.size(),
                option.rect
                )
            option.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus
            style.drawPrimitive(
                QtWidgets.QStyle.PrimitiveElement.PE_IndicatorItemViewItemCheck,
                option,
                painter,
                widget
                )

        elif not option.icon.isNull():
            icon_rect = style.subElementRect(
                    QtWidgets.QStyle.SubElement.SE_ItemViewItemDecoration,
                    option,
                    widget
                    )
            icon_rect = QtWidgets.QStyle.alignedRect(
                    option.direction,
                    QtCore.Qt.AlignmentFlag(
                            index.data(QtCore.Qt.ItemDataRole.TextAlignmentRole).value
                            ),
                    icon_rect.size(),
                    option.rect
                    )
            mode = QtGui.QIcon.Mode.Normal
            if QtWidgets.QStyle.StateFlag.State_Enabled not in option.state:
                mode = QtGui.QIcon.Mode.Disabled

            elif QtWidgets.QStyle.StateFlag.State_Selected in option.state:
                mode = QtGui.QIcon.Mode.Selected

            state = (
                    QtGui.QIcon.State.On
                    if QtWidgets.QStyle.StateFlag.State_Open in option.state
                    else QtGui.QIcon.State.Off
                    )
            option.icon.paint(
                    painter,
                    icon_rect,
                    option.decorationAlignment,
                    mode,
                    state
                    )

        else:
            super().paint(painter, option, index)


    def editorEvent(self, event, model, option, index):

        # Make sure that the item is checkable
        flags = model.flags(index)
        if (QtCore.Qt.ItemFlag.ItemIsUserCheckable not in flags
                or QtWidgets.QStyle.StateFlag.State_Enabled not in option.state
                or QtCore.Qt.ItemFlag.ItemIsEnabled not in flags):
            return False

        # Make sure that we have a check state
        state = index.data(QtCore.Qt.ItemDataRole.CheckStateRole)
        if state is None:
            return False

        widget = option.widget
        style = widget.style() if widget else QtWidgets.QApplication.style()

        # Make sure that we have the right event type
        if event.type() in (
                QtCore.QEvent.Type.MouseButtonRelease,
                QtCore.QEvent.Type.MouseButtonDblClick,
                QtCore.QEvent.Type.MouseButtonPress,
                ):
            view_opt = QtWidgets.QStyleOptionViewItem(option)
            self.initStyleOption(view_opt, index)
            check_rect = style.subElementRect(
                    QtWidgets.QStyle.SubElement.SE_ItemViewItemCheckIndicator,
                    view_opt,
                    widget,
                    )
            check_rect = QtWidgets.QStyle.alignedRect(
                    view_opt.direction,
                    QtCore.Qt.AlignmentFlag(
                            index.data(QtCore.Qt.ItemDataRole.TextAlignmentRole).value
                            ),
                    check_rect.size(),
                    view_opt.rect,
                    )
            if (isinstance(event, QtGui.QMouseEvent)
                    and (event.button() != QtCore.Qt.MouseButton.LeftButton
                         or not check_rect.contains(event.position().toPoint())
                         )):
                return False

            if event.type() in (
                    QtCore.QEvent.Type.MouseButtonPress,
                    QtCore.QEvent.Type.MouseButtonDblClick,
                    ):
                return True

        elif event.type() == QtCore.QEvent.Type.KeyPress:
            if event.key() not in (
                    QtCore.Qt.Key.Key_Space,
                    QtCore.Qt.Key.Key_Select,
                    ):
                return False

        else:
            return False

        # convert to an Enum for easy comparison
        state = QtCore.Qt.CheckState(state)
        if QtCore.Qt.ItemFlag.ItemIsUserTristate in flags:
            state = QtCore.Qt.CheckState((state.value + 1) % 3)

        else:
            state = (
                    QtCore.Qt.CheckState.Checked
                    if state == QtCore.Qt.CheckState.Unchecked
                    else QtCore.Qt.CheckState.Unchecked
                    )

        # set the new checkbox state in the model (as its Enum value)
        return model.setData(
                index,
                state.value,
                QtCore.Qt.ItemDataRole.CheckStateRole,
                )


if __name__ == "__main__":

    app = QtWidgets.QApplication(sys.argv)

    model = QtGui.QStandardItemModel()
    model.setColumnCount(1)
    model.setRowCount(2)

    checkable_item = QtGui.QStandardItem()
    checkable_item.setFlags(
            checkable_item.flags() | QtCore.Qt.ItemFlag.ItemIsUserCheckable
            )
    checkable_item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
    checkable_item.setCheckState(QtCore.Qt.CheckState.Checked)
    model.setItem(0, 0, checkable_item)

    checkable_item = QtGui.QStandardItem()
    checkable_item.setFlags(
            checkable_item.flags() | QtCore.Qt.ItemFlag.ItemIsUserCheckable
            )
    checkable_item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
    checkable_item.setCheckState(QtCore.Qt.CheckState.Checked)
    model.setItem(1, 0, checkable_item)

    table_view = QtWidgets.QTableView()
    table_view.setModel(model)
    table_view.setItemDelegate(StyledCheckboxDelegate())
    #table_view.setItemDelegateForColumn(0, StyledCheckboxDelegate()) # when only a single column should use the delegate
    table_view.show()
    sys.exit(app.exec())

Надеюсь, это сэкономит кому-то другому (или мне самому в будущем) несколько часов времени на поиск аналогичного решения!

Другие вопросы по теме