WNDPROC als Classmember
-
Hey,
ich habe mich jetzt sehr lange im Internet rumgequält eine Möglichkeit zu finden, die WndProc innerhalb einer Klasse haben zu können :
"cwindow.cpp"
CWindow::CWindow(wstring name, wstring caption, CPoint<int> position, int width, int height) { //win.wc.style = CS_HREDRAW | CS_VREDRAW; win.wc.lpfnWndProc = WndProc; /*win.wc.cbClsExtra = 0; win.wc.cbWndExtra = 0; win.wc.hCursor = LoadCursor(NULL,IDC_ARROW); win.wc.hIcon = LoadIcon(NULL,IDI_APPLICATION); win.wc.hbrBackground = (HBRUSH)(WHITE_BRUSH); win.wc.lpszClassName = name.c_str(); win.wc.lpszMenuName = NULL;*/ }
"cwindow.h"
class CWindow { private: static int c; int id; win32str win; LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); public: CWindow(); CWindow(wstring name, wstring caption, CPoint<int> position, int width, int height); CProperty<CWindow, wstring> Caption; CProperty<CWindow, wstring> Name; CPoint<CProperty<CWindow, int> > Position; };
C:\Users\Tobias\Documents\QT\CWinClass\Main\cwindow.cpp:30: Fehler:cannot convert 'CWindow::WndProc' from type 'LRESULT (CWindow::)(HWND, UINT, WPARAM, LPARAM) {aka long int (CWindow::)(HWND__*, unsigned int, unsigned int, long int)}' to type 'WNDPROC {aka long int (__attribute__((__stdcall__)) *)(HWND__*, unsigned int, unsigned int, long int)}' win.wc.lpfnWndProc = WndProc; ^
Das Problem ist hierbei im Internet ja schon mehr als bekannt, ich verstehe nur die Lösungsvorschläge nicht wirklich.
Zum einen heißt es, ich soll die WndProc meiner Klasse doch static machen. Das Problem ist aber, dass ich ja durchaus mehrere Instanzen der Klasse (Fenster) haben möchte. Das soll zwar auch über diese Methode möglich sein, nur verstehe ich das garnicht.
Die zweite Methode, die ich gefunden hab war mit einem inline Assembler, was bei mir leider nichteinmal Funktioniert hat.
Es tut mir leid euch mit diesem alten Lappen von Problem beschäftigen zu müssen, aber ich scheitere da leider gerade dran. Wäre halt schön, wenn ihr mir da helfen würdet
Vielen Dank schonmal!
-
Das Problem an deiner WndProc als Klassenmember ist, dass sie, wenn sie nicht als static deklariert ist, einen zusätzlichen unsichtbaren Parameter bekommt, nämlich den auf das aktuelle this. Deshalb bleibt dir nur, die WndProc als Funktion (=außerhalb der Klasse) oder als static zu definieren:
class CWindow { //... static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); }
Das einzie Problem dabei: diese eine WndProc teilen sich alle Instanzen. Also musst du den this-Zeiger woanders herzaubern. Ich habe dafür das GWL_USERDATA hergenommen. Beim RegisterWndClass() reserviere ich im cbWndExtra dafür entsprechenden Platz. In der WM_CREATE erzeuge ich dann meine CWindow-Instanz, schreibe den Zeiger per SetWindowLongPtr() und muss ihn dann in den nächsten Nachrichten nur noch per GetWindowLongPtr() auslesen. Thats it.
Der Code dafür ist recht einfach:
LRESULT CALLBACK CWindow::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { // Verwaltungsklasse holen und sicherstellen, dass sie auch da ist (!) CWindow*pWnd = (CWindow*) GetWindowLongPtr (hwnd, GWL_USERDATA); if (!pWnd && message != WM_CREATE && message != WM_DESTROY) return DefWindowProc (hwnd, message, wParam, lParam); // dann ganz normal weiter switch (message) { case WM_CREATE: // Verwaltungsdaten initialisieren try { pWnd = NULL; pWnd = new CWindow(); } catch (std::bad_alloc) { return -1; // Fenster kann mangels Verwaltungsdaten nicht geöffnet werden } SetWindowLongPtrA (hwnd, GWL_USERDATA, (LONG_PTR)pWnd); break; // usw.... } }
Die einzige Besonderheit ist, dass du die Member nicht direkt als z.B. win, sondern per pWnd->win ansprechen musst.
-
hey,
vielen Dank für deine Antwort! Muss ich dann in dem cbWndExtra den Speicher für sizeof(CWindow) oder sizeof(*CWindow) setzen?
Und noch eine Frage, die eher an meinen mangelnden Kenntnissen liegt: Ich erstelle ja schon bereits in meinem Programm (WinMain) eine Instanz der Klasse CWindow über:
CWindow MeinFenster();
Die CreateWindowEx() bzw RegisterClass() wird bei mir erst danach durch
MeinFenster.Create();
aufgerufen. Nun zu dem Teil, den ich nicht verstehe:
Jedes Mal, wenn die Message WM_CREATE aufgerufen wird (also nach bzw in MeinFenster.Create() ) erstellst du ja mit pWnd = new CWindow(); einen Pointer auf eine neue Instanz. Welchen Bezug hat das denn dann auf meiner erzeugte Instanz?
-
sizeof(*CWindow) reicht - du willst ja nur einen Zeiger und nicht gleich die ganze Klasse übergeben
Mein Beispiel zeigt, wie man loslegen kann, wenn das Fenster (z.B. als selbsterstelltes Dialogelement) ganz normal mit CreateWindowEx() erzeugt werden soll und sich die Klasse selbst erzeugt. Also erst das Fenster und dann die Klasse.
Du willst jetzt andersrum vorgehen: erst ist die Klasse da, und dann erst das Fenster. Also musst du bereits beim Öffnen des Fensters den Zeiger auf die Klasse mitgeben. Dafür bietet sich der letzte Parameter von CreateWindowEx() an, lpParam. Dort hinein legst du den Zeiger auf dein CWindow.
Und diesen Zeiger findest du dann in der WM_CREATE-Nachricht im lParam (der dann einen Zeiger auf eine CREATESTRUCT darstellt) im Element lpCreateParams. Sodass dann der Aufruf in der WM_CREATE etwa lauten müsste:
SetWindowLongPtrA (hwnd, GWL_USERDATA, (LONG_PTR)((CREATESTRUCT*)lParam)->lpCreateParams);
-
Vielen Dank!
Für alle, die es interessiert:
//STATIC WndProc - sending all messages to the CWindow-class they belong to LRESULT CWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // if (message == WM_NCCREATE) { SetWindowLongPtr(hWnd, GWL_USERDATA, (LONG_PTR) ((CREATESTRUCT*)lParam)->lpCreateParams); } else { CWindow *pCWindow; pCWindow = (CWindow*) GetWindowLongPtr(hWnd,GWL_USERDATA); //pCWindow->PostMessage(hWnd, message, wParam, lParam); } return DefWindowProc(hWnd,message,wParam,lParam); }
-
Passt ja!
Noch ein ergänzender Tipp, auch wenn nur du selbst deinen Code nutzt: stelle sicher, dass lpCreateParams wirklich einen gültigen Zeiger auf dein CWindow enthält oder zumindest nicht NULL ist. Das hilft zumindest für die Fälle, in denen du nachts um zwei direkt CreateWindow() aufrufst, statt eine Klasse zu erzeugen. Und dann den Folgetag mit dem Debugger verbringst
-
Danke für den Tipp, aber das kann nicht passieren, da sich die Klasse um alles kümmert (auch das Erstellen des Fensters) und garkeine Schnittstelle für "eigene Erstellen" beinhaltet