Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
En fönsterprocedur är bara en funktion som anropas för varje meddelande, så den är i sig tillståndslös. Därför behöver du ett sätt att spåra programmets tillstånd från ett funktionsanrop till nästa.
Den enklaste metoden är helt enkelt att placera allt i globala variabler. Detta fungerar tillräckligt bra för små program, och många av SDK-exemplen använder den här metoden. I ett stort program leder det dock till en spridning av globala variabler. Du kan också ha flera fönster, var och en med en egen fönsterprocedur. Att hålla reda på vilket fönster som ska komma åt vilka variabler kan bli förvirrande och felbenäget.
Funktionen CreateWindowEx är ett sätt att skicka datastrukturen till ett fönster. När den här funktionen anropas skickar den följande två meddelanden till fönsterproceduren:
Dessa meddelanden skickas i den ordning som anges. (Det här är inte de enda två meddelandena som skickas under CreateWindowEx, men vi kan ignorera de andra för den här diskussionen.)
Meddelandet WM_NCCREATE och WM_CREATE skickas innan fönstret blir synligt. Det gör dem till en bra plats för att initiera användargränssnittet, till exempel för att fastställa den inledande layouten för fönstret.
Den sista parametern i CreateWindowEx är en pekare av typen void*. Du kan skicka valfritt pekarvärde som du vill i den här parametern. När fönsterproceduren hanterar WM_NCCREATE eller WM_CREATE meddelande kan den extrahera det här värdet från meddelandedata.
Nu ska vi se hur du använder den här parametern för att skicka programdata till ditt fönster. Definiera först en klass eller struktur som innehåller tillståndsinformation.
// Define a structure to hold some state information.
struct StateInfo {
    // ... (struct members not shown)
};
När du anropar CreateWindowExskickar du en pekare till den här strukturen i den sista void* parametern.
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
    );
När du får meddelandena WM_NCCREATE och WM_CREATE, är parametern lParam i varje meddelande en pekare till en CREATESTRUCT- struktur. CREATESTRUCT--struktur innehåller i sin tur pekaren som du skickade till CreateWindowEx.
              
              
            
Så här extraherar du pekaren till din datastruktur. Hämta först CREATESTRUCT- struktur genom att gjuta parametern lParam.
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
lpCreateParams medlem i CREATESTRUCT- struktur är den ursprungliga tomrumspekaren som du angav i CreateWindowEx. Hämta en pekare till din egen datastruktur genom att lpCreateParams.
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
Anropa sedan funktionen SetWindowLongPtr och skicka pekaren till datastrukturen.
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
Syftet med det sista funktionsanropet är att lagra StateInfo– pekaren i instansdata för fönstret. När du gör det kan du alltid hämta pekaren från fönstret genom att anropa GetWindowLongPtr:
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
Varje fönster har egna instansdata, så du kan skapa flera fönster och ge varje fönster en egen instans av datastrukturen. Den här metoden är särskilt användbar om du definierar en klass med fönster och skapar fler än ett fönster i den klassen, till exempel om du skapar en anpassad kontrollklass. Det är praktiskt att omsluta GetWindowLongPtr- anrop i en liten hjälpfunktion.
inline StateInfo* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
    return pState;
}
Nu kan du skriva fönsterproceduren på följande sätt.
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;
}
En Object-Oriented metod
Vi kan utöka den här metoden ytterligare. Vi har redan definierat en datastruktur för att lagra tillståndsinformation om fönstret. Det är klokt att tillhandahålla den här datastrukturen med medlemsfunktioner (metoder) som bearbetar data. Detta leder naturligtvis till en design där strukturen (eller klassen) ansvarar för alla åtgärder i fönstret. Fönsterproceduren skulle sedan bli en del av klassen.
Vi vill med andra ord gå från detta:
// 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.
    }
}
Så här:
// pseudocode
LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            this->HandleResize(...);
            break;
        case WM_PAINT:
            this->HandlePaint(...);
            break;
    }
}
Det enda problemet är hur du ansluter MyWindow::WindowProc-metoden. Funktionen RegisterClass förväntar sig att fönsterproceduren ska vara en funktionspekare. Du kan inte skicka en pekare till en (icke-statisk) medlemsfunktion i den här kontexten. Du kan dock skicka en pekare till en statisk medlemsfunktion och sedan delegera till medlemsfunktionen. Här är en klassmall som visar den här metoden:
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;
};
Klassen BaseWindow är en abstrakt basklass, från vilken specifika fönsterklasser härleds. Här är till exempel deklarationen av en enkel klass som härleds från BaseWindow:
class MainWindow : public BaseWindow<MainWindow>
{
public:
    PCWSTR  ClassName() const { return L"Sample Window Class"; }
    LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};
Om du vill skapa fönstret anropar du 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;
}
Metoden pure-virtual BaseWindow::HandleMessage används för att implementera fönsterproceduren. Följande implementering motsvarar till exempel den fönsterprocedur som visas i början av modul 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;
}
Observera att fönsterhandtaget lagras i en medlemsvariabel (m_hwnd), så vi behöver inte skicka det som en parameter till HandleMessage.
Många av de befintliga Windows-programmeringsramverken, till exempel Microsoft Foundation-klasser (MFC) och Active Template Library (ATL), använder metoder som i princip liknar det som visas här. Naturligtvis är ett fullständigt generaliserat ramverk som MFC mer komplext än detta relativt förenklade exempel.
Nästa
Modul 2: Använda COM i Windows-programmet