I implemented an optional list through QListWidget, and then I found that if I want to change the state of the checkbox, I can only change it by clicking the checkbox.
But I also wanted to switch the checkbox state by clicking on the Item, so I connected the ItemClicked signal of QListWidget to a new slot function to implement it.
I later discovered that the state of the checkbox associated with the Item can only be changed by clicking on the Item, not the checkbox itself.
I'm curious about what's going on in this process, and what I should do to achieve my goal, which is that the state of the checkbox can be switched by clicking on the checkbox or the item itself. Blow is my code:
class Item(QListWidgetItem):
def __init__(self):
super().__init__()
# self.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled)
self.setCheckState(Qt.CheckState.Unchecked)
self.setToolTip("double click edit")
class MainWindow(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.add_btn = QPushButton("add")
self.select_all = QPushButton("select all")
self.del_btn = QPushButton("delete")
btn_layout = QHBoxLayout()
btn_layout.addWidget(self.select_all)
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.del_btn)
self.list_widget = QListWidget()
self.list_widget.setSelectionMode(QListWidget.SelectionMode.ContiguousSelection)
self.list_widget.setAlternatingRowColors(True)
layout.addLayout(btn_layout)
layout.addWidget(self.list_widget)
self.setLayout(layout)
self.add_btn.clicked.connect(self.addItem)
# self.list_widget.itemClicked.connect(self.itemClickedEvent)
def addItem(self):
item = Item()
item.setText("test")
self.list_widget.addItem(item)
def itemClickedEvent(self, item: QListWidgetItem):
pass
if item.checkState() == Qt.CheckState.Checked:
item.setCheckState(Qt.CheckState.Unchecked)
else:
item.setCheckState(Qt.CheckState.Checked)
This is caused by the fact that, when attempting to toggle the checkbox of the item, it's also being clicked, so the check state is actually changed twice.
You can verify this by connecting to the itemChanged
signal:
self.list_widget.itemChanged.connect(lambda i: print(i.checkState()))
When you click on the check indicator, the signal is emitted twice, the first time with the new check state, and the second one with the previous state, set by your function.
A possible solution is to make the item not user checkable:
class Item(QListWidgetItem):
def __init__(self):
super().__init__()
self.setCheckState(Qt.CheckState.Unchecked)
self.setFlags(self.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
self.setToolTip("double click edit")
Unfortunately, this has the disadvantage of making it impossible to toggle the state by using the keyboard.
As an alternative, you can use a custom delegate and make it ignore the left mouse button release whenever it happens within the check indicator rectangle. In this case you must not change the item flags as explained above.
class Delegate(QStyledItemDelegate):
_checkFlags = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable
def editorEvent(self, event, model, option, index):
if (
event.type() == event.Type.MouseButtonRelease
and event.button() == Qt.MouseButton.LeftButton
and index.flags() & self._checkFlags == self._checkFlags
and option.state & QStyle.State.State_Enabled
):
opt = QStyleOptionViewItem(option)
self.initStyleOption(opt, index)
checkRect = option.widget.style().subElementRect(
QStyle.SubElement.SE_ItemViewItemCheckIndicator, opt, option.widget)
if event.pos() in checkRect:
return False
return super().editorEvent(event, model, option, index)
...
self.list_widget.setItemDelegate(Delegate(self.list_widget))