QMenu.setTearOffEnabled窗口无法正确适配QWidgetAction显示尺寸
问题现象
QMenu中包含带自定义尺寸QWidget的QWidgetAction时,菜单可正常显示;但将菜单撕下成为独立窗口后,窗口仅适配普通文本动作的尺寸,QWidgetAction对应的QWidget几乎完全被隐藏。
复现代码
#!/usr/bin/env python from PySide2 import QtCore, QtWidgets def main(): app = QtWidgets.QApplication() window = QtWidgets.QWidget() layout = QtWidgets.QHBoxLayout() window.setLayout(layout) button = QtWidgets.QPushButton("Push Me") menu = QtWidgets.QMenu() menu.setTearOffEnabled(True) menu.addAction("Ok") qwa = QtWidgets.QWidgetAction(menu) big_button = QtWidgets.QPushButton("Big Button") big_button.setMinimumWidth(400) big_button.setMinimumHeight(400) qwa.setDefaultWidget(big_button) menu.addAction(qwa) button.setMenu(menu) layout.addWidget(button) window.show() app.exec_() main()
截图说明
- 正常显示菜单:菜单内同时展示"Ok"文本动作和400×400的大按钮,布局完全正常。
- 撕下菜单后:独立窗口仅适配"Ok"动作的宽度,大按钮几乎被完全隐藏,仅能看到极小部分。
解决方案
问题根源是菜单被撕下为独立窗口时,Qt未正确计算QWidgetAction的尺寸,可通过以下两种方式解决:
方法一:自定义QWidgetAction并重写sizeHint
子类化QWidgetAction,让其返回对应Widget的尺寸提示,确保独立窗口能正确识别:
#!/usr/bin/env python from PySide2 import QtCore, QtWidgets class CustomWidgetAction(QtWidgets.QWidgetAction): def __init__(self, parent=None): super().__init__(parent) self._widget = None def setDefaultWidget(self, widget): self._widget = widget super().setDefaultWidget(widget) def sizeHint(self): if self._widget: return self._widget.sizeHint() return super().sizeHint() def main(): app = QtWidgets.QApplication() window = QtWidgets.QWidget() layout = QtWidgets.QHBoxLayout() window.setLayout(layout) button = QtWidgets.QPushButton("Push Me") menu = QtWidgets.QMenu() menu.setTearOffEnabled(True) menu.addAction("Ok") qwa = CustomWidgetAction(menu) big_button = QtWidgets.QPushButton("Big Button") big_button.setMinimumWidth(400) big_button.setMinimumHeight(400) qwa.setDefaultWidget(big_button) menu.addAction(qwa) button.setMenu(menu) layout.addWidget(button) window.show() app.exec_() main()
方法二:监听菜单撕下事件调整窗口尺寸
通过tearOffTriggered信号监听菜单撕下动作,手动计算所有动作的最大尺寸并调整独立窗口大小:
#!/usr/bin/env python from PySide2 import QtCore, QtWidgets def adjust_tearoff_window(menu): if menu.isTearOffMenuVisible(): tearoff_window = menu.tearOffMenu() max_width = 0 total_height = 0 # 遍历所有动作计算所需尺寸 for action in menu.actions(): if isinstance(action, QtWidgets.QWidgetAction): widget = action.defaultWidget() if widget: w = widget.minimumWidth() h = widget.minimumHeight() max_width = max(max_width, w) total_height += h else: # 计算文本动作的尺寸(含边距) text_rect = menu.fontMetrics().boundingRect(action.text()) item_width = text_rect.width() + 40 # 左右边距 item_height = text_rect.height() + 10 # 上下边距 max_width = max(max_width, item_width) total_height += item_height # 调整窗口大小,加上窗口框架的高度 frame_height = tearoff_window.frameGeometry().height() - tearoff_window.geometry().height() tearoff_window.resize(max_width, total_height + frame_height) def main(): app = QtWidgets.QApplication() window = QtWidgets.QWidget() layout = QtWidgets.QHBoxLayout() window.setLayout(layout) button = QtWidgets.QPushButton("Push Me") menu = QtWidgets.QMenu() menu.setTearOffEnabled(True) menu.addAction("Ok") qwa = QtWidgets.QWidgetAction(menu) big_button = QtWidgets.QPushButton("Big Button") big_button.setMinimumWidth(400) big_button.setMinimumHeight(400) qwa.setDefaultWidget(big_button) menu.addAction(qwa) # 监听菜单撕下事件 menu.tearOffTriggered.connect(lambda: adjust_tearoff_window(menu)) button.setMenu(menu) layout.addWidget(button) window.show() app.exec_() main()
内容的提问来源于stack exchange,提问作者user1885388
相关产品推荐
相关产品推荐

