Gestione dello stato dell'applicazione

Una routine finestra è solo una funzione che viene richiamata per ogni messaggio, quindi è intrinsecamente senza stato. Pertanto, è necessario un modo per tenere traccia dello stato dell'applicazione da una chiamata di funzione alla successiva.

L'approccio più semplice consiste semplicemente nell'inserire tutto in variabili globali. Questo approccio funziona abbastanza bene per i programmi di piccole dimensioni e molti esempi dell'SDK usano questo approccio. In un programma di grandi dimensioni, tuttavia, porta a una proliferazione di variabili globali. Inoltre, potresti avere diverse finestre, ognuna con la propria procedura di finestra. Diventa confuso e soggetto a errori tenere traccia di quale finestra debba accedere a quali variabili.

La funzione CreateWindowEx consente di passare qualsiasi struttura di dati a una finestra. Quando questa funzione viene chiamata, invia i due messaggi seguenti alla routine della finestra:

Questi messaggi vengono inviati nell'ordine elencato. Questi non sono gli unici due messaggi inviati durante CreateWindowEx, ma è possibile ignorare gli altri per questa discussione.

I messaggi WM_NCCREATE e WM_CREATE vengono inviati prima che la finestra diventi visibile. Ciò consente di inizializzare l'interfaccia utente, ad esempio per determinare il layout iniziale della finestra.

L'ultimo parametro di CreateWindowEx è un puntatore di tipo void*. È possibile passare qualsiasi valore del puntatore desiderato in questo parametro. Quando la routine della finestra gestisce il WM_NCCREATE o WM_CREATE messaggio, può estrarre questo valore dai dati del messaggio.

Vediamo come usare questo parametro per passare i dati dell'applicazione alla finestra. Prima di tutto, definire una classe o una struttura che contiene informazioni sullo stato.

// Define a structure to hold some state information.

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

Quando si chiama CreateWindowEx, passare un puntatore a questa struttura nel parametro void* finale.

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

Quando si ricevono i messaggi WM_NCCREATE e WM_CREATE , il parametro lParam di ogni messaggio è un puntatore a una struttura CREATESTRUCT . La struttura CREATESTRUCT , a sua volta, contiene il puntatore passato in CreateWindowEx.

diagramma che mostra il layout della struttura createstruct

Ecco come estrarre il puntatore alla struttura dei dati. Prima di tutto, ottenere la struttura CREATESTRUCT eseguendo il cast del parametro lParam .

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

Il membro lpCreateParams della struttura CREATESTRUCT è il puntatore void originale specificato in CreateWindowEx. Ottenere un puntatore alla propria struttura dati eseguendo il cast di lpCreateParams.

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

Chiamare quindi la funzione SetWindowLongPtr e passare il puntatore alla struttura dei dati.

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

Lo scopo di questa ultima chiamata di funzione è archiviare il puntatore StateInfo nei dati dell'istanza per la finestra. Una volta eseguita questa operazione, è sempre possibile recuperare il puntatore dalla finestra chiamando GetWindowLongPtr:

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

Ogni finestra ha i propri dati di istanza, in modo da poter creare più finestre e assegnare a ogni finestra la propria istanza della struttura dei dati. Questo approccio è particolarmente utile se si definisce una classe di finestre e si crea più finestre di tale classe, ad esempio se si crea una classe di controllo personalizzata. È utile racchiudere la chiamata GetWindowLongPtr in una piccola funzione di supporto.

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

È ora possibile scrivere la procedura della finestra come indicato di seguito.

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

Un approccio orientato agli oggetti

È possibile estendere ulteriormente questo approccio. È già stata definita una struttura di dati per contenere informazioni sullo stato relative alla finestra. È opportuno fornire questa struttura di dati con funzioni membro (metodi) che operano sui dati. Questo porta naturalmente a una progettazione in cui la struttura (o classe) è responsabile di tutte le operazioni nella finestra. La procedura della finestra diventerebbe quindi parte della classe.

In altre parole, si vuole passare da questo:

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

In questa:

// pseudocode

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

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

L'unico problema è come associare il MyWindow::WindowProc metodo . La funzione RegisterClass prevede che la routine della finestra sia un puntatore a funzione. Non è possibile passare un puntatore a una funzione membro (non statica) in questo contesto. Tuttavia, è possibile passare un puntatore a una funzione membro statica e quindi delegare alla funzione membro. Ecco un modello di classe che mostra questo approccio:

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

La BaseWindow classe è una classe base astratta, da cui derivano classi di finestre specifiche. Ecco ad esempio la dichiarazione di una classe semplice derivata da BaseWindow:

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

Per creare la finestra, chiamare BaseWindow::Create:

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

Il metodo virtuale puro BaseWindow::HandleMessage viene utilizzato per implementare la procedura della finestra. Ad esempio, l'implementazione seguente equivale alla procedura della finestra illustrata all'inizio del modulo 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;
}

Si noti che l'handle di finestra viene archiviato in una variabile membro (m_hwnd), pertanto non è necessario passarlo come parametro a HandleMessage.

Molti dei framework di programmazione di Windows esistenti, ad esempio Microsoft Foundation Classes (MFC) e Active Template Library (ATL), usano approcci fondamentalmente simili a quelli illustrati di seguito. Naturalmente, un framework completamente generalizzato, ad esempio MFC, è più complesso di questo esempio relativamente semplicistico.

Avanti

Module 2: Uso di COM nel programma Windows

Esempio di BaseWindow