Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Eine Fensterprozedur ist nur eine Funktion, die für jede Nachricht aufgerufen wird, sodass sie inhärent zustandslos ist. Daher benötigen Sie eine Möglichkeit, den Status Ihrer Anwendung von einem Funktionsaufruf zum nächsten nachzuverfolgen.
Der einfachste Ansatz besteht darin, alles in globale Variablen zu setzen. Dies eignet sich gut genug für kleine Programme, und viele der SDK-Beispiele verwenden diesen Ansatz. In einem großen Programm führt es jedoch zu einer Verbreitung globaler Variablen. Außerdem verfügen Sie möglicherweise über mehrere Fenster, die jeweils über ein eigenes Fensterverfahren verfügen. Den Überblick darüber zu behalten, welches Fenster auf welche Variablen zugreifen darf, wird schnell verwirrend und fehleranfällig.
Die CreateWindowEx-Funktion bietet eine Möglichkeit, jede Datenstruktur an ein Fenster zu übergeben. Wenn diese Funktion aufgerufen wird, sendet sie die folgenden beiden Meldungen an Ihre Fensterprozedur:
Diese Nachrichten werden in der aufgeführten Reihenfolge gesendet. (Dies sind nicht die einzigen zwei Nachrichten, die während CreateWindowEx gesendet wurden, sondern wir können die anderen für diese Diskussion ignorieren.)
Die WM_NCCREATE - und WM_CREATE Nachrichten werden gesendet, bevor das Fenster sichtbar wird. Dies macht sie zu einem guten Ort zum Initialisieren der Benutzeroberfläche – z. B. zum Bestimmen des anfänglichen Layouts des Fensters.
Der letzte Parameter von CreateWindowEx ist ein Zeiger vom Typ void*. Sie können einen beliebigen Zeigerwert in diesem Parameter übergeben. Wenn die Fensterprozedur die WM_NCCREATE oder WM_CREATE Nachricht verarbeitet, kann dieser Wert aus den Nachrichtendaten extrahiert werden.
Sehen wir uns an, wie Sie diesen Parameter verwenden würden, um Anwendungsdaten an Ihr Fenster zu übergeben. Definieren Sie zunächst eine Klasse oder Struktur, die Statusinformationen enthält.
// Define a structure to hold some state information.
struct StateInfo {
// ... (struct members not shown)
};
Wenn Sie CreateWindowEx aufrufen, übergeben Sie einen Zeiger auf diese Struktur im endgültigen void* -Parameter.
StateInfo *pState = new (std::nothrow) StateInfo;
if (pState == NULL)
{
return 0;
}
// Initialize the structure members (not shown).
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
pState // Additional application data
);
Wenn Sie die WM_NCCREATE und WM_CREATE Nachrichten empfangen, ist der lParam-Parameter jeder Nachricht ein Zeiger auf eine CREATESTRUCT-Struktur . Die CREATESTRUCT-Struktur enthält wiederum den Zeiger, den Sie an CreateWindowEx übergeben haben.
Hier erfahren Sie, wie Sie den Zeiger auf Ihre Datenstruktur extrahieren. Rufen Sie zunächst die CREATESTRUCT-Struktur ab, indem Sie den lParam-Parameter umwandeln.
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
Das lpCreateParams-Element der CREATESTRUCT-Struktur ist der ursprüngliche leere Zeiger, den Sie in CreateWindowEx angegeben haben. Rufen Sie einen Zeiger auf Ihre eigene Datenstruktur ab, indem Sie lpCreateParams umwandeln.
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
Rufen Sie als Nächstes die SetWindowLongPtr-Funktion auf, und übergeben Sie den Zeiger an die Datenstruktur.
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
Der Zweck dieses letzten Funktionsaufrufs besteht darin, den StateInfo-Zeiger in den Instanzdaten für das Fenster zu speichern. Sobald Sie dies tun, können Sie den Zeiger immer wieder aus dem Fenster abrufen, indem Sie GetWindowLongPtr aufrufen:
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
Jedes Fenster verfügt über eigene Instanzdaten, sodass Sie mehrere Fenster erstellen und jedem Fenster eine eigene Instanz der Datenstruktur zuordnen können. Dieser Ansatz ist besonders hilfreich, wenn Sie eine Klasse von Fenstern definieren und mehrere Fenster dieser Klasse erstellen, z. B. wenn Sie eine benutzerdefinierte Steuerelementklasse erstellen. Es ist praktisch, den GetWindowLongPtr-Aufruf in eine kleine Hilfsfunktion umzuschließen.
inline StateInfo* GetAppState(HWND hwnd)
{
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
return pState;
}
Jetzt können Sie die Fensterprozedur wie folgt schreiben.
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
StateInfo *pState;
if (uMsg == WM_CREATE)
{
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
}
else
{
pState = GetAppState(hwnd);
}
switch (uMsg)
{
// Remainder of the window procedure not shown ...
}
return TRUE;
}
Ein objektorientierter Ansatz
Wir können diesen Ansatz weiter ausbauen. Wir haben bereits eine Datenstruktur definiert, um Statusinformationen zum Fenster zu enthalten. Es ist sinnvoll, diese Datenstruktur mit Elementfunktionen (Methoden) zu versehen, die auf den Daten operieren. Dies führt natürlich zu einem Entwurf, bei dem die Struktur (oder Klasse) für alle Vorgänge im Fenster verantwortlich ist. Die Fensterprozedur würde dann Teil der Klasse werden.
Mit anderen Worten: Wir möchten von diesem hier ausgehen:
// pseudocode
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
StateInfo *pState;
/* Get pState from the HWND. */
switch (uMsg)
{
case WM_SIZE:
HandleResize(pState, ...);
break;
case WM_PAINT:
HandlePaint(pState, ...);
break;
// And so forth.
}
}
Folgendermaßen:
// pseudocode
LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SIZE:
this->HandleResize(...);
break;
case WM_PAINT:
this->HandlePaint(...);
break;
}
}
Das einzige Problem besteht darin, wie die MyWindow::WindowProc Methode eingebunden wird. Die RegisterClass-Funktion erwartet, dass die Fensterprozedur ein Funktionszeiger ist. Sie können einen Zeiger nicht an eine (nicht statische) Memberfunktion in diesem Kontext übergeben. Sie können jedoch einen Zeiger an eine statische Memberfunktion übergeben und dann an die Memberfunktion delegieren. Dies ist eine Klassenvorlage, die diesen Ansatz zeigt:
template <class DERIVED_TYPE>
class BaseWindow
{
public:
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
DERIVED_TYPE *pThis = NULL;
if (uMsg == WM_NCCREATE)
{
CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam;
pThis = (DERIVED_TYPE*)pCreate->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);
pThis->m_hwnd = hwnd;
}
else
{
pThis = (DERIVED_TYPE*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
}
if (pThis)
{
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
BaseWindow() : m_hwnd(NULL) { }
BOOL Create(
PCWSTR lpWindowName,
DWORD dwStyle,
DWORD dwExStyle = 0,
int x = CW_USEDEFAULT,
int y = CW_USEDEFAULT,
int nWidth = CW_USEDEFAULT,
int nHeight = CW_USEDEFAULT,
HWND hWndParent = 0,
HMENU hMenu = 0
)
{
WNDCLASS wc = {0};
wc.lpfnWndProc = DERIVED_TYPE::WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = ClassName();
RegisterClass(&wc);
m_hwnd = CreateWindowEx(
dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this
);
return (m_hwnd ? TRUE : FALSE);
}
HWND Window() const { return m_hwnd; }
protected:
virtual PCWSTR ClassName() const = 0;
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
HWND m_hwnd;
};
Die BaseWindow Klasse ist eine abstrakte Basisklasse, von der bestimmte Fensterklassen abgeleitet werden. Hier ist beispielsweise die Deklaration einer einfachen Klasse, die von BaseWindow abgeleitet ist:
class MainWindow : public BaseWindow<MainWindow>
{
public:
PCWSTR ClassName() const { return L"Sample Window Class"; }
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};
Um das Fenster zu erstellen, rufen Sie BaseWindow::Create auf:
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int nCmdShow)
{
MainWindow win;
if (!win.Create(L"Learn to Program Windows", WS_OVERLAPPEDWINDOW))
{
return 0;
}
ShowWindow(win.Window(), nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Die rein virtuelle BaseWindow::HandleMessage Methode wird verwendet, um die Fensterprozedur zu implementieren. Die folgende Implementierung entspricht beispielsweise der Fensterprozedur, die zu Beginn von Modul 1 angezeigt wird.
LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(m_hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(m_hwnd, &ps);
}
return 0;
default:
return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
}
return TRUE;
}
Beachten Sie, dass das Fensterhandle in einer Membervariable (m_hwnd) gespeichert ist, sodass wir es nicht als Parameter HandleMessageübergeben müssen.
Viele der vorhandenen Windows Programmierframeworks, z. B. Microsoft Foundation Classes (MFC) und Active Template Library (ATL), verwenden Ansätze, die dem hier gezeigten ähneln. Natürlich ist ein vollständig generalisiertes Framework wie MFC komplexer als dieses relativ vereinfachte Beispiel.
Weiter
Module 2: Verwenden von COM in Ihrem Windows-Programm