简体   繁体   English

带有上下文菜单的 QTreeWidget,无法获取正确的项目

[英]QTreeWidget with contextmenu, can't get correct item

I have the following code to create a QTreeWidget and a contextmenu with 2 actions.我有以下代码来创建一个QTreeWidget和一个带有 2 个操作的上下文菜单。

import sys
from PyQt5 import QtCore, QtWidgets

class Dialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Dialog, self).__init__()

        self.tw = QtWidgets.QTreeWidget()
        self.tw.setHeaderLabels(['Name', 'Cost ($)'])
        cg = QtWidgets.QTreeWidgetItem(['carrots', '0.99'])
        c1 = QtWidgets.QTreeWidgetItem(['carrot', '0.33'])
        self.tw.addTopLevelItem(cg)
        self.tw.addTopLevelItem(c1)
        self.tw.installEventFilter(self)
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.tw)

    def eventFilter(self, source: QtWidgets.QTreeWidget, event):
        if (event.type() == QtCore.QEvent.ContextMenu and
            source is self.tw):
            menu = QtWidgets.QMenu()
            AAction = QtWidgets.QAction("AAAAA")
            AAction.triggered.connect(lambda :self.a(source.itemAt(event.pos())))
            BAction = QtWidgets.QAction("BBBBBB")
            BAction.triggered.connect(lambda :self.b(source, event))
            menu.addAction(AAction)
            menu.addAction(BAction)
            menu.exec_(event.globalPos())
            return True
        return super(Dialog, self).eventFilter(source, event)

    def a(self, item):
        if item is None:
            return
        print("A: {}".format([item.text(i) for i in range(self.tw.columnCount())]))
    def b(self, source, event):
        item = source.itemAt(event.pos())
        if item is None:
            return
        print("B: {}".format([item.text(i) for i in range(source.columnCount())]))

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = Dialog()
    window.setGeometry(600, 100, 300, 200)
    window.show()
    sys.exit(app.exec_())

When opening the contextmenu in the header and clicking on one of the actions it prints either carrot or carrots, depending on where in the contextmenu I click.当打开 header 中的上下文菜单并单击其中一个操作时,它会打印胡萝卜或胡萝卜,具体取决于我在上下文菜单中单击的位置。 But I give the position of right click event to the functions.但是我把右键事件的 position 给了函数。

So why is this happening and what can I do to stop it?那么为什么会发生这种情况,我能做些什么来阻止它呢?

Your code has 2 errors:您的代码有 2 个错误:

  • The main error is that the itemAt() method uses the coordinates with respect to the viewport() and not with respect to the view (the QTreeWidget) so you will get incorrect items (the header occupies a space making the positions with respect to the QTreeWidget and the viewport() have an offset).主要错误是 itemAt() 方法使用相对于 viewport() 而不是相对于视图(QTreeWidget)的坐标,因此您将得到不正确的项目(header 占据了一个空间,使位置相对于QTreeWidget 和 viewport() 有一个偏移量)。

  • You should not block the eventloop, for example blocking the eventFilter you may be blocking other events that would cause errors that are difficult to debug, in this case it is better to use a signal.您不应该阻塞事件循环,例如阻塞 eventFilter 您可能会阻塞其他会导致难以调试的错误的事件,在这种情况下最好使用信号。

class Dialog(QtWidgets.QDialog):
    rightClicked = QtCore.pyqtSignal(QtCore.QPoint)

    def __init__(self, parent=None):
        super(Dialog, self).__init__()

        self.tw = QtWidgets.QTreeWidget()
        self.tw.setHeaderLabels(["Name", "Cost ($)"])
        cg = QtWidgets.QTreeWidgetItem(["carrots", "0.99"])
        c1 = QtWidgets.QTreeWidgetItem(["carrot", "0.33"])
        self.tw.addTopLevelItem(cg)
        self.tw.addTopLevelItem(c1)

        self.tw.viewport().installEventFilter(self)

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.tw)

        self.rightClicked.connect(self.handle_rightClicked)

    def eventFilter(self, source: QtWidgets.QTreeWidget, event):
        if event.type() == QtCore.QEvent.ContextMenu and source is self.tw.viewport():
            self.rightClicked.emit(event.pos())
            return True

        return super(Dialog, self).eventFilter(source, event)

    def handle_rightClicked(self, pos):
        item = self.tw.itemAt(pos)
        if item is None:
            return
        menu = QtWidgets.QMenu()
        print_action = QtWidgets.QAction("Print")
        print_action.triggered.connect(lambda checked, item=item: self.print_item(item))
        menu.addAction(print_action)
        menu.exec_(self.tw.viewport().mapToGlobal(pos))

    def print_item(self, item):
        if item is None:
            return
        texts = []
        for i in range(item.columnCount()):
            text = item.text(i)
            texts.append(text)

        print("B: {}".format(",".join(texts)))

Although it is unnecessary that you use an eventFilter to handle the contextmenu since a simpler solution is to set the contextMenuPolicy of the QTreeWidget to Qt::CustomContextMenu and use the customContextMenuRequested signal:虽然没有必要使用 eventFilter 来处理 contextmenu,因为更简单的解决方案是将 QTreeWidget 的 contextMenuPolicy 设置为 Qt::CustomContextMenu 并使用 customContextMenuRequested 信号:

class Dialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Dialog, self).__init__()

        self.tw = QtWidgets.QTreeWidget()
        self.tw.setHeaderLabels(["Name", "Cost ($)"])
        cg = QtWidgets.QTreeWidgetItem(["carrots", "0.99"])
        c1 = QtWidgets.QTreeWidgetItem(["carrot", "0.33"])
        self.tw.addTopLevelItem(cg)
        self.tw.addTopLevelItem(c1)

        self.tw.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tw.customContextMenuRequested.connect(self.handle_rightClicked)

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.tw)

    def handle_rightClicked(self, pos):
        item = self.tw.itemAt(pos)
        if item is None:
            return
        menu = QtWidgets.QMenu()
        print_action = QtWidgets.QAction("Print")
        print_action.triggered.connect(lambda checked, item=item: self.print_item(item))
        menu.addAction(print_action)
        menu.exec_(self.tw.viewport().mapToGlobal(pos))

    def print_item(self, item):
        if item is None:
            return
        texts = []
        for i in range(item.columnCount()):
            text = item.text(i)
            texts.append(text)

        print("B: {}".format(",".join(texts)))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM