Gestion de l’état de l’application

Une procédure de fenêtre n’est qu’une fonction appelée pour chaque message. Elle est donc intrinsèquement sans état. Par conséquent, vous avez besoin d’un moyen de suivre l’état de votre application d’un appel de fonction à l’autre.

L’approche la plus simple consiste simplement à tout placer dans des variables globales. Cela fonctionne suffisamment bien pour les petits programmes, et de nombreux exemples de SDK utilisent cette approche. Toutefois, dans un programme de grande envergure, cela entraîne une prolifération de variables globales. En outre, vous pouvez avoir plusieurs fenêtres, chacune avec sa propre procédure de fenêtre. Effectuer le suivi de la fenêtre qui doit accéder aux variables qui deviennent confuses et sujettes aux erreurs.

La fonction CreateWindowEx permet de transmettre une structure de données à une fenêtre. Lorsque cette fonction est appelée, elle envoie les deux messages suivants à votre procédure de fenêtre :

Ces messages sont envoyés dans l’ordre indiqué. (Ce ne sont pas les deux seuls messages envoyés pendant CreateWindowEx, mais nous pouvons ignorer les autres pour cette discussion.)

Le message WM_NCCREATE et WM_CREATE sont envoyés avant que la fenêtre ne devienne visible. Cela en fait un bon endroit pour initialiser votre interface utilisateur, par exemple pour déterminer la disposition initiale de la fenêtre.

Le dernier paramètre de CreateWindowEx est un pointeur de type void*. Vous pouvez passer n’importe quelle valeur de pointeur souhaitée dans ce paramètre. Lorsque la procédure de fenêtre gère le message WM_NCCREATE ou WM_CREATE , elle peut extraire cette valeur des données du message.

Voyons comment utiliser ce paramètre pour transmettre des données d’application à votre fenêtre. Tout d’abord, définissez une classe ou une structure qui contient des informations d’état.

// Define a structure to hold some state information.

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

Lorsque vous appelez CreateWindowEx, passez un pointeur vers cette structure dans le paramètre void* final.

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

Lorsque vous recevez les messages WM_NCCREATE et WM_CREATE , le paramètre lParam de chaque message est un pointeur vers une structure CREATESTRUCT . La structure CREATESTRUCT , à son tour, contient le pointeur que vous avez passé dans CreateWindowEx.

diagramme montrant la disposition de la structure createstruct

Voici comment extraire le pointeur vers votre structure de données. Tout d’abord, obtenez la structure CREATESTRUCT en castant le paramètre lParam .

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

Le membre lpCreateParams de la structure CREATESTRUCT est le pointeur void d’origine que vous avez spécifié dans CreateWindowEx. Obtenez un pointeur vers votre propre structure de données en castant lpCreateParams.

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

Ensuite, appelez la fonction SetWindowLongPtr et passez le pointeur à votre structure de données.

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

L’objectif de ce dernier appel de fonction est de stocker le pointeur StateInfo dans les données instance de la fenêtre. Une fois cette opération effectuée, vous pouvez toujours récupérer le pointeur à partir de la fenêtre en appelant GetWindowLongPtr :

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

Chaque fenêtre a ses propres données instance. Vous pouvez donc créer plusieurs fenêtres et donner à chaque fenêtre sa propre instance de la structure de données. Cette approche est particulièrement utile si vous définissez une classe de fenêtres et créez plusieurs fenêtres de cette classe, par exemple si vous créez une classe de contrôle personnalisée. Il est pratique d’encapsuler l’appel GetWindowLongPtr dans une petite fonction d’assistance.

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

Vous pouvez maintenant écrire votre procédure de fenêtre comme suit.

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

Une approche Object-Oriented

Nous pouvons étendre cette approche. Nous avons déjà défini une structure de données pour contenir des informations d’état sur la fenêtre. Il est logique de fournir à cette structure de données des fonctions membres (méthodes) qui opèrent sur les données. Cela conduit naturellement à une conception où la structure (ou la classe) est responsable de toutes les opérations sur la fenêtre. La procédure de fenêtre fait alors partie de la classe .

En d’autres termes, nous aimerions partir de ceci :

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

Par ceci :

// pseudocode

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

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

Le seul problème est de savoir comment raccorder la MyWindow::WindowProc méthode. La fonction RegisterClass s’attend à ce que la procédure de fenêtre soit un pointeur de fonction. Vous ne pouvez pas passer de pointeur vers une fonction membre (non statique) dans ce contexte. Toutefois, vous pouvez passer un pointeur à une fonction membre statique , puis déléguer à la fonction membre. Voici un modèle de classe qui illustre cette approche :

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 est une classe de base abstraite, à partir de laquelle des classes de fenêtre spécifiques sont dérivées. Par exemple, voici la déclaration d’une classe simple dérivée de BaseWindow:

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

Pour créer la fenêtre, appelez 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;
}

La méthode pure-virtuelle BaseWindow::HandleMessage est utilisée pour implémenter la procédure de fenêtre. Par exemple, l’implémentation suivante équivaut à la procédure de fenêtre affichée au début du module 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;
}

Notez que le handle de fenêtre est stocké dans une variable membre (m_hwnd). Nous n’avons donc pas besoin de le passer en tant que paramètre à HandleMessage.

La plupart des frameworks de programmation Windows existants, tels que MFC (Microsoft Foundation Classes) et ATL (Active Template Library), utilisent des approches essentiellement similaires à celles présentées ici. Bien sûr, une infrastructure entièrement généralisée telle que MFC est plus complexe que cet exemple relativement simpliste.

Suivant

Module 2 : Utilisation de COM dans votre programme Windows

BaseWindow, exemple