QFileDialog 中的上下文菜单

问题描述 投票:0回答:2

我想在 QFileDialog 中实现自定义上下文菜单。在下面的代码中,我设法创建主窗口的上下文菜单,但我希望在选择文件时显示菜单:如何知道我应该应用 QFileDialog 中的正确小部件

setContextMenuPolicy

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class Window(QWidget):

    def __init__(self):

        QWidget.__init__(self)

        self.myFileDialog = QFileDialog()

        self.myFileDialog.setContextMenuPolicy(Qt.CustomContextMenu)
        self.myFileDialog.customContextMenuRequested.connect(self.openMenu)

        layout = QVBoxLayout()
        layout.addWidget(self.myFileDialog)
        self.setLayout(layout)

        self.action_perso = QAction( "MyOwnMenu", self )
        self.connect( self.action_perso, SIGNAL("triggered()"), self.test )

    def openMenu(self, position):
        menu = QMenu()
        menu.addAction(self.action_perso)
        menu.exec_(self.myFileDialog.mapToGlobal(position))

    def test(self):
        print("coucou")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())
python pyqt pyqt4 contextmenu qfiledialog
2个回答
1
投票

我找到了一个解决方案,但这可能不是最好的。它依赖于两个要素:

  1. 感谢个人函数

    objectTree
    (此处显示但未使用)递归地列出了
    QFileDialog
    的所有子对象,我识别了正确的小部件,即
    QTreeView
    (我知道QTreeView是正确的小部件)通过尝试连续隐藏所有 QListView 和 QTreeView 小部件)。因此,我可以通过它的 objectName 来选择它
    self.findChild(QTreeView, "treeView"
    )

  2. setContextMenuPolicy( Qt.ActionsContextMenu )
    应用于此 QTreeView。我还尝试实现一个
    setContextMenuPolicy(Qt.CustomContextMenu)
    ,并且它部分起作用:我的菜单确实出现了,但在未激活的原始菜单下!
    以下是我建议的代码:


import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class CustomWidget(QFileDialog):
    def __init__(self, parent=None):
        super(CustomWidget,self).__init__(parent)

        # fetch the QTreeView in the QFileDialog
        self.myTree = self.findChild(QTreeView, "treeView")

        # set the context menu policy to ActionsContextMenu
        self.myTree.setContextMenuPolicy( Qt.ActionsContextMenu )

        # Define a new action
        self.action_perso = QAction( "MyOwnMenu", self.myTree )
        self.myTree.addAction( self.action_perso )

        # connect this action to a personnal function
        self.connect( self.action_perso, SIGNAL("triggered()"), self.myFunction )

    def myFunction(self):
        print("coucou")

    def objectTree(self, objet, plan, j):
        """ list recursively all child objects of objet to fetch the right widget """

        n = len( objet.children() )
        for i, child in enumerate( objet.children() ):
            #print("\t"*j, end="")
            plan_sup = plan+"."+str(i)
            #print( plan_sup, child )
            if isinstance(child, QTreeView):
                self.listViews.append(child)
            self.objectTree(child, plan_sup, j+1)  


class MainWidget(QWidget):

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

        #creation of main layout 
        mainLayout = QVBoxLayout()

        # creation of a widget inside
        self.monWidget = CustomWidget()
        mainLayout.addWidget( self.monWidget )
        self.setLayout( mainLayout )

        self.show()


app = QApplication(sys.argv)
window = MainWidget()
sys.exit(app.exec_())

0
投票

只是想发布 PyQt5 的原始答案的修改。对我来说主要问题是:

  • 在PyQt5中,显然你需要在

    self.show()
    的构造函数中执行
    CustomWidget(QFileDialog)
    - 否则它的
    self.children()
    仍然是一个空列表,并且原始答案所基于的
    self.findChild(...)
    调用都不起作用。

  • 不幸的是(对我来说),这种方法:

    我也尝试过实现一个

    setContextMenuPolicy(Qt.CustomContextMenu)
    ,并且它部分起作用:我的菜单确实出现了,但在未激活的原始菜单下!

    ...(这是我本来想要的)不再在 PyQt5 中工作:

    • 如果您只执行
      setContextMenuPolicy(Qt.CustomContextMenu)
      而不执行其他任何操作(下例中的
      DO_SIGNALS_CONNECT = False
      ),您只会获得原始上下文菜单,没有任何更改(没有添加新操作)
    • 如果您执行
      setContextMenuPolicy(Qt.CustomContextMenu)
      并添加信号连接(下例中的
      DO_SIGNALS_CONNECT = True
      ),那么您必须添加处理程序方法(下例中的
      generate_context_menu_*
      ),但在这些方法中,对原始上下文菜单的引用是不可用,所以您所能做的就是在那里创建一个新的
      QMenu
      ,然后添加您在那里的操作。

这意味着,在下面的示例中,无论您使用上下文菜单策略

Qt.ActionsContextMenu
还是
Qt.CustomContextMenu
(使用
DO_SIGNALS_CONNECT = True
),您都会获得完全相同的右键单击上下文菜单响应:

Screenshot of example right-click context menu

此外,请注意,在此示例中,使用

Qt.CustomContextMenu
策略还将禁用通过键盘 ENTER 或鼠标双击进行的文件夹导航(因此可能需要在
generate_context_menu_*
处理程序方法中完成更多操作,才能将其实现再次工作)。

这是示例:

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QTreeView, QListView, QFileDialog, QAction, QMenu

class CustomWidget(QFileDialog):
    def __init__(self, parent=None):
        super(CustomWidget,self).__init__(parent)

        print("__init__")
        self.objectTree(self, "", 0) # no .children() here - []

        self.show() # must have self.show, so .children() are instantiated; but without extern call to parent .show() afterwards, this on its own does not show the dialog window!
        self.objectTree(self, "", 0) # works here
        print(f"{len(self.children())=}") # 25

        # fetch the QTreeView in the QFileDialog
        self.myTree = self.findChild(QTreeView, "treeView") # when QFileDialog in Detail view
        self.myList = self.findChild(QListView, "listView") # when QFileDialog in List view

        # Define a new action
        self.action_perso_t = QAction( "MyOwnMenu T", self.myTree )
        self.myTree.addAction( self.action_perso_t ) # only works for Qt.ActionsContextMenu
        self.action_perso_l = QAction( "MyOwnMenu L", self.myList )
        self.myList.addAction( self.action_perso_l ) # only works for Qt.ActionsContextMenu

        # connect this action to a personnal function
        self.action_perso_t.triggered.connect(lambda: self.myFunction("MyOwnMenu T"))
        self.action_perso_l.triggered.connect(lambda: self.myFunction("MyOwnMenu L"))

        # CHOOSE HERE: set the context menu policy to ActionsContextMenu
        ctxMenuPolicyChoice = Qt.ActionsContextMenu # Qt.ActionsContextMenu or Qt.CustomContextMenu

        if ctxMenuPolicyChoice == Qt.ActionsContextMenu:
          # ActionsContextMenu fully replaces the original context menu; see also https://stackoverflow.com/q/43890097
          #
          self.myTree.setContextMenuPolicy( Qt.ActionsContextMenu )
          self.myList.setContextMenuPolicy( Qt.ActionsContextMenu )
        #
        elif ctxMenuPolicyChoice == Qt.CustomContextMenu:
          # for CustomContextMenu policy - cannot append action to existing context menu:
          # also, you cannot use double-click or enter to navigate in folders!!
          #
          self.myTree.setContextMenuPolicy( Qt.CustomContextMenu )
          self.myList.setContextMenuPolicy( Qt.CustomContextMenu )
          # CHOOSE HERE:
          DO_SIGNALS_CONNECT = True # True or False
          if DO_SIGNALS_CONNECT:
            self.myTree.disconnect() # must have, see https://stackoverflow.com/q/79192562#comment139645474_79192562
            self.myTree.customContextMenuRequested.connect(self.generate_context_menu_t)
            self.myList.disconnect() # must have, see https://stackoverflow.com/q/79192562#comment139645474_79192562
            self.myList.customContextMenuRequested.connect(self.generate_context_menu_l)

    def generate_context_menu_t(self, location): # https://stackoverflow.com/q/44666427
        print("generate_context_menu t")
        #menu = self.myTree.createStandardContextMenu() # no .createStandardContextMenu for QTreeView or QFileDialog; in qfiledialog.cpp .showContextMenu is private (method of QFileDialogPrivate), and menu = new QMenu(view) is dynamically created there, with no accessible property; renameAction is a propery of QFileDialogPrivate
        menu = QMenu()
        menu.addAction(self.action_perso_t)
        menu.exec(self.mapToGlobal(location)) # https://stackoverflow.com/q/43820152 -> works

    def generate_context_menu_l(self, location): # https://stackoverflow.com/q/44666427
        print("generate_context_menu l")
        menu = QMenu()
        menu.addAction(self.action_perso_l)
        menu.exec(self.mapToGlobal(location))

    def myFunction(self, label):
        print("coucou {}".format(label))

    def objectTree(self, objet, plan, j):
        """ list recursively all child objects of objet to fetch the right widget """
        n = len( objet.children() )
        print(f"objectTree {n=} {objet.children()=}")
        for i, child in enumerate( objet.children() ):
            print("\t"*j, end="")
            plan_sup = plan+"."+str(i)
            print( plan_sup, '"{}"'.format(objet.objectName()), child )
            #if isinstance(child, QTreeView):
            #    self.listViews.append(child) # AttributeError: 'CustomWidget' object has no attribute 'listViews'
            self.objectTree(child, plan_sup, j+1)


class MainWidget(QWidget):

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

        #creation of main layout
        mainLayout = QVBoxLayout()

        # creation of a widget inside
        self.monWidget = CustomWidget()
        print(f"{self.monWidget.children()=}") # empty if no .show() in CustomWidget() constructor
        mainLayout.addWidget( self.monWidget )
        self.setLayout( mainLayout )

        self.show() # nust have here, even if we now have .show() in CustomWidget() constructor
        print(f"{self.monWidget.children()=}") # fine here (after .show())


app = QApplication(sys.argv)
window = MainWidget()
sys.exit(app.exec_())
© www.soinside.com 2019 - 2024. All rights reserved.