如何在Qt中迭代菜单的操作?

21

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

假设我有以下菜单栏:

 File     Edit      Help
   -op1     -op1      -op1
   -op2     -op2      -op2
       

为了设置一个动作(显示与该动作相关联的菜单),我使用以下代码:
menuBar->setActiveAction(mymenuactionpointer);

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

或者

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*。
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() 时,总是导致分段错误。
我一直在搜索,我发现 here 在获取菜单栏操作列表后,可以要求 QAction::menu()(如果该项是菜单,则返回有效的 QMenu 指针)以知道该项是否为 QMenu,如果是,则可以重复获取该菜单的操作列表,并继续迭代。但这对我不起作用,我期望的是
QList<Object*> lst2 = QMenuBar::findChildren<QAction*>();

每个元素"文件、编辑、帮助"QAction::menu()都返回一个有效的菜单指针,所以我可以获取每个菜单的操作列表,但这对我来说根本不起作用。


我不确定你问题的结尾部分。QList<QMenu*> list = menuBar()->findChildren<QMenu*>(); 返回一个有效的 QMenu* 列表,您可以使用递归函数迭代它们,并且您可以获取它们的 ->actions()。您遇到了什么问题? - Hossein
4个回答

27

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

重要的是要理解每个 QMenu 也与 QAction 相关联。因此,正确的实现如下:

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
感谢您的回答。非常有用。 - PeerPandit

8
以下是如何遍历菜单栏中的每个菜单项,它还会查找其中的任何子菜单,因此这里不需要递归调用。
// 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,因为单选按钮分组是它设计的功能。(你可以通过不将可检查项添加到组中来解决这个问题。)

0

这将所有东西组合在一起:

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

然后在你的主函数中:

{
    QMenuBarIterator<CMenuLogger>           bar(ui->menuBar);

    bar.iterate(CMenuLogger());
}

0
qobject_cast 失败的原因是 QMenuBar 只有三个 QAction 作为父级,其他三个是不同的 QObjects(我猜测是三个 QMenus),所以转换失败。与这些菜单相关联的 QActions 在这些菜单下面,而不是根 QMenuBar 下面。我不明白为什么你不能通过递归迭代 QMenus 来构建 QActions 的主列表。
如果你想要一个已知菜单的 QAction 指针,可能不会触发父菜单,那么你可以尝试使用 UI 定义中的 QAction 指针。如果你正在尝试自动化测试,对所需 QAction 进行触发() 就足够了。如果你正在尝试响应用户操作进行一些操作,修改工具栏可能是更好的上下文反馈方式,因为它不会打断焦点。提供一些关于你实际想要完成的更多细节将有助于解决问题。

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