Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Um procedimento de janela é apenas uma função que é invocada para cada mensagem, por isso é inerentemente sem estado. Portanto, você precisa de uma maneira de controlar o estado do seu 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 grande programa, no entanto, 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 torna confuso e propenso a erros.
A funçãoCreateWindowEx fornece uma maneira de passar qualquer estrutura de dados para uma janela. Quando essa função é chamada, ela envia as duas mensagens a seguir para o procedimento da janela:
Essas mensagens são enviadas na ordem listada. (Estas não são as duas únicas mensagens enviadas durante CreateWindowEx, mas podemos ignorar as outras para esta discussão.)
As mensagens WM_NCCREATE e WM_CREATE são enviadas antes que a janela se torne 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 a mensagem WM_NCCREATE ou WM_CREATE, 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)
};
Quando você chama 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 recebes as mensagens WM_NCCREATE e WM_CREATE, o parâmetro lParam de cada mensagem é um ponteiro para a estruturaCREATESTRUCT. A estrutura CREATESTRUCT, por sua vez, contém o ponteiro que você passou para CreateWindowEx.
Veja como extrair o ponteiro para sua estrutura de dados. Primeiro, obtenha a estrutura de CREATESTRUCT lançando o parâmetro lParam.
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
O membro lpCreateParams da estrutura CREATESTRUCT é o ponteiro void original que especificou em CreateWindowEx. Obtenha um ponteiro para a 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 a estrutura de dados.
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
O objetivo desta última chamada de função é armazenar o ponteiro do StateInfo nos dados da instância para a janela. Depois de fazer isso, podes sempre 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 envolver a chamada GetWindowLongPtr do numa pequena função auxiliar.
inline StateInfo* GetAppState(HWND hwnd)
{
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
return pState;
}
Agora você pode escrever seu 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 alargar ainda mais esta abordagem. Já definimos uma estrutura de dados para armazenar informações de estado sobre a janela. Faz sentido oferecer esta estrutura de dados com funções de membro (métodos) que operam sobre os dados. Isso leva naturalmente a um design onde a estrutura (ou classe) é responsável por todas as operações na janela. O procedimento de janela passaria então a fazer parte da classe.
Por outras palavras, gostaríamos de partir disto:
// 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 método MyWindow::WindowProc. A função RegisterClass espera que o procedimento da janela seja um ponteiro para uma função. Não é possível passar um ponteiro para uma função de 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 de 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 classe BaseWindow é uma classe base abstrata, da qual 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 puramente virtual BaseWindow::HandleMessage é usado para implementar o processo de janela. Por exemplo, a implementação seguinte é equivalente ao procedimento de janela apresentado no início de 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 da janela é armazenado em uma variável 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 Microsoft Foundation Classes (MFC) e Ative Template Library (ATL), usam abordagens que são basicamente semelhantes à mostrada aqui. É claro que uma estrutura totalmente generalizada como a MFC é mais complexa do que este exemplo relativamente simplista.
Seguinte
Módulo 2: Usando COM no seu programa Windows