QMainWindow上下文菜单内存泄露(QTBUG)

12 篇文章 9 订阅

起源

CSDN论坛有网友抱怨:

创建Qt工程,基于QMainwindow,什么也不做,程序会自带一个上下文菜单。
不断点击鼠标右键,菜单将反复出现,此时我用任务管理器查看其内存变化,发现每次不断增加,请问大家这是Qt的内存泄漏吗???我用MFC,CB均没有发现类此错误。

在Qt4.7.0 和 4.7.3下可以重现该问题,在Qt4.6.3下不存在该问题。可以确定是Qt的一个bug。

  • 惭愧 :还没学会怎么提交bug、提交patch、然后找人来审核。先记录下来,晚些时候学习好好学习一下这些流程然后再提交

问题重现

在工具栏或停靠窗口中点击右键(弹出上下文菜单),多点击几次,然后点击按钮。观察控制台输出,可以看到很多个 QMenu 对象。

#include <QtGui>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);

private slots:
    void onButtonClicked();
};

MainWindow::MainWindow(QWidget *parent)
{
    addToolBar("ToolBar");
    addDockWidget(Qt::LeftDockWidgetArea, new QDockWidget("DockWidget"));

    QPushButton * btn = new QPushButton("dump object tree");
    setCentralWidget(btn);

    connect(btn, SIGNAL(clicked()), SLOT(onButtonClicked()));
}

void MainWindow::onButtonClicked()
{
    dumpObjectTree();
}

#include "main.moc"
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

原因

既然是QMainWindow的上下文菜单问题,直接看 contextMenuEvent 事件处理函数吧。

void QMainWindow::contextMenuEvent(QContextMenuEvent *event)
{
    event->ignore();
...
    QMenu *popup = createPopupMenu();
    if (popup) {
        if (!popup->isEmpty()) {
            popup->setAttribute(Qt::WA_DeleteOnClose);
            popup->popup(event->globalPos());
            event->accept();
        } else {
            delete popup;
        }
    }
}

看仔细喽,这儿设置了 Qt::WA_DeleteOnClose 属性。

  • 有什么用?设置该属性后,当我们调用该对象的 close() 成员时,隐藏(hide)窗口同时会删除(delete)该对象
  • 有什么问题?问题出在,实际上隐藏菜单时没有 调用菜单的close(),而是 调用的hide()的成员。

调用hide()而不是close(),是的该属性不能发挥任何作用,进而导致内存泄露(Qt 之 show,hide,setVisible,setHidden,close 等小结 )。

为了对比,我们看看Qt4.6.3的源码部分:

void QMainWindow::contextMenuEvent(QContextMenuEvent *event)
{
    event->ignore();
...
    QMenu *popup = createPopupMenu();
    if (popup && !popup->isEmpty()) {
        popup->exec(event->globalPos());
        event->accept();
    }
    delete popup;
}

而这个,也就是我们的比较理想的答案了。

进一步学习

前面说了,菜单隐藏时调用的是hide() 成员,而不是close() 成员。有神马依据??

想想?如何让菜单隐藏

  • 鼠标:点击菜单外区域
  • 键盘:按下Esc键等

这样就比较明朗了,对吧,直接看这两个事件处理函数

  • 键盘的按键事件(调用了hideMenu)
void QMenu::keyPressEvent(QKeyEvent *e)
{
    Q_D(QMenu);
    d->updateActionRects();
    int key = e->key();
...
    bool key_consumed = false;
    switch(key) {
    case Qt::Key_Escape:
        key_consumed = true;
        {
            QPointer<QWidget> caused = d->causedPopup.widget;
            d->hideMenu(this); // hide after getting causedPopup
            if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
                mb->d_func()->setCurrentAction(d->menuAction);
                mb->d_func()->setKeyboardMode(true);
            }
        }
        break;
  • 鼠标在菜单区域外按键,调用了hideUpToMenuBar(进而调用hideMenu)
void QMenu::mousePressEvent(QMouseEvent *e)
{
    Q_D(QMenu);
...
    if (!rect().contains(e->pos())) {
         if (d->noReplayFor
             && QRect(d->noReplayFor->mapToGlobal(QPoint()), d->noReplayFor->size()).contains(e->globalPos()))
             setAttribute(Qt::WA_NoMouseReplay);
         if (d->eventLoop) // synchronous operation
             d->syncAction = 0;
        d->hideUpToMenuBar();
        return;
    }
...
}
  • 前面都调用了hideMenu,从名字也能猜猜它想干什么:
void QMenuPrivate::hideMenu(QMenu *menu, bool justRegister)
{
...
        menu->hide();
}

 

PyQt6中的QListWidget是一个用于显示列表项目的控件,它支持用户通过鼠标右键点击(即上下文菜单)来快速访问常见的操作。在创建QListWidget时,你可以自定义其上下文菜单,以便提供一些特定的功能,比如添加、删除项目、复制、移动等。 下面是一个简单的例子展示如何在PyQt6中设置QListWidget的右键上下文菜单: ```python from PyQt6.QtWidgets import QApplication, QMainWindow, QListWidget, QAction, QMenu class MyWindow(QMainWindow): def __init__(self): super().__init__() self.list_widget = QListWidget() self.setCentralWidget(self.list_widget) # 创建上下文菜单 context_menu = QMenu() # 添加菜单项 add_item_action = QAction('添加', self) remove_item_action = QAction('删除', self) copy_item_action = QAction('复制', self) move_item_action = QAction('移动', self) # 连接信号槽,当动作被触发时执行相应的操作 add_item_action.triggered.connect(self.add_item) remove_item_action.triggered.connect(self.remove_item) copy_item_action.triggered.connect(self.copy_item) move_item_action.triggered.connect(self.move_item) # 将菜单项添加到上下文菜单 context_menu.addAction(add_item_action) context_menu.addAction(remove_item_action) context_menu.addAction(copy_item_action) context_menu.addAction(move_item_action) # 设置右键点击事件处理器 self.list_widget.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self.list_widget.customContextMenuRequested.connect(lambda pos: context_menu.exec(pos)) def add_item(self): print("添加项目") def remove_item(self): print("删除选中的项目") def copy_item(self): print("复制选中的项目") def move_item(self): print("移动选中的项目") if __name__ == '__main__': app = QApplication([]) window = MyWindow() window.show() app.exec_() ``` 在这个例子中,当你在QListWidget上右键点击时,会弹出一个包含预定义操作的菜单。用户可以根据需要选择执行相应操作。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值