首页 > 解决方案 > pyqt 多个对象共享上下文菜单

问题描述

所以我对 qt 相对较新,并且对 python 相当熟悉。但是,我有一个实例,我希望多个小部件(在本例中为标签)共享相同的自定义上下文菜单,但我需要访问小部件的信息。

当我在每个标签上使用setContextMenuPolicyandcustomContextMenuRequested.connect时,尽管使用第二个标签访问上下文菜单,但我只能获得第一个标签的信息。

以下是我正在使用的精简版:

from PyQt5 import QtGui
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QLabel
import sys


class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = "PyQt5 Context Menu"
        self.top = 200
        self.left = 500
        self.width = 200
        self.height = 100
        self.InitWindow()

    def InitWindow(self):
        self.setWindowIcon(QtGui.QIcon("icon.png"))
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.firstLabel = QLabel(self)
        self.firstLabel.setText("Meep!")
        self.firstLabel.setObjectName("firstLabel")
        self.firstLabel.setStyleSheet("background-color: rgb(252, 233, 79);")

        self.firstLabel.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.firstLabel.customContextMenuRequested.connect(self.customMenuEvent)

        self.firstLabel.setGeometry(QtCore.QRect(0,0,50,30))


        self.secondLabel = QLabel(self)
        self.secondLabel.setText("Peem!")
        self.secondLabel.setObjectName("secondLabel")
        self.secondLabel.setStyleSheet("background-color: rgb(79,233, 252);")

        self.secondLabel.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.secondLabel.customContextMenuRequested.connect(self.customMenuEvent)

        self.secondLabel.setGeometry(QtCore.QRect(80,40,50,30))

        print("FIRST:", self.firstLabel)
        print("SECOND:", self.secondLabel)

        self.show()

    def customMenuEvent(self, eventPosition):
        child = self.childAt(eventPosition)
        print(child)
        contextMenu = QMenu(self)
        getText = contextMenu.addAction("Text")
        getName = contextMenu.addAction("Name")
        quitAct = contextMenu.addAction("Quit")
        action = contextMenu.exec_(self.mapToGlobal(eventPosition))

        if action == getText:
            print(child.text())

        if action == getName:
            print(child.objectName())

        if action == quitAct:
            self.close()



App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())

标签: pythonpyqt5contextmenu

解决方案


从有关customContextMenuRequested(pos)信号的文档中:

位置 pos 是小部件接收到的上下文菜单事件的位置

这意味着您将始终收到相对于触发信号的小部件的鼠标位置。

您使用QWidget.childAt()的是相对于父几何图形的 ,但由于提供的位置是相对于子小部件的,因此您最终会得到相对于父几何图形左上角的坐标。

如果您尝试将第一个小部件的几何形状设置在不是左上角的位置,这一点就会变得很清楚:即使您右键单击第一个小部件,您也会看到菜单不会出现在您单击的位置。如果您仔细观察,您还会看到菜单恰好出现在您单击的位置,基于父级左上角的坐标。

为了简单起见,一个简单的解决方案是将坐标从“ sender”(这是发射接收器收到的最后一个信号的对象)映射到其父对象:

    def customMenuEvent(self, eventPosition):
        child = self.childAt(self.sender().mapTo(self, eventPosition))
        contextMenu = QMenu(self)
        getText = contextMenu.addAction("Text")
        getName = contextMenu.addAction("Name")
        quitAct = contextMenu.addAction("Quit")

        # note that the mapToGlobal is referred to the child!
        action = contextMenu.exec_(child.mapToGlobal(eventPosition))
        # ...

但是请注意,这可能会导致一些不一致,尤其是在处理多线程时(因为理论上,另一个线程可能在右键单击事件接收者实际接收到它的那一刻之间触发了另一个信号)。

有多种不同的方法可以避免这种情况,但 IT 主要取决于您将如何构建程序。

例如,您可以使用 lambda 添加一个参数,以帮助识别发送事件的来源:

        self.firstLabel.customContextMenuRequested.connect(
            lambda pos, child=self.firstLabel: self.customMenuEvent(pos, child))
        # ...

    def customMenuEvent(self, eventPosition, child):
        # ...

推荐阅读