Delen via


Toepassingsstatus beheren

Een vensterprocedure is slechts een functie die wordt aangeroepen voor elk bericht, dus het is inherent staatloos. Daarom hebt u een manier nodig om de status van uw toepassing bij te houden van de ene functie-aanroep naar de volgende.

De eenvoudigste methode is om alles in globale variabelen te plaatsen. Dit werkt goed genoeg voor kleine programma's en veel van de SDK-voorbeelden gebruiken deze benadering. In een groot programma leidt het echter tot een verspreiding van wereldwijde variabelen. Het is ook mogelijk dat u meerdere vensters hebt, elk met een eigen vensterprocedure. Het bijhouden van welk venster toegang moet krijgen tot welke variabelen kan verwarrend en foutgevoelig zijn.

De functie CreateWindowEx biedt een manier om een gegevensstructuur door te geven aan een venster. Wanneer deze functie wordt aangeroepen, worden de volgende twee berichten naar uw vensterprocedure verzonden:

Deze berichten worden verzonden in de vermelde volgorde. (Dit zijn niet de enige twee berichten die tijdens CreateWindowEx-worden verzonden, maar we kunnen de anderen voor deze discussie negeren.)

Het bericht WM_NCCREATE en WM_CREATE worden verzonden voordat het venster zichtbaar wordt. Dit maakt ze een goede plek om uw gebruikersinterface te initialiseren, bijvoorbeeld om de initiële indeling van het venster te bepalen.

De laatste parameter van CreateWindowEx is een aanwijzer van het type void*. U kunt elke gewenste aanwijzer opgeven in deze parameter. Wanneer de vensterprocedure het bericht WM_NCCREATE of WM_CREATE verwerkt, kan deze waarde worden geëxtraheerd uit de berichtgegevens.

Laten we eens kijken hoe u deze parameter zou gebruiken om toepassingsgegevens door te geven aan uw venster. Definieer eerst een klasse of structuur die statusinformatie bevat.

// Define a structure to hold some state information.

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

Wanneer u CreateWindowExaanroept, geeft u een aanwijzer door naar deze structuur in de laatste 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
    );

Wanneer u de WM_NCCREATE- en WM_CREATE-berichten ontvangt, is de parameter lParam van elk bericht een aanwijzer naar een CREATESTRUCT- structuur. De CREATESTRUCT structuur bevat de pointer die u hebt doorgegeven aan CreateWindowEx.

diagram met de indeling van de createstruct-structuur

Hier ziet u hoe u de aanwijzer naar uw gegevensstructuur extraheert. Haal eerst de CREATESTRUCT structuur op door de parameter lParam te casten.

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

Het lpCreateParams lid van de CREATESTRUCT structuur is de oorspronkelijke void-pointer die u hebt opgegeven in CreateWindowEx. Verkrijg een aanwijzer naar de eigen gegevensstructuur door lpCreateParamste casten.

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

Roep vervolgens de SetWindowLongPtr-functie aan en geef de aanwijzer door aan uw gegevensstructuur.

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

Het doel van deze laatste functie-aanroep is het opslaan van de StateInfo aanwijzer in de instantiegegevens voor het venster. Zodra u dit hebt uitgevoerd, kunt u de aanwijzer altijd terughalen vanuit het venster door GetWindowLongPtraan te roepen:

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

Elk venster heeft zijn eigen exemplaargegevens, zodat u meerdere vensters kunt maken en elk venster een eigen exemplaar van de gegevensstructuur kunt geven. Deze methode is vooral handig als u een klasse van vensters definieert en meer dan één venster van die klasse maakt, bijvoorbeeld als u een aangepaste besturingselementklasse maakt. Het is handig om de GetWindowLongPtr aanroep in een kleine helperfunctie te verpakken.

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

U kunt nu als volgt uw vensterprocedure schrijven.

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

Een Object-Oriented-benadering

We kunnen deze aanpak verder uitbreiden. We hebben al een gegevensstructuur gedefinieerd voor statusinformatie over het venster. Het is zinvol om deze gegevensstructuur te voorzien van lidfuncties (methoden) die op de gegevens werken. Dit leidt natuurlijk tot een ontwerp waarbij de structuur (of klasse) verantwoordelijk is voor alle bewerkingen in het venster. De vensterprocedure zou dan onderdeel van de klasse worden.

Met andere woorden, we willen van dit:

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

Ga als volgt te werk:

// pseudocode

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

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

Het enige probleem is hoe je de MyWindow::WindowProc-methode kunt koppelen. De functie RegisterClass verwacht dat de vensterprocedure een functiepointer is. U kunt een aanwijzer niet doorgeven aan een (niet-statische) lidfunctie in deze context. U kunt echter een aanwijzer doorgeven aan een statische lidfunctie en vervolgens delegeren aan de lidfunctie. Hier volgt een klassesjabloon waarin deze benadering wordt weergegeven:

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

De BaseWindow-klasse is een abstracte basisklasse, waaruit specifieke vensterklassen worden afgeleid. Hier volgt bijvoorbeeld de declaratie van een eenvoudige klasse die is afgeleid van BaseWindow:

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

Als u het venster wilt maken, roept u BaseWindow::Createaan:

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

De pure virtuele BaseWindow::HandleMessage methode wordt gebruikt om de vensterprocedure te implementeren. De volgende implementatie is bijvoorbeeld gelijk aan de vensterprocedure die wordt weergegeven aan het begin van 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;
}

U ziet dat de venstergreep wordt opgeslagen in een lidvariabele (m_hwnd), zodat we deze niet als parameter hoeven door te geven aan HandleMessage.

Veel van de bestaande Windows-programmeerframeworks, zoals Microsoft Foundation Classes (MFC) en Active Template Library (ATL), gebruiken benaderingen die in principe vergelijkbaar zijn met de methoden die hier worden weergegeven. Natuurlijk is een volledig gegeneraliseerde framework zoals MFC complexer dan dit relatief eenvoudige voorbeeld.

Volgende

Module 2: COM gebruiken in uw Windows-programma

BaseWindow-voorbeeld