Udostępnij przez


Zarządzanie stanem aplikacji

Procedura okna to tylko funkcja, która jest wywoływana dla każdego komunikatu, więc jest z natury bezstanowa. W związku z tym potrzebny jest sposób śledzenia stanu aplikacji z jednego wywołania funkcji do następnego.

Najprostszym podejściem jest po prostu umieszczenie wszystkiego w zmiennych globalnych. Działa to wystarczająco dobrze w przypadku małych programów, a wiele przykładów zestawu SDK używa tego podejścia. Jednak w dużym programie prowadzi to do rozprzestrzeniania się zmiennych globalnych. Ponadto możesz mieć kilka okien, z których każde ma własną procedurę okna. Kontrolowanie, które okno powinno uzyskiwać dostęp do których zmiennych, staje się mylące i podatne na błędy.

Funkcja CreateWindowEx umożliwia przekazywanie dowolnej struktury danych do okna. Po wywołaniu tej funkcji wysyła następujące dwa komunikaty do procedury okna:

Te komunikaty są wysyłane w podanej kolejności. (Nie są to jedyne dwa komunikaty wysyłane podczas CreateWindowEx, ale możemy zignorować inne komunikaty do tej dyskusji).

Komunikat WM_NCCREATE i WM_CREATE są wysyłane, zanim okno stanie się widoczne. To sprawia, że są dobrym miejscem do inicjowania interfejsu użytkownika — na przykład w celu określenia początkowego układu okna.

Ostatni parametr CreateWindowEx jest wskaźnikiem typu void*. Możesz przekazać dowolną wartość wskaźnika w tym parametrze. Gdy procedura okna obsługuje komunikat WM_NCCREATE lub WM_CREATE, może wyodrębnić tę wartość z danych komunikatu.

Zobaczmy, jak za pomocą tego parametru przekazać dane aplikacji do okna. Najpierw zdefiniuj klasę lub strukturę zawierającą informacje o stanie.

// Define a structure to hold some state information.

struct StateInfo {
    // ... (struct members not shown)
};

Podczas wywoływania CreateWindowExprzekaż wskaźnik do tej struktury w ostatnim parametrze void*.

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

Po otrzymaniu komunikatów WM_NCCREATE i WM_CREATE parametr lParam jest wskaźnikiem dla każdego komunikatu do strukturyCREATESTRUCT. Struktura CREATESTRUCT z kolei zawiera wskaźnik przekazany do CreateWindowEx.

diagram przedstawiający układ struktury o nazwie createstruct

Poniżej przedstawiono sposób wyodrębniania wskaźnika do struktury danych. Najpierw pobierz strukturę CREATESTRUCT, odrzucając parametr lParam.

CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);

lpCreateParams element członkowski struktury CREATESTRUCT jest oryginalnym wskaźnikiem void określonym w CreateWindowEx. Pobierz wskaźnik do własnej struktury danych, rzutując lpCreateParams.

pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);

Następnie wywołaj funkcję SetWindowLongPtr i przekaż wskaźnik do struktury danych.

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

Celem tego ostatniego wywołania funkcji jest przechowywanie wskaźnika StateInfo w danych instancji okna. Gdy to zrobisz, zawsze możesz uzyskać wskaźnik z powrotem z okna, wywołując GetWindowLongPtr:

LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);

Każde okno ma własne dane wystąpienia, więc można utworzyć wiele okien i nadać każdemu okno własne wystąpienie struktury danych. Takie podejście jest szczególnie przydatne, jeśli zdefiniujesz klasę okien i utworzysz więcej niż jedno okno tej klasy — na przykład jeśli utworzysz niestandardową klasę sterowania. Wygodne jest zawijanie wywołania GetWindowLongPtr w małej funkcji pomocniczej.

inline StateInfo* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
    return pState;
}

Teraz możesz napisać procedurę okna w następujący sposób.

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

Podejście Object-Oriented

Możemy jeszcze bardziej rozszerzyć to podejście. Zdefiniowaliśmy już strukturę danych do przechowywania informacji o stanie okna. Warto zapewnić tę strukturę danych za pomocą funkcji składowych (metod), które działają na danych. To naturalnie prowadzi do projektu, w którym struktura (lub klasa) jest odpowiedzialna za wszystkie operacje w oknie. Procedura okna stałaby wówczas się częścią klasy.

Innymi słowy, chcielibyśmy przejść od tego:

// 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.
    }
}

Do tego:

// pseudocode

LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            this->HandleResize(...);
            break;

        case WM_PAINT:
            this->HandlePaint(...);
            break;
    }
}

Jedynym problemem jest sposób podłączenia metody MyWindow::WindowProc. Funkcja RegisterClass oczekuje, że procedura okna będzie wskaźnikiem funkcji. W tym kontekście nie można przekazać wskaźnika do funkcji składowej (niestatycznej). Można jednak przekazać wskaźnik do funkcji składowej statycznej, a następnie delegować do funkcji składowej. Oto szablon klasy, który pokazuje to podejście:

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

Klasa BaseWindow jest abstrakcyjną klasą bazową, z której pochodzą określone klasy okien. Na przykład poniżej znajduje się deklaracja prostej klasy pochodzącej z BaseWindow:

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

Aby utworzyć okno, wywołaj 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;
}

Metoda wirtualna abstrakcyjna BaseWindow::HandleMessage służy do implementowania procedury okna. Na przykład następująca implementacja jest równoważna procedurze okna pokazanej na początku modułu 1.

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

Zwróć uwagę, że uchwyt okna jest przechowywany w zmiennej składowej (m_hwnd), więc nie musimy przekazywać go jako parametru do HandleMessage.

Wiele istniejących struktur programowania systemu Windows, takich jak Klasy Microsoft Foundation (MFC) i Biblioteka active template Library (ATL), używają metod, które są zasadniczo podobne do przedstawionego tutaj. Oczywiście w pełni uogólniona struktura, taka jak MFC, jest bardziej złożona niż ten stosunkowo uproszczony przykład.

Następny

Module 2: Używanie COM w programie Windows

BaseWindow Sample