Fensterfunktionen in eine Klasse packen



  • Ich wollte mal die ganzen Sachen, die man so braucht, um ein Fenster zu erstellen, in eine Klasse packen, so daß man nur noch ein Objekt erstellen und fenster.Start () aufrufen braucht. Aber hier gibt es Probleme, da die WndProc-Funktion global sein muß und er eine Memberfunktion nicht anerkennt. Das einzige, was ging, war, die WndProc statisch zu machen, aber das ist ja nicht Sinn der Sache, da man ja von dieser Fensterklasse ableiten und die WndProc überschreiben können soll, um sich eigene Fenster anzulegen.
    Wie kann ich dieses Problem also möglichst unkompliziert lösen?



  • http://www.c-plusplus.net/forum/viewtopic-var-t-is-39356.html

    Statisch, wie du schon sagtest und dann mittels Get/SetWindowLong(Ptr) den Parameter GWL_USERDATA auf das aktuelle Objekt setzen, damit kannst du dann in deiner statischen WndProc zwischen den Fenstern/Objekten differenzieren.

    Ich würde weiterhin die Basis-Fensterklasse abstrakt definieren, also die Methode WndProc abtrakt nutzen, da du ja eh (Neu) definieren musst.



  • Ich hab den Text von dem Link nicht genau gelesen, aber ich habe mir in der Zwischenzeit mal was eigenes ausgedacht: Die Klasse beskommt eine statische Funktion StaticWndProc, welche die eigentliche WndProc für das Programm darstellt. Außerdem gibt es eine virtuelle WndProc, die man redefinieren kann, wenn man von dem Fenster ableitet, um die eigenen Nachrichten zu handlen. Das ganze funktioniert so (meine Fensterklasse heißt Window):
    Ich habe eine statische Liste

    std::list<Window *> windowList;
    

    Im Konstruktor wird nun der this-Zeiger per push_back-Funktion der Liste hinzugefügt. In der StaticWndProc-Funktion wird die Liste durchsucht, bis die HWND-Variable des Listenelements (welche ja eine Membervariable der Klasse Window ist) mit dem lokalen HWND-Handle identisch ist. Und dann wird durch die Variable windowList die virtuelle Memberfunktion WndProc des jeweiligen Fensters aufgerufen. Durch entsprechende Rückgabewerte wird noch geprüft, ob die Nachricht verarbeitet wurde und je nachdem wird in StaticWndProc entweder 0 oder DefWindowProc zurückgegeben.



  • NES-Spieler schrieb:

    In der StaticWndProc-Funktion wird die Liste durchsucht, bis die HWND-Variable des Listenelements (welche ja eine Membervariable der Klasse Window ist) mit dem lokalen HWND-Handle identisch ist.

    Jo das kannste zwar (wenn ich dich richtig verstanden habe 😉 ) so machen, aber das suchen dauert ja auch -je nach Anzahl der Objekte- n' bissl.

    Hab dir mal n Beispiel geschrieben, wie es meiner Meinung nach, effizienter geht:

    Datei => CButton.hpp

    class CButton
    {
       private:
          HWND _hWndButton;
          UINT _uiControlID;
          // Weitere...
    
          static LRESULT CALLBACK ButtonWndProc(HWND hWnd, UINT uiMsg, LPARAM lParam, WPARAM wParam);
    
       public:
          bool Create(HWND hParent, const PTCHAR pszText, UINT uiID, int iX, int iY,
                     int iWidth, int iHeight, HINSTANCE hInst = GetModuleHandle(NULL));
          // Weitere...
    };
    

    Datei => CButton.cpp

    bool CButton::Create(HWND hParent, const PTCHAR pszText, UINT uiID, int iX, int iY, int iWidth, int iHeight, HINSTANCE hInst)
    {
       _hWndButton = CreateWindow(TEXT("BUTTON"), pszText, WS_CHILD | WS_VISIBLE,
                                  iX, iY, iWidth, iHeight, hParent,
                                  reinterpret_cast<HMENU>(uiID), hInst, NULL);
       SetWindowLongPtr(_hWndButton, GWL_USERDATA, reinterpret_cast<LONG_PTR>(this));
    }
    
    LRESULT CALLBACK CButton::ButtonWndProc(HWND hWnd, UINT uiMsg, LPARAM lParam, WPARAM wParam)
    {
       CButton* pClass = reinterpret_cast<CButton*>(GetWindowLongPtr(hWnd, GWL_USERDATA));
    
       pClass-> ...
    }
    

    Oder meinst du was ganz anderes ? 😃



  • Oder meinst du was ganz anderes ?

    Nein, nein, Du hast das schon richtig verstanden. Ich habe das Programm mal folgendermaßen geschrieben, aber leider stürzt es aus mir unerfindlichen Gründen ab. Was habe ich falsch gemacht?

    #include <windows.h>
    
    class Window
    {
    public:
    
    	Window ()
    	{
    		WNDCLASSEX wndClassEx={0};
    
    		wndClassEx.cbSize=sizeof (wndClassEx);
    		wndClassEx.lpfnWndProc=StaticWindowProc;
    		wndClassEx.hInstance=GetModuleHandle (NULL);
    		wndClassEx.hIcon=LoadIcon (NULL, IDI_APPLICATION);
    		wndClassEx.hCursor=LoadCursor (NULL, IDC_ARROW);
    		wndClassEx.hbrBackground=reinterpret_cast<HBRUSH> (NULL_BRUSH);
    		wndClassEx.lpszClassName="Testprogramm";
    		wndClassEx.hIconSm=wndClassEx.hIcon;
    
    		RegisterClassEx (&wndClassEx);
    
    		window=CreateWindowEx (0, wndClassEx.lpszClassName, wndClassEx.lpszClassName,
                                   WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
                                   CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
                                   wndClassEx.hInstance, NULL);
    
    		//Setzen des Zeigers
    		SetWindowLongPtr (window, GWL_USERDATA, reinterpret_cast<LONG_PTR> (this));
    	}
    
    	virtual int Start (int showCmd)
    	{
    		MSG message;
    
    		ShowWindow (window, showCmd);
    		UpdateWindow (window);
    
    		while (GetMessage (&message, NULL, 0, 0))
    		{
    			TranslateMessage (&message);
    			DispatchMessage (&message);
    		}
    
    		return message.wParam;
    	}
    
    protected:
    
    	//Die ableitbare virtuelle WindowProc
    	virtual bool WindowProc (UINT msg, WPARAM wParam, LPARAM lParam)
    	{
    		switch (msg)
    		{
    		case WM_CLOSE:
    			DestroyWindow (window);
    			break;
    		case WM_DESTROY:
    			PostQuitMessage (0);
    			break;
    		default:
    			return false;
    		}
    
    		return true;
    
    		/*Je nachdem, ob eine Nachricht behandelt wurde oder nicht,
    		  wird ein anderer Wert zurückgegeben.*/
    	}
    
    private:
    
    	HWND window;
    
    	/*Die eigentliche WindowProc, die nach dem Objekt sucht und
    	  dessen virtuelle WindowProc aufruft*/
    	static LRESULT CALLBACK StaticWindowProc (HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
    	{
    		//Holen des Zeigers
    		Window *thisWindow=reinterpret_cast<Window *> (GetWindowLongPtr (wnd, GWL_USERDATA));
    
    		/*Je nach Rückgabewert wird entweder 0 zurückgegeben oder
    		  DefWindowProc aufgerufen.*/
    		return thisWindow->WindowProc (msg, wParam, lParam)?0:DefWindowProc
    		(wnd, msg, wParam, lParam);
    	}
    };
    
    int WINAPI WinMain (HINSTANCE instance, HINSTANCE, LPSTR, int showCmd)
    {
    	Window w;
    
    	return w.Start (showCmd);
    }
    


  • Wie bzw wo genau schmiert er denn ab ?



  • Ist doch wohl klar. Es kommen doch schon Nachrichten an bevor du das SetWindowLongPtr machst. Und dann ist thisWindow eben ungültig.



  • Jop, stimmt. Sowas bekommste aber schnell mit dem Debugger heraus.

    Hab den Code mal korrigiert (auch n bissl das Design 😃 -Sry-) 😉 :

    #include <Windows.h>
    
    class CWindow
    {
    	public:
    
    		CWindow(const PTCHAR pszClass, const PTCHAR pszCaption, HINSTANCE hInst = GetModuleHandle(NULL))
    		{
    			WNDCLASSEX wndclex = { 0 };
    
    			wndclex.cbSize			= sizeof(WNDCLASSEX);
    			wndclex.lpfnWndProc		= CWindow::CommonWndProc;
    			wndclex.hInstance		= hInst;
    			wndclex.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
    			wndclex.hCursor			= LoadCursor(NULL, IDC_ARROW);
    			wndclex.hbrBackground	= reinterpret_cast<HBRUSH>(NULL_BRUSH);
    			wndclex.lpszClassName	= pszClass;
    			wndclex.hIconSm			= wndclex.hIcon;
    
    			RegisterClassEx(&wndclex);
    
    			_hWnd = CreateWindowEx(0L, pszClass, pszCaption,
    									WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
    									CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
    									hInst, NULL);
    
    			// Setzen des Zeigers:
    			SetWindowLongPtr(_hWnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(this));
    		}
    
    		virtual int Start(int iShowCmd)
    		{
    			MSG msgApp;
    
    			ShowWindow(_hWnd, iShowCmd);
    			UpdateWindow(_hWnd);
    
    			while(GetMessage (&msgApp, NULL, 0, 0))
    			{
    				TranslateMessage (&msgApp);
    				DispatchMessage (&msgApp);
    			}
    
    			return (msgApp.wParam);
    		}
    
    	protected:
    
    		// Die ableitbare virtuelle WndProc:
    		virtual bool WndProc (UINT uiMessage, WPARAM wParam, LPARAM lParam)
    		{
    			switch(uiMessage)
    			{
    				case WM_CLOSE:
    					DestroyWindow(_hWnd);
    					break;
    				case WM_DESTROY:
    					PostQuitMessage(0);
    					break;
    				default:
    					return (false);
    			}
    			return (true);
    
    			/*
    			 * Je nachdem, ob eine Nachricht behandelt wurde oder nicht,
    			 * wird ein anderer Wert zurückgegeben.
    			 */
    		}
    
    	private:
    
    		HWND _hWnd;
    
    		/*
    		 * Die eigentliche CWindowProc, die nach dem Objekt sucht und
    		 * dessen virtuelle CWindowProc aufruft
    		 */
    		static LRESULT CALLBACK CommonWndProc (HWND hWnd, UINT uiMessage, WPARAM wParam, LPARAM lParam)
    		{
    			// Holen des Zeigers:
    			CWindow* pThis = reinterpret_cast<CWindow*>(GetWindowLongPtr(hWnd, GWL_USERDATA));
    
    			if(pThis == NULL)
    				return (DefWindowProc(hWnd, uiMessage, wParam, lParam));
    			/*
    			 * Je nach Rückgabewert wird entweder 0 zurückgegeben oder
    			 * DefCWindowProc aufgerufen.
    			 */
    			return pThis->WndProc(uiMessage, wParam, lParam) ? 0L :
    						  DefWindowProc(hWnd, uiMessage, wParam, lParam);
    		}
    };
    
    int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, char* pszCmdLine, int iShowCmd)
    {
        CWindow cAppWnd(TEXT("Fensterklasse"), TEXT("Fenstertitel"), hInst);
    
        return (cAppWnd.Start(iShowCmd));
    }
    


  • Vielen Dank für die Hilfe.
    Eine Frage hätte ich aber noch: Kennt jemand eine vernünftige Art der virtuellen WndProc, einen Wert zurückzugeben? Ich meine, das Problem ist ja folgendes: Nur, wenn die Nachricht nicht behandelt wurde, wird DefWindowProc aufgerufen. Wäre dem nicht so, könnte man in der statischen WndProc einfach folgendes schreiben (nach allen Abfragen und Vorarbeiten etc.):

    pThis->WndProc (uiMessage, wParam, lParam);
    
    return DefWindowProc (hWnd, uiMessage, wParam, lParam);
    

    (WndProc könnte hier den Rückgabewert void haben.) Aber da man DefWindowProc eben nur aufrufen soll, wenn die Nachricht nicht behandelt wurde, muß es so aussehen:

    if (pThis->WndProc (uiMessage, wParam, lParam))
        return 0;
    else
        return DefWindowProc (hWnd, uiMessage, wParam, lParam);
    

    Nun ist das ganze aber etwas blöd, wenn man eine Klasse ableitet und die virtuelle WndProc überschreibt. Würde man wie im ersten Fall vorgehen, könnte man folgendes schreiben:

    void MyWindow::WndProc (...)
    {
        //Vorgeplänkel
    
        CWindow::WndProc (...);
    }
    

    Die Funktion ruft einfach die gleichnamige Funktion der Basisklasse auf, wie es bei dieser Art der Redefinition ja üblich ist. Doch nun, da ja der Rückgabewert wichtig ist und dieser auch von den Ergebnissen der eigenen Funktion abhängt, müßte man eigentlich bei jeder Ableitung folgendes schreiben:

    bool MyWindow::WndProc (...)
    {
        bool processed=false;
    
        switch (msg)
        {
        case WM_Irgendwas:
            //Mach was
            processed=true;
            break;
        case WM_IrgendwasAnderes:
            //Mach was anderes
            processed=true;
            break;
        }
    
        return CWindow::WndProc (...) || processed; //Wurde eine Nachricht in der Basisfunktion oder hier behandelt? Dann gib true zurück!
    }
    

    Und diese Art der Rückgabe (es muß ja immer geguckt werden, ob irgendwo, hier oder in einer Basisklasse, die Nachricht behandelt wurde) finde ich etwas umständlich, unelegant und einfach nicht gut. Gibt es da eine bessere Möglichkeit?



  • Ich habe zwar keine Ahnung von WinAPI (da ich seit Jahren nur noch wxWidgets benutze und zufrieden bin) aber kannst du nicht sowas schreiben?

    LRESULT CAbgeleitet::WndProc(HWND Wnd, unsigned int iMessage, WPARAM wParam, LPARAM lParam)
    {
    	switch(iMessage)
    	{
    	case WM_WasWeisIch1:
    		//...
    		return DenPassendenReturnWert;
    	case WM_WasWeisIch2:
    		//...
    		return DenPassendenReturnWert;
    	case WM_WasWeisIch3:
    		//...
    		return DenPassendenReturnWert;
    	}
    
    	return CBasis::WndProc(Wnd, iMessage, wParam, lParam);
    }
    
    LRESULT CBasis::WndProc(HWND Wnd, unsigned int iMessage, WPARAM wParam, LPARAM lParam)
    {
    	switch(iMessage)
    	{
    	case WM_WasWeisIch4:
    		//...
    		return DenPassendenReturnWert;
    	case WM_WasWeisIch5:
    		//...
    		return DenPassendenReturnWert;
    	case WM_WasWeisIch6:
    		//...
    		return DenPassendenReturnWert;
    	}
    
    	return DefWindowProc(Wnd, iMessage, wParam, lParam);
    }
    
    LRESULT CBasis::StatischeWndProc(HWND Wnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
    {
    	if(!reinterpret_cast<CBasis*>(GetWindowLongPtr(Wnd, GWL_USERDATA)))
    	{
    		return DefWindowProc(Wnd, iMessage, wParam, lParam);
    	}
    
    	return reinterpret_cast<CBasis*>(GetWindowLongPtr(Wnd, GWL_USERDATA))->WndProc(Wnd, iMessage,wParam,lParam);
    }
    

    [EDIT]Fehler o_O[/EDIT]



  • Naja, das würde wohl gehen, aber was ist, wenn ich zu einer bereits behandelten Nachricht noch etwas hinzufügen will? In Deinem Fall beendet sich die abgeleitete WndProc sofort, nachdem die Nachricht verarbeitet wurde. Aber die Basisfunktion soll ja in jedem Fall immer mit aufgerufen werden. Nehmen wir an, ich möchte, bevor sich das Fenster schließt, noch eine Message-Box anzeigen. Dann wird WM_CLOSE sowohl in meiner abgeleiteten Funktion behandelt (Message-Box anzeigen), als auch in der Basisfunktion (Schließen durchführen). (Und ich will ja nicht den eigentlichen Schließvorgang komplett neu schreiben müssen, wenn ich zu WM_CLOSE einfach nur eine Aktion hinzufüge.)
    Wie macht das eigentlich die MFC? Da ist es doch auch so, daß man jegliche Nachrichten abfangen kann und danach einfach die Nachrichtenfunktion der Basisklasse aufruft. (Es gibt sogar eine eigene, ableitbare WindowProc.) Und alle Funktionen sind void. Wie erkennt das Programm da, ob es in letzter Instanz noch die DefWindowProc aufrufen muß?



  • Ich wär's mal wieder. Ich hab jetzt ein neues Problem. Doch zuerst eine Frage: Wohin packt man den Aufruf, wenn man einen Button erstellen will? Gleich unter den Aufruf, der das Fenster erstellt hat oder in die WndProc unter WM_CREATE?
    Mein Problem ist folgendes:

    #include <windows.h>
    
    class Window
    {
    public:
    
        Window ()
        {
            //Das gleiche wie immer: Fenster registrieren
            //...
    
            window=CreateWindowEx (...);
    
            SetWindowLong (window, GWL_USERDATA, reinterpret_cast<LONG> (this));
        }
    
        virtual int Start (int showCmd)
        {
            //...
        }
    
    protected:
    
        virtual bool WindowProc (UINT msg, WPARAM wParam, LPARAM lParam)
        {
            switch (msg)
            {
    
            //Das hier funktioniert nicht:
            case WM_CREATE:
                button=CreateWindow ("Button", "Button",
                                     WS_CHILD|WS_VISIBLE, 0, 0, 100, 50,
                                     window, NULL, GetModuleHandle (NULL),
                                     NULL);
                break;
    
            //...
            }
    
            return true;
        }
    
    private:
    
        HWND window;
        /*static*/ HWND button;
    
        static LRESULT CALLBACK StaticWindowProc (HWND window, UINT message, WPARAM wParam, LPARAM lParam)
        {
            //Das hier würde funktionieren:
            /*if (message==WM_CREATE)
                button=CreateWindow ("Button", "Button",
                                     WS_CHILD|WS_VISIBLE, 0, 0, 100, 50,
                                     window, NULL, GetModuleHandle (NULL),
                                     NULL);*/
    
            Window *thisWindow=reinterpret_cast<Window *> (GetWindowLong (window, GWL_USERDATA));
    
            return thisWindow && thisWindow->WindowProc (message, wParam, lParam)?0:
            DefWindowProc (window, message, wParam, lParam);
        }
    };
    
    /*HWND Window::button=NULL;*/
    
    int WINAPI WinMain (HINSTANCE instance, HINSTANCE, LPSTR, int showCmd)
    {
        Window w;
    
        return w.Start (showCmd);
    }
    

    Wenn ich in den Button in der virtuellen WindowProc erstelle, funktioniert das nicht. Gehe ich direkt in die statische Funktion und mache das gleiche, funktioniert es. Wie kommt das? Die statische Funktion ruft doch die dynamische sowieso auf. Wenn ich es dagegen in den Konstruktor schreibe, funktioniert es wieder. Doch ich habe es so gelesen, als müsse es unter WM_CREATE stehen.





  • hola

    haett da zu der klsse mal ne frage:
    muss man nicht, wenn man mehrere von den window-klassen erstellt, jeweils einen neuen ClassName der RegisterClass uebergeben? ansonsten kann er die klasse ja nur einmal registrieren oder?
    wenn man nun die klasse nur einmal registrieren kann, dann kann man auch den Style von einem Window nicht aendern, da sich die aenderung auf alle beziehen wuerde. stimmt das so ?

    Meep Meep



  • Jo clör.

    Naja: Ein Objelt der Klasse entspricht halt einem Fenster.



  • gilt dann der ClassName nur fuer den jeweiligen process oder systemweit ?
    ansonsten kanns doch mal vorkommen, das 2 unterschiedliche programme den gleichen ClassName benutzen.

    Meep Meep



  • Meep Meep schrieb:

    gilt dann der ClassName nur fuer den jeweiligen process oder systemweit ?
    ansonsten kanns doch mal vorkommen, das 2 unterschiedliche programme den gleichen ClassName benutzen.

    Meep Meep

    systemweit<<
    Jops, das kann passieren, es gibt aber auch UnregisterClass(...).



  • CodeFinder schrieb:

    Meep Meep schrieb:

    gilt dann der ClassName nur fuer den jeweiligen process oder systemweit ?
    ansonsten kanns doch mal vorkommen, das 2 unterschiedliche programme den gleichen ClassName benutzen.

    Meep Meep

    systemweit<<
    Jops, das kann passieren, es gibt aber auch UnregisterClass(...).

    naja ob das so gut ist, wenn man eine fremde klasse unregistriert, nur weil sie den gleichen namen hat so wie mein ?
    angenommen jemand verwendet mein programm und hat ein anderes bei sich laufen das den gleichen klassennamen hat. dann kann er mein programm einfach nicht verwenden?

    Meep Meep



  • Schau dir den Ansatz mal an: http://turing.fh-landshut.de/~jamann/IMB/IMB.html

    Uff. Also, ich hatte eigentlich nicht vor, mich durch eine komplette Klassenbibliothek zu kämpfen. Mir ging es einfach nur darum, zu wissen, wieso ich in der eigentlichen statischen WndProc einen Button erzeugen kann, das aber nicht geht, wenn ich es in einer nicht-statischen Funktion mache und diese von der statischen aufrufen lasse.
    Außerdem müßte ich wissen, ob es o.k. ist, in meinem Beispiel den Button im Konstruktor zu erzeugen oder ob das immer in der WndProc bei der Nachricht WM_CREATE geschehen muß.



  • NES-Spieler schrieb:

    Außerdem müßte ich wissen, ob es o.k. ist, in meinem Beispiel den Button im Konstruktor zu erzeugen oder ob das immer in der WndProc bei der Nachricht WM_CREATE geschehen muß.

    Kannst du auch im Konstruktor erstellen. Klar. Wenn zu dem Zeitpunkt ein gültiger Parent-Handle zur Verfügung steht.



  • Wieso wird das dann überhaupt in WM_CREATE erstellt und nicht (bei nicht-objekt-orientierter Programmierung) direkt in der WinMain?


Anmelden zum Antworten