PyQt5 QToolButton替换QMenu点击菜单项出现空指针及对象删除错误
错误原因
- 你在菜单项触发的槽函数中直接重建并替换QToolButton绑定的菜单,此时被点击的原菜单仍处于Qt的事件处理流程中,你直接销毁原菜单对象,导致Qt访问已经被删除的QObject对象,触发
QObject: shared QObject was deleted directly报错。 - 首次调用
make_qtoolbutton_menu方法时,self.mute_deck_1_action等Action对象还未初始化,执行disconnect操作时Qt底层收到空指针参数,触发QObject::disconnect: Unexpected nullptr parameter报错,虽然Python层的异常被try-except捕获,但Qt底层的警告依然会输出。 - 每次重建菜单时你直接将新的QMenu赋值给
self.mute_toolbutton_menu,原菜单对象的Python引用计数归零被GC回收,同时Qt也在管理该QObject的生命周期,出现双重释放的风险,进一步加剧崩溃概率。
解决方案
最优方案(推荐)
你的需求仅为更新菜单项文本,完全不需要每次重建整个菜单和Action对象,仅需初始化时一次创建好菜单、绑定信号槽,状态变更时仅更新Action文本即可,彻底规避对象生命周期问题。修改后可正常运行的main.py代码如下:
from PyQt5 import QtCore, QtGui, QtWidgets from ui import Ui_Dialog import sys class Main_Program: def __init__(self): app = QtWidgets.QApplication(sys.argv) self.Dialog = QtWidgets.QDialog() self.ui = Ui_Dialog() self.ui.setupUi(self.Dialog) self.Dialog.show() self.deck_1 = { "mute":False } self.deck_2 = { "mute":False } self.music_clip_deck = { "mute":False } # 初始化仅执行1次:创建菜单、Action、绑定信号槽 self.init_menu() # 初始化更新菜单项文本 self.update_menu_text() sys.exit(app.exec_()) def init_menu(self): self.mute_toolbutton_menu = QtWidgets.QMenu() # 创建deck1操作项 self.mute_deck_1_action = QtWidgets.QAction(self.Dialog) self.mute_deck_1_action.triggered.connect(self.mute_or_unmute_deck_1) self.mute_toolbutton_menu.addAction(self.mute_deck_1_action) # 创建deck2操作项 self.mute_deck_2_action = QtWidgets.QAction(self.Dialog) self.mute_deck_2_action.triggered.connect(self.mute_or_unmute_deck_2) self.mute_toolbutton_menu.addAction(self.mute_deck_2_action) # 创建music clip deck操作项 self.mute_music_clip_deck_action = QtWidgets.QAction(self.Dialog) self.mute_music_clip_deck_action.triggered.connect(self.mute_or_unmute_music_clip_deck) self.mute_toolbutton_menu.addAction(self.mute_music_clip_deck_action) # 绑定菜单到工具按钮 self.ui.general_deck_mute_toolbutton.setMenu(self.mute_toolbutton_menu) def update_menu_text(self): # 仅更新菜单项文本,无对象销毁创建操作 if not self.deck_1["mute"]: self.mute_deck_1_action.setText("Απενεργοποίηση σίγασης deck 1") else: self.mute_deck_1_action.setText("Ενεργοποίηση σίγασης στο deck 1") if not self.deck_2["mute"]: self.mute_deck_2_action.setText("Απενεργοποίηση σίγασης deck 2") else: self.mute_deck_2_action.setText("Ενεργοποίηση σίγασης στο deck 2") if not self.music_clip_deck["mute"]: self.mute_music_clip_deck_action.setText("Απενεργοποίηση σίγασης music clip deck") else: self.mute_music_clip_deck_action.setText("Ενεργοποίηση σίγασης στο music clip deck") def mute_or_unmute_deck_1(self): self.deck_1["mute"] = not self.deck_1["mute"] self.update_menu_text() def mute_or_unmute_deck_2(self): self.deck_2["mute"] = not self.deck_2["mute"] self.update_menu_text() def mute_or_unmute_music_clip_deck(self): self.music_clip_deck["mute"] = not self.music_clip_deck["mute"] self.update_menu_text() if __name__ == "__main__": program = Main_Program()
备用方案(仅适用于必须动态增减菜单项的场景)
如果你的业务需要动态调整菜单项数量必须重建菜单,可按以下规则修改避免报错:
- 重建菜单前先调用
self.mute_toolbutton_menu.deleteLater(),让Qt在事件循环空闲时安全销毁旧菜单,避免正在处理事件时直接删除对象 - 调用disconnect前先判断对应Action是否已存在,避免空指针断开操作
- 不要在菜单项的触发槽函数中直接执行重建操作,可使用
QtCore.QTimer.singleShot(0, self.make_qtoolbutton_menu)将重建操作延迟到当前事件处理完成后执行
内容的提问来源于stack exchange,提问作者Chris P
相关产品推荐
相关产品推荐

