防止将工具栏添加到上下文菜单
Prevent toolbar from being added to context menu
我正在创建一个自定义工具栏,它会在初始化时自动将自己添加到父级(如果存在)。
我希望此自定义工具栏不会出现在上下文菜单中。但是,尽管使用 setContextMenuPolicy
:
,但还是会出现与工具栏相关的内容(我不知道是什么)
我不知道上下文菜单项是什么。我的理解是,任何小部件都可以通过其 contextMenuPolicy 的方式添加到上下文菜单中。但是 CustomToolBar 中没有任何其他小部件。
解决方法是完全禁用 MainWindow 上的上下文菜单并创建一个切换可见性的菜单项(例如视图)。
import sys
import time
from PyQt5 import QtCore, QtWidgets
class CustomToolBar(QtWidgets.QToolBar):
def __init__(self, parent=None):
super().__init__(parent=parent)
if parent:
self.setParent(parent)
self.parent().addToolBar(QtCore.Qt.BottomToolBarArea, self)
self.setObjectName('Custom ToolBar')
self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.counter = 0
self.resize(250, 75)
self.init_widgets()
self.init_layout()
def init_widgets(self):
self.exit_action = QtWidgets.QAction('&Exit', self)
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.setToolTip('Exit application')
self.exit_action.triggered.connect(self.close)
self.menu = self.menuBar()
self.menu_file = self.menu.addMenu('&File')
self.menu_file.addAction(self.exit_action)
# setting parent to self embeds the custom toolbar in the main window
self.status = CustomToolBar(self)
def init_layout(self):
layout = QtWidgets.QVBoxLayout()
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
注意:如果您单击菜单栏或下方的工具栏,图像将无法正确显示。
一些小部件不会覆盖 contextMenuEvent 方法,因此使用 self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
将不起作用,QToolBar(以及 QMenuBar 也是如此)就是这种情况。在这些情况下,必须覆盖并拒绝事件方法。
假设您只是希望它不出现在 QToolBar 中,那么只需使用:
class CustomToolBar(QtWidgets.QToolBar):
def __init__(self, parent=None):
super().__init__(parent=parent)
if isinstance(parent, QtWidgets.QMainWindow):
self.setParent(parent)
parent.addToolBar(QtCore.Qt.BottomToolBarArea, self)
self.setObjectName('Custom ToolBar')
def event(self, event):
if event.type() == QtCore.QEvent.ContextMenu:
return True
return super().event(event)
如果您还想对 MenuBar 执行相同的操作,则必须实现类似的逻辑:
class StatusBar(QtWidgets.QMenuBar):
def event(self, event):
if event.type() == QtCore.QEvent.ContextMenu:
return True
return super().event(event)
最后,建议使用表明它是什么类型的小部件的名称,所以我将菜单更改为 menu_bar:
def init_widgets(self):
self.exit_action = QtWidgets.QAction('&Exit', self)
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.setToolTip('Exit application')
self.exit_action.triggered.connect(self.close)
self.menu_bar = StatusBar()
self.setMenuBar(self.menu_bar)
self.menu_file = self.menu_bar.addMenu('&File')
self.menu_file.addAction(self.exit_action)
# setting parent to self embeds the custom toolbar in the main window
self.tool_bar = CustomToolBar(self)
更新:
似乎OP的objective是QMainWindow的QMenu中实现的与QToolBar关联的QAction没有显示,所以为此最好覆盖createPopupMenu方法并删除QAction为此,没有必要实现自定义 QToolBar。
import sys
import uuid
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.init_widgets()
self.init_layout()
def init_widgets(self):
self.exit_action = QtWidgets.QAction("&Exit", self)
self.exit_action.setShortcut("Ctrl+Q")
self.exit_action.setToolTip("Exit application")
self.exit_action.triggered.connect(self.close)
self.menu_bar = self.menuBar()
self.menu_file = self.menu_bar.addMenu("&File")
self.menu_file.addAction(self.exit_action)
self.custom_toolbar = QtWidgets.QToolBar()
self.addToolBar(QtCore.Qt.BottomToolBarArea, self.custom_toolbar)
self.custom_toolbar.setProperty("hide_action_toolbar", True)
self.dock = QtWidgets.QDockWidget("Dock")
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.dock)
def init_layout(self):
layout = QtWidgets.QVBoxLayout()
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
def createPopupMenu(self):
titles = []
for toolbar in self.findChildren(QtWidgets.QToolBar):
if toolbar.property("hide_action_toolbar") is None:
continue
if toolbar.property("hide_action_toolbar"):
toolbar.setProperty("last_window_title", toolbar.windowTitle())
toolbar.setWindowTitle(uuid.uuid4().hex)
titles.append(toolbar.windowTitle())
else:
toolbar.setWindowTitle(toolbar.property("last_window_title") or "")
menu = super().createPopupMenu()
for action in menu.actions():
if action.text() in titles:
menu.removeAction(action)
return menu
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.resize(640, 480)
main_window.show()
sys.exit(app.exec_())
上下文菜单中显示的项目实际上是工具栏。菜单项来自相应的操作文本。这反过来又来自工具栏 window 标题。由于默认为空字符串,因此在菜单中显示为空白。
将行 self.setObjectName('Custom ToolBar')
更改为 self.setWindowTitle('Custom ToolBar')
将按预期显示工具栏名称。
import sys
import time
from PyQt5 import QtCore, QtWidgets
class CustomToolBar(QtWidgets.QToolBar):
def __init__(self, parent=None, title='CustomToolBar'):
super().__init__(parent=parent)
# Sets the action text in the context menu
self.setWindowTitle(title)
if parent:
self.setParent(parent)
self.parent().addToolBar(QtCore.Qt.BottomToolBarArea, self)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.counter = 0
self.resize(250, 75)
self.init_widgets()
self.init_layout()
def init_widgets(self):
self.exit_action = QtWidgets.QAction('&Exit', self)
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.setToolTip('Exit application')
self.exit_action.triggered.connect(self.close)
# self.menu_bar = CustomMenuBar()
# self.setMenuBar(self.menu_bar)
self.menu_bar = self.menuBar()
self.menu_file = self.menu_bar.addMenu('&File')
self.menu_file.addAction(self.exit_action)
# self embeds the toolbar in the main window
self.status = CustomToolBar(self)
def init_layout(self):
layout = QtWidgets.QVBoxLayout()
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
至于CustomToolBar没有出现在上下文菜单中,那就比较复杂了。一种选择是通过 parent(即 MainWindow)使用自定义上下文菜单。 This post 解释如何做到这一点。
然而,想要让 CustomToolBar 自动删除自身对用户来说更好,但更具挑战性。上下文菜单实际上是由 QMainWindow 通过 createContextPopupMenu
方法动态创建的。 CustomToolBar 必须重写此方法,以便将其自身从结果列表中删除。理想情况下,这应该以保留上下文菜单行为的方式完成。
createContextPopupMenu
方法由 MainWindow 的 contextMenuEvent
handler 调用。因此,利用 Python 将函数视为第一个 class 公民这一事实,我们可以将 createPopupMenu
方法替换为从中删除 CustomToolBar 的版本。下面的示例添加了一个 QDockWidget 以演示 CustomToolBar 已自行删除。
import sys
import time
from PyQt5 import QtCore, QtWidgets
class CustomToolBar(QtWidgets.QToolBar):
def __init__(self, parent=None, title='CustomToolBar'):
super().__init__(parent=parent)
# Sets the action text in the context menu
self.setWindowTitle(title)
if parent:
self.setParent(parent)
self.parent().addToolBar(QtCore.Qt.BottomToolBarArea, self)
############################################################
# Remove the custom toolbar from the parent's context menu #
############################################################
original_popup = self.parent().createPopupMenu
original_handler = self.parent().contextMenuEvent
# Create custom contextMenuEvent which repurposes the
# original createPopupMenu method through use of closures
def customContextMenuEvent(event):
def custom_popup():
popup = original_popup()
# Because this is being handled by CustomToolBar,
# which adds itself to the MainWindow, there is
# guaranteed to be at least one item
for action in popup.actions():
if action.text() == self.windowTitle():
popup.removeAction(action)
return popup
# call the original handler with its call to
# createPopupMenu sneakily replaced with our own
self.parent().createPopupMenu = custom_popup
original_handler(event)
# Replace the MainWindow's contextMenuEvent with our own
self.parent().contextMenuEvent = customContextMenuEvent
# Prevents right click on CustomToolBar from showing context menu
self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.counter = 0
self.resize(250, 75)
self.init_widgets()
self.init_layout()
def init_widgets(self):
self.exit_action = QtWidgets.QAction('&Exit', self)
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.setToolTip('Exit application')
self.exit_action.triggered.connect(self.close)
self.menu_bar = self.menuBar()
self.menu_file = self.menu_bar.addMenu('&File')
self.menu_file.addAction(self.exit_action)
# self embeds the status toolbar in the main window
self.status = CustomToolBar(self)
self.dock = QtWidgets.QDockWidget('Dock')
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.dock)
def init_layout(self):
layout = QtWidgets.QVBoxLayout()
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
我正在创建一个自定义工具栏,它会在初始化时自动将自己添加到父级(如果存在)。
我希望此自定义工具栏不会出现在上下文菜单中。但是,尽管使用 setContextMenuPolicy
:
我不知道上下文菜单项是什么。我的理解是,任何小部件都可以通过其 contextMenuPolicy 的方式添加到上下文菜单中。但是 CustomToolBar 中没有任何其他小部件。
解决方法是完全禁用 MainWindow 上的上下文菜单并创建一个切换可见性的菜单项(例如视图)。
import sys
import time
from PyQt5 import QtCore, QtWidgets
class CustomToolBar(QtWidgets.QToolBar):
def __init__(self, parent=None):
super().__init__(parent=parent)
if parent:
self.setParent(parent)
self.parent().addToolBar(QtCore.Qt.BottomToolBarArea, self)
self.setObjectName('Custom ToolBar')
self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.counter = 0
self.resize(250, 75)
self.init_widgets()
self.init_layout()
def init_widgets(self):
self.exit_action = QtWidgets.QAction('&Exit', self)
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.setToolTip('Exit application')
self.exit_action.triggered.connect(self.close)
self.menu = self.menuBar()
self.menu_file = self.menu.addMenu('&File')
self.menu_file.addAction(self.exit_action)
# setting parent to self embeds the custom toolbar in the main window
self.status = CustomToolBar(self)
def init_layout(self):
layout = QtWidgets.QVBoxLayout()
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
注意:如果您单击菜单栏或下方的工具栏,图像将无法正确显示。
一些小部件不会覆盖 contextMenuEvent 方法,因此使用 self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
将不起作用,QToolBar(以及 QMenuBar 也是如此)就是这种情况。在这些情况下,必须覆盖并拒绝事件方法。
假设您只是希望它不出现在 QToolBar 中,那么只需使用:
class CustomToolBar(QtWidgets.QToolBar):
def __init__(self, parent=None):
super().__init__(parent=parent)
if isinstance(parent, QtWidgets.QMainWindow):
self.setParent(parent)
parent.addToolBar(QtCore.Qt.BottomToolBarArea, self)
self.setObjectName('Custom ToolBar')
def event(self, event):
if event.type() == QtCore.QEvent.ContextMenu:
return True
return super().event(event)
如果您还想对 MenuBar 执行相同的操作,则必须实现类似的逻辑:
class StatusBar(QtWidgets.QMenuBar):
def event(self, event):
if event.type() == QtCore.QEvent.ContextMenu:
return True
return super().event(event)
最后,建议使用表明它是什么类型的小部件的名称,所以我将菜单更改为 menu_bar:
def init_widgets(self):
self.exit_action = QtWidgets.QAction('&Exit', self)
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.setToolTip('Exit application')
self.exit_action.triggered.connect(self.close)
self.menu_bar = StatusBar()
self.setMenuBar(self.menu_bar)
self.menu_file = self.menu_bar.addMenu('&File')
self.menu_file.addAction(self.exit_action)
# setting parent to self embeds the custom toolbar in the main window
self.tool_bar = CustomToolBar(self)
更新:
似乎OP的objective是QMainWindow的QMenu中实现的与QToolBar关联的QAction没有显示,所以为此最好覆盖createPopupMenu方法并删除QAction为此,没有必要实现自定义 QToolBar。
import sys
import uuid
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.init_widgets()
self.init_layout()
def init_widgets(self):
self.exit_action = QtWidgets.QAction("&Exit", self)
self.exit_action.setShortcut("Ctrl+Q")
self.exit_action.setToolTip("Exit application")
self.exit_action.triggered.connect(self.close)
self.menu_bar = self.menuBar()
self.menu_file = self.menu_bar.addMenu("&File")
self.menu_file.addAction(self.exit_action)
self.custom_toolbar = QtWidgets.QToolBar()
self.addToolBar(QtCore.Qt.BottomToolBarArea, self.custom_toolbar)
self.custom_toolbar.setProperty("hide_action_toolbar", True)
self.dock = QtWidgets.QDockWidget("Dock")
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.dock)
def init_layout(self):
layout = QtWidgets.QVBoxLayout()
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
def createPopupMenu(self):
titles = []
for toolbar in self.findChildren(QtWidgets.QToolBar):
if toolbar.property("hide_action_toolbar") is None:
continue
if toolbar.property("hide_action_toolbar"):
toolbar.setProperty("last_window_title", toolbar.windowTitle())
toolbar.setWindowTitle(uuid.uuid4().hex)
titles.append(toolbar.windowTitle())
else:
toolbar.setWindowTitle(toolbar.property("last_window_title") or "")
menu = super().createPopupMenu()
for action in menu.actions():
if action.text() in titles:
menu.removeAction(action)
return menu
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.resize(640, 480)
main_window.show()
sys.exit(app.exec_())
上下文菜单中显示的项目实际上是工具栏。菜单项来自相应的操作文本。这反过来又来自工具栏 window 标题。由于默认为空字符串,因此在菜单中显示为空白。
将行 self.setObjectName('Custom ToolBar')
更改为 self.setWindowTitle('Custom ToolBar')
将按预期显示工具栏名称。
import sys
import time
from PyQt5 import QtCore, QtWidgets
class CustomToolBar(QtWidgets.QToolBar):
def __init__(self, parent=None, title='CustomToolBar'):
super().__init__(parent=parent)
# Sets the action text in the context menu
self.setWindowTitle(title)
if parent:
self.setParent(parent)
self.parent().addToolBar(QtCore.Qt.BottomToolBarArea, self)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.counter = 0
self.resize(250, 75)
self.init_widgets()
self.init_layout()
def init_widgets(self):
self.exit_action = QtWidgets.QAction('&Exit', self)
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.setToolTip('Exit application')
self.exit_action.triggered.connect(self.close)
# self.menu_bar = CustomMenuBar()
# self.setMenuBar(self.menu_bar)
self.menu_bar = self.menuBar()
self.menu_file = self.menu_bar.addMenu('&File')
self.menu_file.addAction(self.exit_action)
# self embeds the toolbar in the main window
self.status = CustomToolBar(self)
def init_layout(self):
layout = QtWidgets.QVBoxLayout()
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
至于CustomToolBar没有出现在上下文菜单中,那就比较复杂了。一种选择是通过 parent(即 MainWindow)使用自定义上下文菜单。 This post 解释如何做到这一点。
然而,想要让 CustomToolBar 自动删除自身对用户来说更好,但更具挑战性。上下文菜单实际上是由 QMainWindow 通过 createContextPopupMenu
方法动态创建的。 CustomToolBar 必须重写此方法,以便将其自身从结果列表中删除。理想情况下,这应该以保留上下文菜单行为的方式完成。
createContextPopupMenu
方法由 MainWindow 的 contextMenuEvent
handler 调用。因此,利用 Python 将函数视为第一个 class 公民这一事实,我们可以将 createPopupMenu
方法替换为从中删除 CustomToolBar 的版本。下面的示例添加了一个 QDockWidget 以演示 CustomToolBar 已自行删除。
import sys
import time
from PyQt5 import QtCore, QtWidgets
class CustomToolBar(QtWidgets.QToolBar):
def __init__(self, parent=None, title='CustomToolBar'):
super().__init__(parent=parent)
# Sets the action text in the context menu
self.setWindowTitle(title)
if parent:
self.setParent(parent)
self.parent().addToolBar(QtCore.Qt.BottomToolBarArea, self)
############################################################
# Remove the custom toolbar from the parent's context menu #
############################################################
original_popup = self.parent().createPopupMenu
original_handler = self.parent().contextMenuEvent
# Create custom contextMenuEvent which repurposes the
# original createPopupMenu method through use of closures
def customContextMenuEvent(event):
def custom_popup():
popup = original_popup()
# Because this is being handled by CustomToolBar,
# which adds itself to the MainWindow, there is
# guaranteed to be at least one item
for action in popup.actions():
if action.text() == self.windowTitle():
popup.removeAction(action)
return popup
# call the original handler with its call to
# createPopupMenu sneakily replaced with our own
self.parent().createPopupMenu = custom_popup
original_handler(event)
# Replace the MainWindow's contextMenuEvent with our own
self.parent().contextMenuEvent = customContextMenuEvent
# Prevents right click on CustomToolBar from showing context menu
self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.counter = 0
self.resize(250, 75)
self.init_widgets()
self.init_layout()
def init_widgets(self):
self.exit_action = QtWidgets.QAction('&Exit', self)
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.setToolTip('Exit application')
self.exit_action.triggered.connect(self.close)
self.menu_bar = self.menuBar()
self.menu_file = self.menu_bar.addMenu('&File')
self.menu_file.addAction(self.exit_action)
# self embeds the status toolbar in the main window
self.status = CustomToolBar(self)
self.dock = QtWidgets.QDockWidget('Dock')
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.dock)
def init_layout(self):
layout = QtWidgets.QVBoxLayout()
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())