Managing Application State (Verwalten eines Anwendungszustands)

Eine Fensterprozedur ist nur eine Funktion, die für jede Nachricht aufgerufen wird, sodass sie von Natur aus 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, einfach alles in globale Variablen zu setzen. Dies funktioniert 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 Zunahme von globalen Variablen. Außerdem können Sie über mehrere Fenster verfügen, von denen jedes über eine eigene Fensterprozedur verfügt. Das Nachverfolgen, welches Fenster auf welche Variablen zugreifen soll, wird verwirrend und fehleranfällig.

Die CreateWindowEx-Funktion bietet eine Möglichkeit, beliebige Datenstrukturen an ein Fenster zu übergeben. Wenn diese Funktion aufgerufen wird, sendet sie die folgenden beiden Meldungen an Ihre Fensterprozedur:

Diese Nachrichten werden in der angegebenen Reihenfolge gesendet. (Dies sind nicht die einzigen zwei Nachrichten, die während CreateWindowEx gesendet 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 WM_NCCREATE oder WM_CREATE Nachricht 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 letzten 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 erhalten, 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.

Diagramm, das das Layout der CreateStruct-Struktur zeigt

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);

Der lpCreateParams-Member der CREATESTRUCT-Struktur ist der ursprüngliche void-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 auf Ihre Datenstruktur.

SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);

Der Zweck dieses letzten Funktionsaufrufs besteht darin, den StateInfo-Zeiger in den instance Daten für das Fenster zu speichern. Anschließend 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 instance Daten, sodass Sie mehrere Fenster erstellen und jedem Fenster einen eigenen instance 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 eine kleine Hilfsfunktion einzuschließ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 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 ausbauen. Wir haben bereits eine Datenstruktur definiert, die Zustandsinformationen zum Fenster enthält. Es ist sinnvoll, diese Datenstruktur mit Memberfunktionen (Methoden) bereitzustellen, 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 wird dann Teil der -Klasse.

Mit anderen Worten, wir möchten davon 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 in diesem Kontext keinen Zeiger an eine (nicht statische) Memberfunktion übergeben. Sie können jedoch einen Zeiger auf eine statische Memberfunktion übergeben und dann an die Memberfunktion delegieren. Im Folgenden finden Sie 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 BaseWindowabgeleitet wird:

class MainWindow : public BaseWindow<MainWindow>
{
public:
    PCWSTR  ClassName() const { return L"Sample Window Class"; }
    LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};

Rufen Sie zum Erstellen des Fensters auf BaseWindow::Create:

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. Die folgende Implementierung entspricht beispielsweise der Fensterprozedur, die am Anfang 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 Membervariablen (m_hwnd) gespeichert ist, sodass wir es nicht als Parameter an 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 ansatzweise ähneln. Natürlich ist ein vollständig generalisiertes Framework wie MFC komplexer als dieses relativ vereinfachte Beispiel.

Nächste

Modul 2: Verwenden von COM in Ihrem Windows-Programm

BaseWindow-Beispiel