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

本文详细分析了Qt中一个特定的内存泄漏问题,即在QMainWindow的上下文菜单中反复弹出QMenu导致的内存占用持续增加现象。通过对比Qt不同版本的源代码,找出问题所在,并提供了解决方案。

起源

优快云论坛有网友抱怨:

创建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();
}

 

<think>好的,我现在需要在QtQMainWindow菜单栏中添加一个QPushButton。首先,我得回忆一下Qt菜单栏结构。QMainWindow有默认的菜单栏,可以通过menuBar()方法获取。通常菜单栏里添加的是QMenu,每个QMenu里包含QAction。但用户想添加的是QPushButton,这不太常见,因为通常菜单项用的是QAction。不过可能有方法实现。 我需要查一下如何在菜单栏中插入自定义控件。根据引用中的内容,比如引用[3]提到在工具栏中添加QPushButton是使用addWidget方法。那菜单栏是否也有类似的方法呢?比如QMenu是否有addWidget的方法?或者是否有其他方式? 假设QMenu允许添加自定义控件,那么步骤可能是这样的:先获取菜单栏,然后找到要添加的QMenu,或者创建一个新的QMenu,然后使用QMenu的addWidget方法添加QPushButton。或者可能需要将QPushButton包装成QWidgetAction,因为QAction通常是用来表示菜单项的,而QWidgetAction允许嵌入自定义的控件。 根据Qt的文档,QMenu的addAction方法可以接受QWidgetAction,这样可以将任何QWidget作为菜单项。所以可能的步骤是:创建一个QWidgetAction,将QPushButton设置到该action中,然后将这个action添加到菜单里。 例如,代码可能如下: QMenu *menu = menuBar()->addMenu("菜单"); QPushButton *button = new QPushButton("按钮"); QWidgetAction *action = new QWidgetAction(this); action->setDefaultWidget(button); menu->addAction(action); 这样就能在菜单中添加一个按钮了。需要测试一下这样的代码是否正确,同时要注意按钮的点击事件如何处理。可能需要连接按钮的clicked信号到某个槽函数。 另外,引用[3]中的例子提到在工具栏中使用addWidget来添加按钮,但菜单栏的结构不同。可能需要使用QWidgetAction来包装按钮。这样用户点击菜单项时,按钮就会出现,但可能不会像普通按钮那样显示,需要调整样式。 或者,是否有其他方法?比如在菜单栏的某个位置直接添加QPushButton,但这可能破坏菜单栏的结构,因为菜单栏通常是水平排列的QMenu对象。所以更合理的方式是将按钮作为某个菜单中的一个条目,使用QWidgetAction。 所以最终的步骤应该是:创建QWidgetAction,将按钮添加进去,然后将该action添加到菜单中。同时,要注意按钮的父对象和内存管理,避免内存泄漏。 示例代码可能需要包含头文件如QWidgetAction,并且在菜单中添加这个action。用户的问题是关于如何在QMainWindow菜单栏中添加QPushButton,因此示例代码应该展示如何通过代码实现这一点,而不是使用UI文件。 结合引用中的代码,例如引用[1]中的头文件结构,应该创建一个继承自QMainWindow的类,并在构造函数中设置菜单栏和按钮。例如在myWidget的构造函数中: myWidget::myWidget(QWidget *parent) : QMainWindow(parent) { QMenu *fileMenu = menuBar()->addMenu("文件"); QPushButton *button = new QPushButton("点击我", this); QWidgetAction *buttonAction = new QWidgetAction(this); buttonAction->setDefaultWidget(button); fileMenu->addAction(buttonAction); connect(button, &QPushButton::clicked, this, &myWidget::newFile); //假设newFile是槽函数 } 这样应该可以实现将按钮添加到菜单中。用户可能需要调整按钮的大小或样式,但基本功能应该如此。需要确保QWidgetAction正确管理按钮的生命周期,设置父对象以避免内存泄漏。 总结:使用QWidgetAction将QPushButton包装成菜单项,添加到QMenu中,然后将QMenu添加到菜单栏。这样就能在菜单栏的某个菜单里显示按钮了。</think>在Qt中向QMainWindow菜单栏添加QPushButton需要借助QWidgetAction实现,以下是具体步骤和示例代码: ### 实现步骤 1. **获取主窗口菜单栏** 通过`menuBar()`方法获取QMainWindow的默认菜单栏 2. **创建自定义菜单项** 使用QWidgetAction包装QPushButton控件 3. **添加动作到菜单** 将QWidgetAction添加到目标菜单中 4. **信号连接** 绑定按钮的点击事件到槽函数 ### 示例代码 ```cpp // 头文件 mywidget.h #include <QMainWindow> #include <QMenuBar> #include <QPushButton> #include <QWidgetAction> class MyWidget : public QMainWindow { Q_OBJECT public: explicit MyWidget(QWidget *parent = nullptr); private slots: void handleButtonClick(); }; // 实现文件 mywidget.cpp MyWidget::MyWidget(QWidget *parent) : QMainWindow(parent) { // 创建菜单 QMenu *customMenu = menuBar()->addMenu("自定义菜单"); // 创建按钮和包装动作 QPushButton *menuButton = new QPushButton("菜单按钮"); QWidgetAction *buttonAction = new QWidgetAction(this); buttonAction->setDefaultWidget(menuButton); // 添加动作到菜单 customMenu->addAction(buttonAction); // 连接信号槽 connect(menuButton, &QPushButton::clicked, this, &MyWidget::handleButtonClick); } void MyWidget::handleButtonClick() { QMessageBox::information(this, "提示", "按钮被点击"); } ``` ### 关键说明 1. **QWidgetAction的核心作用** 作为容器将任意QWidget派生类嵌入到菜单系统中[^3] 2. **内存管理** 所有控件都应指定父对象(this指针),确保自动释放 3. **样式适配** 按钮在菜单中会继承菜单样式,可通过`setStyleSheet()`自定义外观
评论 16
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值