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

Hermandroid picture Hermandroid · Feb 22, 2012 · Viewed 21.7k times · Source

I'm working in a project where I need to open (show or popup) automatically the items in the QMenuBar.

Let's say I have the next menu bar:

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

To set an action (show the menu associated with that action) I use:

menuBar->setActiveAction(mymenuactionpointer);

As I know, I can use one of the following to get a list of pointers to the elements of QMenuBar:

QMenuBar::actions();

or

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

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

When I use QMenuBar::findChildren<QAction*>() or MenuBar::actions() I got a list of the menus in menubar, I mean, I got "File, Edit, Help" from my QMenuBar, the size of the QList in this case is 3.

When I use QMenuBar::findChildren<QObject*>() I got a list of QObject of size 6, which is the correct number of items in the menu bar. However, I have tried cast to QAction*

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

In all this cases a is not NULL, but when I try to get the action name QAction::title() it always causes me segmentation fault.

I have been searching and I found here that after getting the menubar actions list, one can ask to QAction::menu() (which returns a valid QMenu pointer if the item is a menu) to know if the item is a QMenu, if yes, one can repeat getting the actions list of that menu, and continue iterating. But this does not work for me, I expected that for

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

each element "File, Edit Help" QAction::menu() returns a valid menu pointer, so I could get the list of the actions of each menu, but this does not work at all for me.

Answer

sashoalm picture sashoalm · Jul 18, 2016

The correct way to enumerate a QMenu is to use the actions() functions, but there is a catch - some of the actions are submenus, and they need to be iterated recursively. In fact, each QMenu is associated with a QAction, and they both hold pointers to each other - see QMenu::menuAction() and QAction::menu().

It is crucial to understand that each QMenu is also associated with a QAction. So knowing that, the proper implementation is the following:

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