带有上下文菜单的 QTreeWidget,无法获取正确的项目
问题描述
我有以下代码来创建一个 QTreeWidget
和一个包含 2 个操作的上下文菜单.
I have the following code to create a QTreeWidget
and a contextmenu with 2 actions.
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. But I give the position of right click event to the functions.
那么为什么会发生这种情况,我该怎么做才能阻止它?
So why is this happening and what can I do to stop it?
解决方案
你的代码有2个错误:
主要错误是 itemAt() 方法使用相对于 viewport() 的坐标,而不是相对于视图(QTreeWidget),所以你会得到不正确的项目(标题占用空间相对于 QTreeWidget 和 viewport() 的位置有一个偏移量).
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).
你不应该阻塞事件循环,例如阻塞 eventFilter 你可能阻塞了其他会导致难以调试的错误的事件,在这种情况下最好使用信号.
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.
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)))
虽然没有必要使用 eventFilter 来处理 contextmenu,因为更简单的解决方案是将 QTreeWidget 的 contextMenuPolicy 设置为 Qt::CustomContextMenu 并使用 customContextMenuRequested 信号:
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:
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)))
相关文章