Qt:如何在Mac OS X上更改应用程序的QMenuBar内容

3

我的应用程序使用QTabWidget来实现多个“页面”,顶部菜单栏根据用户所在的页面而变化。

我的问题是,尝试重新创建菜单栏的内容会导致严重的显示问题。在除了Mac OS X以外的所有平台上,第一种和第三种样式都按预期工作(没有测试第二种,但我宁愿不使用那种样式)。

第一个菜单是我在应用程序中创建大多数菜单的方式,它们接收到正确的标题,但是一旦重新创建菜单,它们就会消失。

第二个菜单在初始填充和重新填充菜单栏时都会出现,但在两种情况下都有标签“未命名”。第二个菜单的样式是在尝试解决这个问题时创建的,所以这是我能让菜单保持存在的唯一方法。

第三个动态菜单从未出现过。我使用这种样式来动态填充即将显示的菜单。

我已经尝试删除QMenuBar并使用以下代码重新创建:

m_menuBar = new QMenuBar(0);

相比于m_menuBar->clear(),使用它具有相同的行为。

我没有足够的声望来内联发布图片,因此我将包含imgur链接:

启动行为http://i.imgur.com/ZEvvGKl.png

发布按钮点击后的行为http://i.imgur.com/NzRmcYg.png

我已经创建了一个最小化的示例,在Mac OS X 10.9.4和Qt 5.3上重现了这种行为。

mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    m_menuBar = new QMenuBar(0);
    m_dynamicMenu = new QMenu("Dynamic");
    connect(m_dynamicMenu, SIGNAL(aboutToShow()), this, SLOT(updateDynamicMenu()));

    changeMenuBar();

    QPushButton *menuBtn = new QPushButton("Test");
    connect(menuBtn, SIGNAL(clicked()), this, SLOT(changeMenuBar()));

    setCentralWidget(menuBtn);
}

void MainWindow::changeMenuBar() {
    m_menuBar->clear();

    // Disappears as soon as this is called a second time
    QMenu *oneMenu = m_menuBar->addMenu("One");
    oneMenu->addAction("foo1");
    oneMenu->addAction("bar1");
    oneMenu->addAction("baz1");

    // Stays around but has 'Untitled' for title in menu bar
    QMenu *twoMenu = new QMenu("Two");
    twoMenu->addAction("foo2");
    twoMenu->addAction("bar2");
    twoMenu->addAction("baz2");
    QAction *twoMenuAction = m_menuBar->addAction("Two");
    twoMenuAction->setMenu(twoMenu);

    // Never shows up
    m_menuBar->addMenu(m_dynamicMenu);
}

void MainWindow::updateDynamicMenu() {
    m_dynamicMenu->clear();
    m_dynamicMenu->addAction("foo3");
    m_dynamicMenu->addAction("bar3");
    m_dynamicMenu->addAction("baz3");
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtWidgets>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);

private slots:
    void changeMenuBar();
    void updateDynamicMenu();

private:
    QMenuBar *m_menuBar;
    QMenu *m_dynamicMenu;
};

#endif // MAINWINDOW_H
2个回答

5

所有这些看起来都像是在OS X上的Qt bug。实际上,这是一个非常老的bug。

您可以通过使用QMenuBar :: addMenu函数调用来解决问题,而不是像这样使用QMenu:

m_menuBar->addMenu("One");

相反地,通过动态创建QMenu实例并通过QMenu :: menuAction检索到的QAction实例调用QMenuBar :: addAction来处理它,如下所示:

m_menuBar->addAction(oneMenu->menuAction());

除了使用QMenuBar :: addAction之外,如果您想仅动态创建某些特定菜单项,则可以使用QMenuBar :: removeAction和QMenuBar :: insertAction。
根据您的源代码,这是修改版本,它在每次按钮单击时处理所有菜单的动态创建(您在源代码中执行此操作),并且菜单“Dynamic”每次单击按钮时都会填充不同数量的项目。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtWidgets>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);

private slots:
    void changeMenuBar();

private:
    QMenuBar *m_menuBar;
    QMenu *m_dynamicMenu;
    int m_clickCounter;

};

#endif // MAINWINDOW_H

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      m_clickCounter(1)
{
    m_menuBar = new QMenuBar(this);

    connect(m_dynamicMenu, SIGNAL(aboutToShow()), this, SLOT(updateDynamicMenu()));

    changeMenuBar();

    QPushButton *menuBtn = new QPushButton("Test");
    connect(menuBtn, SIGNAL(clicked()), this, SLOT(changeMenuBar()));

    setCentralWidget(menuBtn);
}

void MainWindow::changeMenuBar() {
    ++m_clickCounter;

    m_menuBar->clear();

    QMenu *oneMenu = new QMenu("One");

    oneMenu->addAction("bar1");
    oneMenu->addAction("baz1");
    m_menuBar->addAction(oneMenu->menuAction());

    QMenu *twoMenu = new QMenu("Two");
    twoMenu->addAction("foo2");
    twoMenu->addAction("bar2");
    twoMenu->addAction("baz2");

    m_menuBar->addAction(twoMenu->menuAction());

    m_dynamicMenu = new QMenu("Dynamic");
    for (int i = 0; i < m_clickCounter; ++i) {
        m_dynamicMenu->addAction(QString("foo%1").arg(i));
    }

    m_menuBar->addAction(m_dynamicMenu->menuAction());
}

另外,在开发 OS X 菜单逻辑时,需要记住以下几点:

  • 可以使用 QMenuBar::setNativeMenuBar 禁用 QMenuBar 的本地行为。
  • 由于默认情况下 QMenuBar 的本地行为已启用,因此带有标准 OS X 标题(如“关于”、“退出”)的 QActions 将由 Qt 自动在屏幕上以预定义的方式放置;空 QMenu 实例将完全不显示。

2
我想指出的是,虽然添加oneMenu->menuAction()是正确的,并且对前两个菜单有效,但显然(在Mac OS X上)当将菜单添加到QMenuBar时,必须具有现有操作。由于我的动态菜单示例直到发出aboutToShow()信号才添加操作,因此我的解决方法是使用一个没有其他作用的QAction填充动态菜单以使其最初显示。在我的updateDynamicMenu()槽中,我清除并重新填充动态菜单。 - syrius
@syrius,仅供参考,可以使用QMenuBar :: setNativeMenuBar禁用平台本地菜单栏行为。但这可能会带来更多的麻烦。 在实际应用中,我们使用QActions来监听它们的信号。为了避免开销,最优解决方案是将所有QActions保留为私有成员,并在每次需要更改QMenu时使用它们填充动态菜单。 - Max Go
但在实际应用中,比如编辑器的“窗口”菜单,你不会为每个当前打开的文件设置私有成员操作。这就是为什么我在连接到aboutToShow()信号的槽中填充菜单。我只是提到,在您的动态菜单示例中,如果直到发出aboutToShow()信号才填充它,它将永远不会出现在菜单栏中,这就是我发布初始评论并要求一个空的QAction(因为您还不知道内容)的原因。我认为将此包含在答案中对其他人会有所帮助。 - syrius
@syrius “在 Mac OS X 上,似乎必须为 QMenuBar 添加现有操作时才能显示菜单”,你能提供一个参考吗?我正在尝试解决这个确切的问题。 - astrojuanlu

1
我认为您的问题在于这一行:


QMenu *oneMenu = m_menuBar->addMenu("One");

要将菜单添加到菜单栏中,您需要使用以下代码:
QMenuBar *m = new QMenuBar;
m->addMenu( new QMenu("Hmmm") );
m->show();

创建菜单,然后添加操作,最后将菜单添加到菜单栏中:
QMenu *item = new QMenu( "Test1" );
item->addAction( "action1" );

QMenuBar *t = new QMenuBar;
t->addMenu( item );
t->show();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接