Compartilhar via


Gerenciando o estado do aplicativo

Um procedimento de janela é apenas uma função que é invocada para cada mensagem, portanto, é inerentemente sem estado. Portanto, você precisa de uma maneira de acompanhar o estado do aplicativo de uma chamada de função para a próxima.

A abordagem mais simples é simplesmente colocar tudo em variáveis globais. Isso funciona bem o suficiente para programas pequenos, e muitos dos exemplos de SDK usam essa abordagem. Em um programa grande, no entanto, ele leva a uma proliferação de variáveis globais. Além disso, você pode ter várias janelas, cada uma com seu próprio procedimento de janela. Manter o controle de qual janela deve acessar quais variáveis se tornam confusas e propensas a erros.

A função CreateWindowEx fornece uma maneira de passar qualquer estrutura de dados para uma janela. Quando essa função é chamada, ela envia as duas seguintes mensagens para o procedimento de janela:

Essas mensagens são enviadas na ordem listada. (Essas não são as duas únicas mensagens enviadas durante CreateWindowEx, mas podemos ignorar as outras para essa discussão.)

A mensagem WM_NCCREATE e WM_CREATE são enviadas antes que a janela fique visível. Isso os torna um bom lugar para inicializar sua interface do usuário, por exemplo, para determinar o layout inicial da janela.

O último parâmetro de CreateWindowEx é um ponteiro do tipo void*. Você pode passar qualquer valor de ponteiro desejado nesse parâmetro. Quando o procedimento de janela manipula o WM_NCCREATE ou WM_CREATE mensagem, ele pode extrair esse valor dos dados da mensagem.

Vamos ver como você usaria esse parâmetro para passar dados do aplicativo para sua janela. Primeiro, defina uma classe ou estrutura que contenha informações de estado.

// Define a structure to hold some state information.

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

Ao chamar CreateWindowEx, passe um ponteiro para essa estrutura no parâmetro final 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
    );

Quando você recebe as mensagens WM_NCCREATE e WM_CREATE , o parâmetro lParam de cada mensagem é um ponteiro para uma estrutura CREATESTRUCT . A estrutura CREATETRUCT , por sua vez, contém o ponteiro que você passou para CreateWindowEx.

diagrama que mostra o layout da estrutura createstruct

Veja como extrair o ponteiro para sua estrutura de dados. Primeiro, obtenha a estrutura CREATESTRUCT convertendo o parâmetro lParam .

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

O membro lpCreateParams da estrutura CREATETRUCT é o ponteiro nulo original especificado em CreateWindowEx. Obtenha um ponteiro para sua própria estrutura de dados convertendo lpCreateParams.

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

Em seguida, chame a função SetWindowLongPtr e passe o ponteiro para sua estrutura de dados.

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

A finalidade dessa última chamada de função é armazenar o ponteiro StateInfo nos dados da instância da janela. Depois de fazer isso, você sempre poderá obter o ponteiro de volta da janela chamando GetWindowLongPtr:

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

Cada janela tem seus próprios dados de instância, para que você possa criar várias janelas e dar a cada janela sua própria instância da estrutura de dados. Essa abordagem é especialmente útil se você definir uma classe de janelas e criar mais de uma janela dessa classe, por exemplo, se você criar uma classe de controle personalizada. É conveniente encapsular a chamada GetWindowLongPtr em uma função auxiliar pequena.

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

Agora você pode escrever o procedimento de janela da seguinte maneira.

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

Uma abordagem Object-Oriented

Podemos estender ainda mais essa abordagem. Já definimos uma estrutura de dados para armazenar informações de estado sobre a janela. Faz sentido fornecer essa estrutura de dados com funções membro (métodos) que operam nos dados. Isso naturalmente leva a um design em que a estrutura (ou classe) é responsável por todas as operações na janela. Em seguida, o procedimento de janela se tornaria parte da classe .

Em outras palavras, gostaríamos de ir a partir disso:

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

Para isso:

// pseudocode

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

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

O único problema é como conectar o MyWindow::WindowProc método . A função RegisterClass espera que o procedimento de janela seja um ponteiro de função. Você não pode passar um ponteiro para uma função membro (não estática) neste contexto. No entanto, você pode passar um ponteiro para uma função membro estática e, em seguida, delegar para a função membro. Aqui está um modelo de classe que mostra essa abordagem:

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

A BaseWindow classe é uma classe base abstrata, da qual as classes de janela específicas são derivadas. Por exemplo, aqui está a declaração de uma classe simples derivada de BaseWindow:

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

Para criar a janela, chame 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;
}

O método pure-virtual BaseWindow::HandleMessage é usado para implementar o procedimento de janela. Por exemplo, a implementação a seguir é equivalente ao procedimento de janela mostrado no início do Módulo 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;
}

Observe que o identificador de janela é armazenado em uma variável de membro (m_hwnd), portanto, não precisamos passá-lo como um parâmetro para HandleMessage.

Muitas das estruturas de programação existentes do Windows, como o Microsoft Foundation Classes (MFC) e a ATL (Biblioteca de Modelos Ativos), usam abordagens que são basicamente semelhantes à mostrada aqui. É claro que uma estrutura totalmente generalizada, como o MFC, é mais complexa do que este exemplo relativamente simplista.

Avançar

Módulo 2: Usando COM em seu programa windows

Exemplo de BaseWindow