Menü-Änderung auserhalb des .rc Resource Files?



  • Also in der TestButtons.cpp gibt es folgende Funktion:

    void CTestButtons::OnSelchangeTabs(NMHDR*,LRESULT* pResult) 
    	{
    	int curSel = m_tabs.GetCurSel();
    
    	if( curSel == m_currentPage )
    		return;
    //
    //	Hide previous selection, delete its menu entry
    //
    	CSManApp*		app = (CSManApp*)AfxGetApp();
    	CMenu*			mainMenu = AfxGetMainWnd()->GetMenu();
    	int				pos;
    
    	if( m_currentPage != -1 )
    		{
    		pos = mainMenu->GetMenuItemCount() - 2;
    		mainMenu->DeleteMenu(pos, MF_BYPOSITION);		//	remove old
    		m_pages[m_currentPage]->ShowWindow(SW_HIDE);	//	hide previous
    		}
    	else
    		pos = mainMenu->GetMenuItemCount() - 1;
    //
    //	Show new selection.
    //
    	m_pages[m_currentPage = curSel]->ShowWindow(SW_NORMAL);
    //
    //	Change height
    //
    	setTabHeight();
    //
    //	change menu entry
    //
    	CStringArray	menuKeys;
    	CMenu*			menu = new CMenu;
    	CString			tabKey = commandKey + _T("\\") + m_subKeys.ElementAt(m_currentPage);
    
    	getSubKeys(tabKey,menuKeys);
    
    	int				menuCount = menuKeys.GetSize();
    
    	menu->CreateMenu();
    	for( int m = 0; m < menuCount; m++ )
    		{
    		menu->AppendMenu(MF_STRING,m + DEVICE_TYPE_START,menuKeys.ElementAt(m));
    		}
    	mainMenu->InsertMenu(
    		pos,
    		MF_STRING | MF_BYPOSITION | MF_POPUP,
    		(unsigned int)menu->GetSafeHmenu(),
    		m_subKeys.ElementAt(m_currentPage));
    	AfxGetMainWnd()->DrawMenuBar();
    	delete menu;
    //
    	if( pResult )
    		*pResult = 0;
    }		//	end of CTestBu
    

    und in CMenu in der AFXWIN.H steht:

    BOOL InsertMenu(UINT nPosition, UINT nFlags, UINT nIDNewItem = 0,
    					LPCTSTR lpszNewItem = NULL);
    	BOOL InsertMenu(UINT nPosition, UINT nFlags, UINT nIDNewItem,
    					const CBitmap* pBmp);
    

    Bin ich da auf dem richtigen Dampfer? Oder komplett verkehrt? 😕



  • Scheint der richtige Dampfer zu sein. Helfen sollte

    AfxGetMainWnd()->DrawMenuBar(); 
    menu->Detach(); // -> Add
    delete menu;
    


  • Uuuäääähhhh! !?! Wie kommt man auf sowas?? Genial es funktioniert und ein ganzes Menü erscheint! 🙂

    Ehrlich ... wie kommt man auf diese Lösungen?

    Tausend Dank sri... wirklich, tausend Dank! 🙂

    Unglaublich *kopf.schüttel* 🙂

    Phil



  • phil_z schrieb:

    Ehrlich ... wie kommt man auf diese Lösungen?

    Meistens, weil man selbst schon mal den gleichen Fehler gemacht hat 😃


  • Mod

    phil_z schrieb:

    Uuuäääähhhh! !?! Wie kommt man auf sowas?? Genial es funktioniert und ein ganzes Menü erscheint! 🙂
    Ehrlich ... wie kommt man auf diese Lösungen?
    Tausend Dank sri... wirklich, tausend Dank! 🙂
    Unglaublich *kopf.schüttel* 🙂

    Du hast ein Objekt erzeugt mit CreateMenu. Dieses Objekt weist Du einem aktiven Menu zu und zerstörst es durch den delete.
    Das Menühandlör fliegt auf die Schnautze wenn er dieses zerstörte Menü angezeigen soll.
    Detach verhindert, dass das zugewiesene Objekt zerstört wird.

    Detach muss immer verwendet werden wenn ein Handle nich mehr an ein Objekt gebunden ist und andersweitig (z.B. in der Win32 API) verwaltet wird.

    Aber mal grundsätzlich:
    Warum veränderst Du das Menü sofort undimmer, warum veränderst Du das Menü nicht est dann, wenn es auch benötigt wird und aufgeklappt wird?
    Schau Dir mal den Code an, den die MFC verwendet um die LRU-Files Liste zu verwalten.

    Siehe sourcecode filelist.cpp
    void CRecentFileList::UpdateMenu(CCmdUI* pCmdUI)

    Dort wird ein Dummy Entrag durch eine Liste von Commands mit InsertMenu (hir nurnoch einzelne Items) ersetzt.



  • Im Prinzip hat Martin schon alles gesagt. Mit mainMenu->InsertMenu fügst Du das neu erstellte Menü in das Hauptmenü ein. Das Menühandle fällt damit in den Zuständigkeitsbereich des Hauptmenüs und wird automatisch bei Löschen des Hauptmenüs freigegeben. Durch delete menu wird es aber schon vorher gelöscht und das im Hauptmenü steckende Handle ist ungültig (daher das ASSERT).

    Deshalb muss man dafür sorgen, dass menu zwar gelöscht wird, das Windows-Handle aber erhalten bleibt. Dafür verwendet man in der Regel Detach.

    Ich selbst würde hier auf das dynamische CMenu verzichten und stattdessen eine lokale CMenu-Instanz erstellen. Zudem kann man das Detach auch gleich bei mainMenu->InsertMenu verwenden:

    //
    //    change menu entry
    //
        CStringArray    menuKeys;
        CMenu            menu;
        CString            tabKey = commandKey + _T("\\") + m_subKeys.ElementAt(m_currentPage);
    
        getSubKeys(tabKey,menuKeys);
    
        int                menuCount = menuKeys.GetSize();
    
        menu.CreateMenu();
        for( int m = 0; m < menuCount; m++ )
            {
            menu.AppendMenu(MF_STRING,m + DEVICE_TYPE_START,menuKeys.ElementAt(m));
            }
        mainMenu->InsertMenu(
            pos,
            MF_STRING | MF_BYPOSITION | MF_POPUP,
            (unsigned int)menu.Detach(),
            m_subKeys.ElementAt(m_currentPage));
        AfxGetMainWnd()->DrawMenuBar();
    


  • Hey,

    vielen, vielen Dank für eure Erklärungen! Wenn man zum ersten mal damit in Berührung kommt, ist es doch etwas kompliziert. 🙂

    Also Du schlägst vor anstatt

    CMenu*	menu = new CMenu;
    
    CMenu   menu;
    

    zu verwenden. Aber wird durch diesen Befehl nicht nur Speicherplatz reserviert?
    Muss dann nicht irgendwo noch

    menu = irgendwas;
    

    stehen?

    Zum Verständnis: CMenu scheint doch die Klasse zu sein um generell Drop-Down-Menüs zu erstellen, ist das so richtig?

    Und wo ist denn der Unterschied zwischen Detach() und GetSafeHmenu()? Bzw was genau ist das Argument nIDNewItem

    Weil Detach() führt ja was aus (eben dass das Windows Handle nicht gelöscht wird) aber wie kann es das gleiche Argument zurück geben wie GetSafeHmenu()?

    Hoffentlich frage ich hier nicht zu viel... danke im Voraus! 🙂 👍

    lg
    Phil



  • CMenu   menu;
    

    macht im funktionell auch nichts anderes als

    CMenu*    menu = new CMenu;
    

    Der Unterschied besteht darin, dass im ersten Fall das Objekt direkt auf dem Stack und im zweiten Fall dynamisch auf dem Heap angelegt wird. Im ersten Fall wird das Objekt beim Verlassen des Gültigkeitsbereichs (in diesem Fall der Funktion) automatisch entfernt und der Destruktor aufgerufen. Beim dynamischen Anlegen muss zwingend ein delete erfolgen, ansonsten wird der Speicher nicht freigegeben (bzw. erst beim Beeenden der Anwendung).

    In beiden Fällen hat man aber ein leeres CMenu-Objekt. Die eigentliche Erstellung des Menüs erfolgt erst durch CreateMenu.

    GetSafeMenu liefert lediglich das in CMenu gekapselte HMENU zurück. Detach liefert ebenfalls das HMENU, löst aber zudem noch die Verbindung zwischen CMenu-Objekt und HMENU. Unterlässt man das, dann wird das HMENU im Destruktor von CMenu automatisch gelöscht und das bei Dir in mainMenu verwendete Handle wird damit ungültig.



  • Ok,... soweit verstehe ich das. (Also ist CMenu auch eine statische klasse, oder?)

    Und wo genau liegt der Vorteil, dass es nicht dynamisch erzeugt wird und es deshalb nicht über den Heap sondern über den Stack abgewickelt wird? Spart das Rechenzeit uns Speicher?

    Warum HMENU dann als unsigned int der Funktion übergeben wird, verstehe ich jetzt zwar nicht ganz genau, aber das ist vermutlich egal. Ich meine ganz egal was HMENU ist, die funktion bekommt ja "bloß" eine uint zahl übergeben.)



  • CMenu kann wie fast jede Klasse statisch oder dynamisch angelegt werden. Letztlich liegt es daran, was der jeweilige Entwickler bevorzugt.

    Durch die statische Verwendung wird auf dynamische Allokation mit new verzichtet und stattdessen der Stack verwendet. Das spart vorwiegend Rechenzeit. Der Speichergewinn ist eher minimal, da CMenu nicht gerade groß ist.

    Die Übergabe als UINT_PTR hat mit der Funktionsdeklaration von InsertMenu zu tun.

    BOOL InsertMenu(
       UINT nPosition,
       UINT nFlags,
       UINT_PTR nIDNewItem = 0,
       LPCTSTR lpszNewItem = NULL 
    );
    

    nIDNewItem gibt entweder die Befehls-ID für den Menüeintrag an oder das HMENU, wenn nFlags den Wert MF_POPUP enthält.



  • Oh super! Vielen, vielen Dank für die Hilfestellungen und die vielen Erklärungen. Jetzt verstehe ich den Hintergrund viel besser und alles scheint einen Sinn zu ergeben 🙂

    Gerade das mit der Befehls-ID war Rätselhaft für mich, weil ich mich fragte, wieviel Information kann in so einer Integer Zahl drin stecken :). Aber so macht es natürlich großen Sinn und spart sogar noch Ressource, da nicht das ganze HMENU übergeben werden muss!

    Echt cool dass ihr zwei euch die Zeit genommen habt meine löchernden Fragen zu beantworten und ich hoffe ich kann das Wissen genau so weiter geben! 👍

    Besten Dank und Grüße,
    Phil


  • Mod

    Ein HMENU ist auch nur eine 32bit Zahl! 😉

    Nur noch mal am Rande:

    Hinter einem Handle verbirgt sich ein internes Objekt der Win32 API. Ein Handle ist 32bit groß im 32bit OS (64bit in einem 64bit OS).

    Der Speicher, der hinter einem Handle steht kann immens sein (Bitmap).

    Die MFC Kapselt ein Handle immer in einem Objekt. Dieses Objekt ist immer so konstruiert das der Destruktor das Handle mit der passenden Funktion freigibt.
    Diese Wrapper Klassen (wie man sie auch gemeinhin nennt), sind minimale Klassen, die auch die entsprechenden API Funktionen passend zum Handle kapseln.

    Wrapper Klassen können überall angelegt werden. Auf dem Stack, in einer Klasse eingebettet, auf dem Heap.
    Die FRage ist eigentlich nur, wann will man das Objekt entsorgen. Der Heap ist die ungewöhnlichste Allokationsart und ist meistens überflüssig.

    Wichtig ist das bei Initialisierung einer solchen Wrapper Klasse das Handle meistens NULL ist.

    Die entsprechenden Handle können durch die entsprechenden Create Funktionen erzeugt werden. Oder man hat ein Handle und weißt es per Attach der Wrapper Klasse zu.
    Wichtig! Der Destruktor schlägt zu wenn das Handle zugeordnet bleibt.
    Will man das Handle über die Lebensdauer der Wrapperklasse erhalten muss man ein Detach durchführen.

    Wichtig: Ein Handle kann nur einer Wrapperklasse zugeordnet werden. Mit der Funktion FromHandle kann man jederzeit die angelegte Wrapperklasse zu dem Handle ermitteln. Aber Achtung. Manche dieser Wrapperobjekte die dann returniert werden sind nur temporär.


Anmelden zum Antworten