Managing Application State (Verwalten eines Anwendungszustands)
Eine Fensterprozedur ist nur eine Funktion, die für jede Nachricht aufgerufen wird, sodass sie grundsätzlich zustandslos ist. Aus diesem Grund benötigen Sie eine Möglichkeit, den Zustand Ihrer Anwendung von einem Funktionsaufruf zum nächsten nachverfolgungen.
Der einfachste Ansatz besteht einfach in globalen Variablen. Dies funktioniert gut genug für kleine Programme, und viele der SDK-Beispiele verwenden diesen Ansatz. In einem großen Programm führt dies jedoch zu einer Zunahme globaler Variablen. Darüber hinaus verfügen Sie möglicherweise über mehrere Fenster, die jeweils über eine eigene Fensterprozedur verfügen. Nachverfolgen, welches Fenster auf welche Variablen zugreifen sollte, wird verwirrend und fehleranfällig.
Die CreateWindowEx-Funktion bietet eine Möglichkeit, eine beliebige Datenstruktur an ein Fenster zu übergeben. Wenn diese Funktion aufgerufen wird, sendet sie die folgenden zwei Meldungen an Ihre Fensterprozedur:
Diese Nachrichten werden in der angegebenen Reihenfolge gesendet. (Dies sind nicht die einzigen beiden Nachrichten, die während CreateWindowExgesendet werden, aber wir können die anderen für diese Diskussion ignorieren.)
Die WM _ NCCREATE- und WM _ CREATE-Nachricht werden gesendet, bevor das Fenster sichtbar wird. Dies macht sie zu einem guten Ort, um Ihre Benutzeroberfläche zu initialisieren, z. B. um das anfängliche Layout des Fensters zu bestimmen.
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 Meldung _ WM _ NCCREATE * oder WM _ CREATE verarbeitet, kann sie diesen Wert aus den Nachrichtendaten extrahieren.
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 Zustandsinformationen 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.

So extrahieren Sie den Zeiger auf Ihre Datenstruktur. Erhalten Sie zunächst die CREATESTRUCT-Struktur, indem Sie den lParam-Parameter umwandlungen.
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
Das lpCreateParams-Member der CREATESTRUCT-Struktur ist der ursprüngliche void-Zeiger, den Sie in CreateWindowEx angegeben haben. Erhalten Sie einen Zeiger auf Ihre eigene Datenstruktur, indem Sie lpCreateParams umwandlungen.
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
Rufen Sie als Nächstes die Funktion SetWindowLongPtr auf, und übergeben Sie den Zeiger auf Ihre Datenstruktur.
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
Der Zweck dieses letzten Funktionsaufrufs besteht im Speichern des StateInfo-Zeigers in den Instanzdaten für das Fenster. Sobald Sie dies tun, können Sie den Zeiger immer wieder aus dem Fenster erhalten, 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 geben können. Dieser Ansatz ist besonders nützlich, 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 einer kleinen Hilfsfunktion zu umschließen.
inline StateInfo* GetAppState(HWND hwnd)
{
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
return pState;
}
Nun können Sie ihre 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 Object-Oriented Ansatz
Wir können diesen Ansatz weiter erweitern. Wir haben bereits eine Datenstruktur definiert, die Zustandsinformationen zum Fenster enthält. Es ist sinnvoll, diese Datenstruktur mit Memberfunktionen (Methoden) zur Verfügung zu stellen, die mit den Daten arbeiten. 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.
Anders ausgedrückt: Wir möchten von hier aus gehen:
// 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 in der Vorgehensweise beim Hooking der MyWindow::WindowProc -Methode. Die RegisterClass-Funktion erwartet, dass die Fensterprozedur ein Funktionszeiger ist. Sie können in diesem Kontext keinen Zeiger auf eine (nicht statische) Memberfunktion übergeben. Sie können jedoch einen Zeiger auf eine statische Memberfunktion übergeben und dann an die Memberfunktion delegieren. Dies ist eine Klassenvorlage, die diesen Ansatz veranschaulicht:
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 abgeleitet BaseWindow ist:
class MainWindow : public BaseWindow<MainWindow>
{
public:
PCWSTR ClassName() const { return L"Sample Window Class"; }
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};
Rufen Sie auf, um das Fenster zu BaseWindow::Create erstellen:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, 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. Beispielsweise entspricht die folgende Implementierung der Fensterprozedur, die am Anfang von Modul 1 gezeigt 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 Fensterhand handle in einer Membervariablen (m _ hwnd) gespeichert ist, sodass wir es nicht als Parameter an übergeben HandleMessage müssen.
Viele der vorhandenen Windows-Programmierframeworks wie Microsoft Foundation Classes (MFC) und Active Template Library (ATL) verwenden Ansätze, die im Grunde dem hier gezeigten ähneln. Natürlich ist ein vollständig generalisiertes Framework wie MFC komplexer als dieses relativ einfache Beispiel.
Nächste
Modul 2: Verwenden von COM in Ihrem Windows-Programm