如何对项目的复选框和文本以不同的方式处理点击事件? (PyQt/PySide/Qt)

Posted

技术标签:

【中文标题】如何对项目的复选框和文本以不同的方式处理点击事件? (PyQt/PySide/Qt)【英文标题】:How to treat click events differently for an item's checkbox versus its text? (PyQt/PySide/Qt) 【发布时间】:2015-04-15 02:05:40 【问题描述】:

我有一个QStandardItemModel,其中每个项目都是可检查的。一方面,当我单击项目的复选框时,我希望调用不同的插槽,另一方面,当我单击其文本时。我的最终目标是进行文本编辑和复选框状态的更改,分别转到QUndoStack

在我对clicked 的重新实现中,我想区别对待复选框点击和文本点击。到目前为止,我还没有办法区分the documentation 中的这些事件,用于QCheckBoxQStandardItem. 虽然QCheckBox 有一个我可以使用的toggled 信号,但我不确定如何专门监听点击文本区域。

我试图避免手动设置坐标,然后在项目视图的不同区域监听点击。

这似乎不像调用itemChanged 这样简单,因为这只会为您提供项目的 状态,而不是之前的状态。基于之前的问题,我相信您需要某种方法将之前的状态打包到撤消堆栈中,这样您就知道要恢复到什么。这就是我的目标是使用 clicked,但可能有更好的方法。

这个问题借鉴了本系列的前两个问题,我试图弄清楚如何撤消模型中的内容:

How to undo edit of QStandardItem in PySide/PyQt? How to undo an edit of a QListWidgetItem in PySide/PyQt?

【问题讨论】:

这很麻烦,但是您可以创建一个 QCheckBox 并将小部件嵌入到 QTreeWidget (setItemWidget) 中。您可以将一种方法连接到 QCheckBox 单击方法,并将另一种方法连接到 itemChanged 信号。 【参考方案1】:

基于ekhumoro's suggestion 和代码块,我构建了QStandardItemModel 的树视图,当项目更改时会发出自定义信号。该代码通过setData 中的角色区分文本与复选框更改(对于文本,使用Qt.EditRole,对于复选框状态更改,使用Qt.CheckStateRole):

# -*- coding: utf-8 -*-

from PySide import QtGui, QtCore
import sys

class CommandTextEdit(QtGui.QUndoCommand):
    def __init__(self, tree, item, oldText, newText, description):
        QtGui.QUndoCommand.__init__(self, description)
        self.item = item
        self.tree = tree
        self.oldText = oldText
        self.newText = newText

    def redo(self):      
        self.item.model().itemDataChanged.disconnect(self.tree.itemDataChangedSlot) 
        self.item.setText(self.newText)
        self.item.model().itemDataChanged.connect(self.tree.itemDataChangedSlot) 

    def undo(self):
        self.item.model().itemDataChanged.disconnect(self.tree.itemDataChangedSlot) 
        self.item.setText(self.oldText)
        self.item.model().itemDataChanged.connect(self.tree.itemDataChangedSlot) 


class CommandCheckStateChange(QtGui.QUndoCommand):
    def __init__(self, tree, item, oldCheckState, newCheckState, description):
        QtGui.QUndoCommand.__init__(self, description)
        self.item = item
        self.tree = tree
        self.oldCheckState = QtCore.Qt.Unchecked if oldCheckState == 0 else QtCore.Qt.Checked
        self.newCheckState = QtCore.Qt.Checked if oldCheckState == 0 else QtCore.Qt.Unchecked

    def redo(self): #disoconnect to avoid recursive loop b/w signal-slot
        self.item.model().itemDataChanged.disconnect(self.tree.itemDataChangedSlot) 
        self.item.setCheckState(self.newCheckState)
        self.item.model().itemDataChanged.connect(self.tree.itemDataChangedSlot) 

    def undo(self):
        self.item.model().itemDataChanged.disconnect(self.tree.itemDataChangedSlot)
        self.item.setCheckState(self.oldCheckState)
        self.item.model().itemDataChanged.connect(self.tree.itemDataChangedSlot) 


class StandardItemModel(QtGui.QStandardItemModel):
    itemDataChanged = QtCore.Signal(object, object, object, object)


class StandardItem(QtGui.QStandardItem):
    def setData(self, newValue, role=QtCore.Qt.UserRole + 1):
        if role == QtCore.Qt.EditRole:
            oldValue = self.data(role)
            QtGui.QStandardItem.setData(self, newValue, role)
            model = self.model()
            #only emit signal if newvalue is different from old
            if model is not None and oldValue != newValue:
                model.itemDataChanged.emit(self, oldValue, newValue, role)
            return True
        if role == QtCore.Qt.CheckStateRole:
            oldValue = self.data(role)
            QtGui.QStandardItem.setData(self, newValue, role)
            model = self.model()
            if model is not None and oldValue != newValue:
                model.itemDataChanged.emit(self, oldValue, newValue, role)
            return True
        QtGui.QStandardItem.setData(self, newValue, role)


class UndoableTree(QtGui.QWidget):
    def __init__(self, parent = None):
        QtGui.QWidget.__init__(self, parent = None)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.view = QtGui.QTreeView()
        self.model = self.createModel()
        self.view.setModel(self.model)
        self.view.expandAll()
        self.undoStack = QtGui.QUndoStack(self)
        undoView = QtGui.QUndoView(self.undoStack)
        buttonLayout = self.buttonSetup()
        mainLayout = QtGui.QHBoxLayout(self)
        mainLayout.addWidget(undoView)
        mainLayout.addWidget(self.view)
        mainLayout.addLayout(buttonLayout)
        self.setLayout(mainLayout)
        self.makeConnections()

    def makeConnections(self):
        self.model.itemDataChanged.connect(self.itemDataChangedSlot)
        self.quitButton.clicked.connect(self.close)
        self.undoButton.clicked.connect(self.undoStack.undo)
        self.redoButton.clicked.connect(self.undoStack.redo)

    def itemDataChangedSlot(self, item, oldValue, newValue, role):
        if role == QtCore.Qt.EditRole:
            command = CommandTextEdit(self, item, oldValue, newValue,
                "Text changed from '0' to '1'".format(oldValue, newValue))
            self.undoStack.push(command)
            return True
        if role == QtCore.Qt.CheckStateRole:
            command = CommandCheckStateChange(self, item, oldValue, newValue, 
                "CheckState changed from '0' to '1'".format(oldValue, newValue))
            self.undoStack.push(command)
            return True

    def buttonSetup(self):
        self.undoButton = QtGui.QPushButton("Undo")
        self.redoButton = QtGui.QPushButton("Redo")
        self.quitButton = QtGui.QPushButton("Quit")
        buttonLayout = QtGui.QVBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.undoButton)
        buttonLayout.addWidget(self.redoButton)
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.quitButton)
        return buttonLayout

    def createModel(self):
        model = StandardItemModel()
        model.setHorizontalHeaderLabels(['Titles', 'Summaries'])
        rootItem = model.invisibleRootItem()
        item0 = [StandardItem('Title0'), StandardItem('Summary0')]
        item00 = [StandardItem('Title00'), StandardItem('Summary00')]
        item01 = [StandardItem('Title01'), StandardItem('Summary01')]
        item0[0].setCheckable(True)
        item00[0].setCheckable(True)
        item01[0].setCheckable(True)
        rootItem.appendRow(item0)
        item0[0].appendRow(item00)
        item0[0].appendRow(item01)
        return model


def main():
    app = QtGui.QApplication(sys.argv)
    newTree = UndoableTree()
    newTree.show()
    sys.exit(app.exec_())    

if __name__ == "__main__":
    main()

【讨论】:

【参考方案2】:

clicked 信号似乎是完全错误的跟踪更改的方法。您将如何处理通过键盘进行的更改?那么以编程方式进行的更改呢?为了使撤消堆栈正常工作,必须记录每个更改,并且按照与所做更改完全相同的顺序。

如果您使用的是QStandardItemModelitemChanged 信号几乎就是您想要的。但是,它的主要问题是,尽管它发送了更改的 item,但它没有提供有关 what 更改的信息。所以看起来你需要做一些子类化并发出一个自定义信号来做到这一点:

class StandardItemModel(QtGui.QStandardItemModel):
    itemDataChanged = QtCore.Signal(object, object, object)

class StandardItem(QtGui.QStandardItem):
    def setData(self, newValue, role=QtCore.Qt.UserRole + 1):
        oldValue = self.data(role)
        QtGui.QStandardItem.setData(self, newValue, role)
        model = self.model()
        if model is not None:
            model.itemDataChanged.emit(oldValue, newvValue, role)

请注意,仅在将项目添加到模型之后发出信号。

【讨论】:

优秀的批评,我希望你能站出来让我直截了当:):***.com/questions/29527610/…。今晚完成工作后会看看这个....并尝试看看我是否可以使用您在上一篇文章中关于将文本编辑推送到撤消堆栈的想法.... 我认为您的解决方案不起作用:如果仅获取数据就足够了,itemChanged 会很好(对于文本编辑案例)。但它只返回 new 文本,而不是旧文本。因此,本系列前两个问题中使用的技巧(我现在已在我的问题中明确链接到)。 可能要做的事情是有条件的:先检查checkstate是否改变,如果有not,则更新文本。即使这样,我也需要一种方法来存储以前的文本,并且我对“点击”以外的信号持开放态度,我承认这可能不是最好的。也许只是选定的项目而不是点击的项目。 @neuronet。我没有看到问题。我的答案中的示例代码只是一个简单的草图——它可以很容易地适应你想做的任何事情(我已经对其进行了一些调整以表明我的意思)。要意识到的重要一点是data()setData() 是对标准模型项进行的所有 数据更改的单点退出和进入。所有其他 API 最终都依赖于它们:这是 Model Subclassing 的基本功能。 我终于用你的许多建议完成了一个简单的小例子......codereview.stackexchange.com/questions/88065/… 以防万一有人想在那里发表评论......咳咳......

以上是关于如何对项目的复选框和文本以不同的方式处理点击事件? (PyQt/PySide/Qt)的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式将文本注释添加到图形

如何防止指定任务触发

如何更改复选框输入样式,以便用户点击标签而不是实际框?

“event.target as Checkbox”通过 Mouse CLICK 事件传递“selected”属性的不同值

单选框

是否必须以不同的方式处理键盘事件才能在 Matplotlib/PyQt5 中选择事件? [复制]