我想在 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_())
我找到了一个解决方案,但这可能不是最好的。它依赖于两个要素:
感谢个人函数
objectTree
(此处显示但未使用)递归地列出了QFileDialog
的所有子对象,我识别了正确的小部件,即QTreeView
(我知道QTreeView是正确的小部件)通过尝试连续隐藏所有 QListView 和 QTreeView 小部件)。因此,我可以通过它的 objectName 来选择它 self.findChild(QTreeView, "treeView"
)将
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_())
只是想发布 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
),您都会获得完全相同的右键单击上下文菜单响应:
此外,请注意,在此示例中,使用
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_())