关于 qmenu:如何在 Qt 中迭代菜单的操作?

How to iterate through a menu's actions in Qt?

我正在一个项目中工作,我需要自动打开(显示或弹出)QMenuBar 中的项目。

假设我有下一个菜单栏:

1
2
3
 File     Edit      Help
   -op1     -op1      -op1
   -op2     -op2      -op2

要设置一个动作(显示与该动作相关的菜单),我使用:

1
menuBar->setActiveAction(mymenuactionpointer);

据我所知,我可以使用以下方法之一来获取指向 QMenuBar 元素的指针列表:

1
QMenuBar::actions();

1
2
3
QList<Object*> lst1 = QMenuBar::findChildren<QObject*>();

QList<Object*> lst2 = QMenuBar::findChildren<QAction*>();

当我使用 QMenuBar::findChildren<QAction*>()MenuBar::actions() 时,我得到了菜单栏中的菜单列表,我的意思是,我从 QMenuBar 得到 "File, Edit, Help",在这种情况下 QList 的大小为 3。

当我使用 QMenuBar::findChildren<QObject*>() 时,我得到了一个大小为 6 的 QObject 列表,这是菜单栏中正确的项目数。但是,我尝试过强制转换为 QAction*

1
2
3
QAction *a = (QAction *)lst1.at(0);
QAction *a = qobject_cast<QAction*>(lst1.at(0));
QAction *a = dynamic_cast<QAction*>(lst1.at(0));

在所有这些情况下,a 都不是 NULL,但是当我尝试获取操作名称 QAction::title() 时,它总是会导致我出现分段错误。

我一直在搜索,我在这里发现,在获取菜单栏操作列表后,可以询问 QAction::menu()(如果项目是菜单,则返回有效的 QMenu 指针)以了解项目是否是 QMenu,如果是的,可以重复获取该菜单的操作列表,然后继续迭代。但这对我不起作用,我希望对于

1
QList<Object*> lst2 = QMenuBar::findChildren<QAction*>();

每个元素 "File, Edit Help" QAction::menu() 返回一个有效的菜单指针,因此我可以获得每个菜单的操作列表,但这对我来说根本不起作用。


枚举 QMenu 的正确方法是使用 actions() 函数,但有一个问题——一些动作是子菜单,它们需要递归迭代。事实上,每个 QMenu 都与一个 QAction 相关联,并且它们都持有彼此的指针 - 参见 QMenu::menuAction() 和 QAction::menu()。

了解每个 QMenu 也与一个 QAction 相关联至关重要。所以知道了,正确的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void enumerateMenu(QMenu *menu)
{
    foreach (QAction *action, menu->actions()) {
        if (action->isSeparator()) {
            qDebug("this action is a separator");
        } else if (action->menu()) {
            qDebug("action: %s", qUtf8Printable(action->text()));
            qDebug(">>> this action is associated with a submenu, iterating it recursively...");
            enumerateMenu(action->menu());
            qDebug("<<< finished iterating the submenu");
        } else {
            qDebug("action: %s", qUtf8Printable(action->text()));
        }
    }
}


下面是如何遍历菜单栏中的每个菜单项,它还会查找下面的任何菜单,因此这里不需要递归调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
// assuming you have a private QActionGroup* actions; defined in the header..
// ...and a slot named 'onAction(QAction*)' as well... this should work:
QList<QMenu*> lst;
lst = ui->menuBar->findChildren<QMenu*>();
actions = new QActionGroup(this);
foreach (QMenu* m, lst)
{
    foreach (QAction* a, m->actions())
    {
        actions->addAction(a);
    }
}
connect(actions,SIGNAL(triggered(QAction*)),this,SLOT(onAction(QAction*)));

如您所见,然后您可以连接一个主插槽来处理一个动作可能引发的各种事件(我只是在这里展示了触发,但您明白了)。希望这会有所帮助.. 某人..

注意事项
我将 QActionGroup 用于使用您可能迭代的列表的示例目的,但除非您正在处理无线电组,否则您真的不应该使用它,因为这就是它的用途。其次,如果您想要这些操作,因为您计划将它们链接到一个方法来处理所有项目,我建议您使用 QMenu 的触发/悬停信号,或者如果您需要知道菜单何时即将弹出,您\\'将需要 QMenuBar\\ 的 aboutToShow() 信号。我想不出一个原因(无论如何对我来说)你不能在这些信号中做你需要的事情,因为你在插槽中通过了 QAction*。但是,如果您必须以其他方式进行操作,您可以按照我上面显示的方式进行操作,您可能不想使用 QActionGroup,因为无线电分组是它的设计目的。 (您可以通过不将"可检查"的项目添加到组中来解决此问题。


这把它们放在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
template <class Function>
class QMenuBarIterator {
    QMenuBar    *i_barP;

    void    iterate_sub(Function f, size_t tabsL, QMenu* m) {
        foreach (QAction *action, m->actions()) {
            f(tabsL, action);

            if (action->menu()) {
                iterate_sub(f, tabsL + 1, action->menu());
            }
        }
    }

    public:
    QMenuBarIterator(QMenuBar *barP) : i_barP(barP) {}

    virtual void operator()(size_t levelL, QAction *actionP) {
    }

    void    iterate(Function f) {
        QList<QMenu *>  menuBar = i_barP->findChildren<QMenu *>();

        foreach (QMenu* m, menuBar) {
            f(0, m->menuAction());
            iterate_sub(f, 1, m);
        }
    }
};

/***************************************************************************/
class CMenuLogger {
    public:

    void operator()(size_t tabsL, QAction *action) {
        SuperString     tabStr(GetIndentString(tabsL)); //  returns a string with tabsL tab characters in it

        if (action->isSeparator()) {
            qDebug("%s-------------------", tabStr.utf8Z());

        } else {
            qDebug("%s%s (%s)",
                tabStr.utf8Z(),
                qUtf8Printable(action->text()),
                qUtf8Printable(action->objectName()));
        }
    }
};

然后在你的主目录中:

1
2
3
4
5
{
    QMenuBarIterator<CMenuLogger>           bar(ui->menuBar);

    bar.iterate(CMenuLogger());
}

qobject_cast 失败的原因是只有三个以 QMenuBar 作为父级的 QAction。其他三个是不同的 QObjects(我猜是三个 QMenus),所以转换失败。与这些菜单关联的 QActions 位于这些菜单下,而不是根 QMenuBar。我不明白为什么您不能通过递归地遍历 QMenus 来构建 QActions 的主列表。

如果您在使用已知菜单,则可能只使用 UI 定义中的 QAction 指针,这可能不会触发父菜单。如果您尝试自动化测试,您所需的 QAction 上的 trigger() 可能与您需要的一样详细。如果您尝试对用户操作做出响应,那么修改工具栏可能是一种更好的上下文反馈方式,因为它不会破坏焦点。有关您实际尝试完成的任务的更多详细信息会有所帮助。