How to update geometry on QAction visibility change in QMenu::aboutToShow?



  • Hello, I'm wanting to update the visibility of my QActions in my QMenu just before the menu is shown. This results in poor placement of the menus (example):

    http://i.imgur.com/SWq3ZPp.png

    It only draws poorly when the length of the menu - before visibility changes - exceeds available vertical drawing space. (The actual code has a long, long list of QActions in the QMenu, though only a small subset are visible at any given period).

    Sample code:

    QMenu* pMenu = mainWin.menuBar()->addMenu( QString().sprintf( "Menu %d", m ) );
    app.connect( pMenu, &QMenu::aboutToShow, [=]() { AboutToShow( pMenu ); } );
    
    void AboutToShow( QMenu* pMenu )
    {	// randomly assign some to not visible (dummy sample code)
    	for ( int a = 0; a < pMenu->actions().size(); a++ )
    	{	bool isVisible = (rand() % 4) > 0;
    		pMenu->actions().at( a )->setVisible( isVisible );
    	}
    	// ***
    }
    

    At the end of AboutToShow at ***, I've tried:

    • update, updateGeometry, adjustSize(), ensurePolished with both pMenu and pMenu->parentWidget()
    • invalidating the layout, but neither pMenu nor pMenu->parentWidget have a valid widget at this point.

    Suggestions? Thoughts? Alternatives? If you don't change which actions are visible between showings, on any subsequent popups, the menu's position is correctly computed.

    Thanks in advance!


  • Moderators

    Looks like a nasty bug. Please see if there's anything already reported at bugreports.qt.io and if not make a new entry.

    As for possible workarounds - you could probably calculate the proper position for the menu yourself. The problem is that you need to move the menu after Qt has set its position, or whatever you set will be overwritten. For that you can use a 0 timeout singleshot timer - it will execute next time you get back to the event loop, which should be right after the menu is shown:

    void AboutToShow( QMenu* pMenu )
    {   
        // do your hiding here
       QTimer::singleShot(0, [=]{
            pMenu->move(pMenu->parentWidget()->mapToGlobal(pMenu->parentWidget()->rect().bottomLeft()));
        });
    }
    

    This is a crude calculation just to give you an example. You could do better so the menu does not show outside of the screen.



  • Thanks Chris for the singleShot timer idea. In the meanwhile, I've posted a bug report: https://bugreports.qt.io/browse/QTBUG-54421.