何时以及如何正确销毁 QMenu 上下文菜单?

When and how to properly destroy QMenu context menu?

我允许自定义上下文菜单出现在 table 上。这是生成菜单的方式,使用接受目标小部件和坐标的通用函数:

#include <QMenu>
void MainWindow::makeContextMenu(const QPoint& pos, QWidget* target)
{
    QMenu *menu = new QMenu(this);
    menu->addAction(new QAction("Action 1", menu));
    menu->addAction(new QAction("Action 2", menu));
    menu->addAction(new QAction("Action 3", menu));
    // Notify window about clicking
    QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
    // If this is a scroll area, map coordinates to real app coordinates
    if(QAbstractScrollArea* area = dynamic_cast<QAbstractScrollArea*>(target))
        menu->popup(area->viewport()->mapToGlobal(pos));
    else
        menu->popup(pos);
}

问题是 QMenu* menu 永远不会被销毁并从内存中删除。即使在隐藏之后,它仍然作为 MainWindow 的子项存在。

我该怎么办?我可以将菜单设置为自行删除吗?或者我应该重复使用相同的菜单实例还是将其保存到相同的指针中?

从你的代码来看,似乎 menu 应该在这个事件发生后删除?

// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);

Can I set the menu to delete itself?

是的,您可以这样 object delete itself

// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
QObject::connect(menu, &QMenu::triggered, menu, &QMenu::deleteLater);

如果您担心这些插槽被调用的顺序,请参阅 this


Or should I reuse the same instance of menu or maybe save it into same pointer?

好吧,你可以这样做

//Your constructor
MainWindow::MainWindow(....)
{
    menu = nullptr;
    ....
}

//Make context Menu
void MainWindow::makeContextMenu(const QPoint& pos, QWidget* target)
{
    if(menu)
        delete menu; 
    menu = new QMenu(this);
    ....
}

至于 MainWindow::~MainWindow() 析构函数,它将负责 menu 的清理工作。由于 MainWindow(这是 QObject 派生的 class) automatically deletes 所有 children


最后,您可以简单地将 menu 作为 MainWindow 的成员,并且每当您需要为 menu 执行新操作时,您可以使用 QMenu::clear删除所有现有操作。

//Your constructor
MainWindow::MainWindow(....)
{
    menu = new QMenu(this);
    ....
}

void MainWindow::makeContextMenu(const QPoint& pos, QWidget* target)
{
    menu->clear();
    //QMenu *menu = new QMenu(this);
    menu->addAction(new QAction("Action 1", menu));
    menu->addAction(new QAction("Action 2", menu));
    menu->addAction(new QAction("Action 3", menu));
    // Notify window about clicking
    QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
    // If this is a scroll area, map coordinates to real app coordinates
    if(QAbstractScrollArea* area = dynamic_cast<QAbstractScrollArea*>(target))
        menu->popup(area->viewport()->mapToGlobal(pos));
    else
        menu->popup(pos);
}

隐藏时可以删除QMenu。为此,我设计了事件过滤器 class:

#ifndef DELETEONHIDEFILTER_H
#define DELETEONHIDEFILTER_H

#include <QObject>
#include <QEvent>

class DeleteOnHideFilter : public QObject
{
        Q_OBJECT
    public:
        explicit DeleteOnHideFilter(QObject *parent = 0) : QObject(parent) {}

    protected slots:
        bool eventFilter(QObject *obj, QEvent *event) override {
            if(event->type() == QEvent::Hide) {
                obj->deleteLater();
            }
            return false;
        }
};

#endif // DELETEONHIDEFILTER_H

它也可以用于其他对象。

没必要这么复杂。已经这样了:

menu->setAttribute(Qt::WA_DeleteOnClose);

这样,当 QMenu 关闭时,一旦再次进入事件循环,class 就会被删除。触发动作或刚刚关闭弹出窗口都没有关系。

为了证明我的答案,您可以通过检查菜单创建时间以及是否使用相同地址触发 'deleted' 消息来自行测试:

qDebug() << "created" << (qintptr)menu;
connect(menu, &QMenu::destroyed, 
        this, [menu]() { qDebug() << "deleted" << (qintptr)menu; });