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



  • Die MFC ist mit ASSERTs zugepflastert, um beim Debuggen mögliche Fehlerquellen schneller zu lokalisieren. Hier verwendet aber kaum noch einer VC5, deshalb kann hier keiner nachschauen, was in der afxwin1.inl an Zeile 1046 steht. Dies ist aber wichtig, um den Fehler etwas eingrenzen zu können.

    Aber Du kannst uns sicher sagen, was um dieser Stelle herum dort steht. 🙂



  • lol ja is scho recht alt... das stimmt.
    Also beim click auf retry im debug modus steht folgendes in der AFXWIN1.INL

    _AFXWIN_INLINE UINT CMenu::GetMenuItemCount() const
    	{ ASSERT(::IsMenu(m_hMenu)); return ::GetMenuItemCount(m_hMenu); }
    

    Verstehe ich das scho richtig, dass eben kein Untermenü existiert und das Programm deshalb abstürzt, ge?

    In der WINFRM.CPP steht folgendes:

    state.m_nIndexMax = pMenu->GetMenuItemCount();
    

    Macht das hier irgendwen schlauer? 😕

    Daanke! 🙂



  • Das heißt, dass m_hMenu kein gültiges Menühandle ist.

    Wie heißt denn der "überflüssige" Menüeintrag in der Leiste genau?



  • Der Menü eintrag heißt "Calibration" ...
    nach dem String zu suchen macht aber keinen Sinn, da gibt es einfach zuuu viele Dateien.


  • Mod

    pMenu ist NULL oder hat kein gültiges Menu!

    Welcher Code von Dir wird denn ausgeführt. Schau Dir mal den Callstack an!



  • Das sind die oberen Stack Zeilen nach dem klick auf Retry:

    CMenu::GetMenuItemCount() line 1046 + 46 bytes
    CFrameWnd::OnInitMenuPopup(CMenu * 0x00940700 {CMenu}, unsigned int 2, int 0) line 1402 + 8 bytes
    CWnd::OnWndMsg(unsigned int 279, unsigned int 1574269, long 2, long * 0x0012f2a4) line 1858
    CWnd::WindowProc(unsigned int 279, unsigned int 1574269, long 2) line 1575 + 30 bytes
    AfxCallWndProc(CWnd * 0x0092e900 {CMainFrame hWnd=???}, HWND__ * 0x000801dc, unsigned int 279, unsigned int 1574269, long 2) line 217 + 26 bytes
    AfxWndProc(HWND__ * 0x000801dc, unsigned int 279, unsigned int 1574269, long 2) line 371
    AfxWndProcBase(HWND__ * 0x000801dc, unsigned int 279, unsigned int 1574269, long 2) line 203 + 21 bytes



  • ... und... wird aus diesem Stack irgendjemand schlauer? Es kann doch nicht sein, dass so ein Menüeintrag nicht wegzukriegen ist!? 😞

    Wo (außer in der Resource Datei) wird denn das Menü noch "beeinflusst"?

    Danke und beste Grüße,
    Phil


  • Mod

    Das Framework kommt mit dem Menü nicht zurecht.
    Dieser Menüeintrag erscheint ja nicht von Geisterhand!
    Hast Du evtl. mit InsterMenu/InsertMenuItem herum gespielt?

    BTW: Ohne das ich weiß was für eine VS Version Du benutzt kann man Dir schwer helfen, selbst mit Stacktrace.



  • Hey also wie oben geschrieben benutze ich VS C++ 5.0 Developer Studio 97 (also scho a bissi älter).
    Ne das Problem ist, das jemand anders den Fehler verursacht hat... ich versuche nur ihn auszumärzen. *grr*
    Gibt es ne Möglichkeit die InsertMenu zu reparieren oder sowas?



  • Grase mal den gesamten Quelltext nach InsertMenu/InsertMenuItem ab.



  • 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.


Anmelden zum Antworten