Interação entre Janelas Principais e Filhas
No ecossistema da API Win32, a criação de uma interface gráfica robusta depende fundamentalmente da relação entre janelas principais (parent) e janelas filhas (child). O ciclo de vida de qualquer janela inicia-se com o registro de sua classe e a subsequente instanciação.
Ao configurar a estrutura WNDCLASS para uma janela filha, algumas alterações específicas são necessárias em relação à janela principal:
- lpfnWndProc: Deve apontar para uma função de procedimento de janela dedicada à janela filha.
- cbWndExtra: Frequentemente configurado com
sizeof(LONG_PTR)ou 4/8 bytes para alocar espaço extra na estrutura interna do Windows. Isso permite armazenar dados específicos de cada instância, recuperáveis viaGetWindowLongPtr. - hIcon: Definido como
NULL, visto que janelas filhas (como botões ou controles internos) não possuem representações na barra de tarefas ou em caixas de diálogo de sistema.
Comparação de Parâmetros na Criação
A função CreateWindow exige parâmetros distintos dependendo da hierarquia da janela:
| Parâmetro | Janela Principal | Janela Filha |
|---|---|---|
| Classe da Janela | Nome da classe principal | Nome da classe filha |
| Título | Texto da barra de título | NULL (geralmente irrelevante) |
| Estilo | WS_OVERLAPPEDWINDOW |
WS_CHILDWINDOW | WS_VISIBLE |
| Posição (X, Y) | CW_USEDEFAULT |
Coordenadas relativas ao pai |
| Dimensões (W, H) | CW_USEDEFAULT |
Dimensões específicas em pixels |
| Handle do Pai | NULL |
Handle da janela principal (hWnd) |
| Menu / ID | Handle do Menu | Identificador inteiro (HMENU) do controle |
| Instância | hInstance |
(HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE) |
A inclusão do estilo WS_VISIBLE dispensa a chamada explícita a ShowWindow. O identificador (ID) passado no parâmetro de menu é crucial, pois atua como a referência primária para comunicação via WM_COMMAND.
Comunicação Bidirecional
A manipulação direta de janelas filhas pela janela pai é feita através de seus handles ou IDs:
// Movendo um controle específico
MoveWindow(hChildCtrl, x, y, width, height, TRUE);
// Recuperando o handle através do ID
HWND hTarget = GetDlgItem(hWndParent, ID_CONTROL);
SetFocus(hTarget);
// Obtendo o ID a partir do handle
int ctrlId = GetDlgCtrlID(hChildCtrl);
Inversamente, janelas filhas podem consultar ou notificar seus pais:
// Obtendo o handle da janela pai
HWND hParent = GetParent(hWndChild);
// Delegando o processamento de uma mensagem para o pai
SendMessage(hParent, WM_USER_CUSTOM, wParam, lParam);
- Controles de Botão (Button Class)
Os botões notificam a janela pai enviando mensagens WM_COMMAND e, ocasionalmente, WM_CTLCOLORBTN. A estrutura do wParam contém o ID do controle na palavra de baixa ordem (LOWORD) e o código de notificação na palavra de alta ordem (HIWORD).
Códigos de Notificação e Mensagens
As notificações mais comuns incluem BN_CLICKED (0) e BN_DOUBLECLICKED (5). Mensagens de foco como BN_SETFOCUS exigem que o botão possua o estilo BS_NOTIFY.
O pai pode alterar o estado do botão enviando mensagens prefixadas com BM_:
BM_SETCHECK: Alterna o estado de caixas de seleção ou botões de opção.BM_GETSTATE: Recupera o estado de destaque (highlight) do botão.BM_SETSTYLE: Modifica dinamicamente o estilo visual do controle.
Botões com Desenho Personalizado (Owner-Draw)
Utilizando o estilo BS_OWNERDRAW, o sistema delega a renderização visual inteiramente à aplicação. O controle envia WM_DRAWITEM sempre que precisa ser repintado, fornecendo uma estrutura DRAWITEMSTRUCT contendo o contexto de dispositivo (HDC), a área de recorte e o estado atual.
#include <windows.h>
#define ID_SHAPE_CIRCLE 101
#define ID_SHAPE_SQUARE 102
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE hInstGlobal;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASS wc = { CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInstance,
NULL, LoadCursor(NULL, IDC_ARROW),
(HBRUSH)(COLOR_WINDOW + 1), NULL, TEXT("GeometricApp") };
RegisterClass(&wc);
hInstGlobal = hInstance;
HWND hWndMain = CreateWindow(TEXT("GeometricApp"), TEXT("Owner-Draw Demo"),
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
400, 300, NULL, NULL, hInstance, NULL);
ShowWindow(hWndMain, nCmdShow);
UpdateWindow(hWndMain);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
static HWND hBtnCircle, hBtnSquare;
switch (msg) {
case WM_CREATE: {
hBtnCircle = CreateWindow(TEXT("button"), NULL, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
50, 50, 80, 80, hWnd, (HMENU)ID_SHAPE_CIRCLE, hInstGlobal, NULL);
hBtnSquare = CreateWindow(TEXT("button"), NULL, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
150, 50, 80, 80, hWnd, (HMENU)ID_SHAPE_SQUARE, hInstGlobal, NULL);
return 0;
}
case WM_COMMAND: {
RECT rc;
GetWindowRect(hWnd, &rc);
if (LOWORD(wParam) == ID_SHAPE_CIRCLE) {
MoveWindow(hWnd, rc.left, rc.top, rc.right - rc.left + 20, rc.bottom - rc.top + 20, TRUE);
} else if (LOWORD(wParam) == ID_SHAPE_SQUARE) {
MoveWindow(hWnd, rc.left, rc.top, max(200, rc.right - rc.left - 20), max(150, rc.bottom - rc.top - 20), TRUE);
}
return 0;
}
case WM_DRAWITEM: {
LPDRAWITEMSTRUCT pDis = (LPDRAWITEMSTRUCT)lParam;
FillRect(pDis->hDC, &pDis->rcItem, (HBRUSH)GetStockObject(WHITE_BRUSH));
FrameRect(pDis->hDC, &pDis->rcItem, (HBRUSH)GetStockObject(BLACK_BRUSH));
HBRUSH hBrush = CreateSolidBrush(RGB(50, 100, 150));
HGDIOBJ hOld = SelectObject(pDis->hDC, hBrush);
int cx = pDis->rcItem.right - pDis->rcItem.left;
int cy = pDis->rcItem.bottom - pDis->rcItem.top;
if (pDis->CtlID == ID_SHAPE_CIRCLE) {
Ellipse(pDis->hDC, cx/4, cy/4, 3*cx/4, 3*cy/4);
} else if (pDis->CtlID == ID_SHAPE_SQUARE) {
Rectangle(pDis->hDC, cx/4, cy/4, 3*cx/4, 3*cy/4);
}
if (pDis->itemState & ODS_SELECTED) InvertRect(pDis->hDC, &pDis->rcItem);
if (pDis->itemState & ODS_FOCUS) DrawFocusRect(pDis->hDC, &pDis->rcItem);
SelectObject(pDis->hDC, hOld);
DeleteObject(hBrush);
return TRUE;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
Para alinhar as cores dos textos e fundos com o tema do sistema, intercepte WM_CTLCOLORBTN ou utilize SetTextColor e SetBkColor baseados em valores retornados por GetSysColor, como COLOR_BTNFACE e COLOR_WINDOWTEXT.
- Controles Estáticos (Static Class)
Controles estáticos são utilizados principalmente para exibição de texto ou formas geométricas simples que não requerem interação direta. Estilos como SS_BLACKRECT, SS_GRAYFRAME e SS_WHITERECT mapeiam automaticamente para cores do sistema (COLOR_3DDKSHADOW, COLOR_BTNSHADOW, COLOR_BTNHIGHLIGHT), garantindo consistência visual independente das configurações do usuário.
- Barras de Rolagem (Scrollbar Class)
Diferente dos botões, barras de rolagem criadas como janelas filhas não emitem WM_COMMAND. Em vez disso, elas geram WM_VSCROLL ou WM_HSCROLL. O parâmetro lParam destas mensagens contém o handle exato da barra de rolagem que originou o evento, permitindo a distinção quando múltiplos controles existem.
#include <windows.h>
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmd, int show) {
WNDCLASS wc = { 0 };
wc.lpfnWndProc = MainWndProc;
wc.hInstance = hInst;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = TEXT("ColorMixerApp");
RegisterClass(&wc);
HWND hMain = CreateWindow(TEXT("ColorMixerApp"), TEXT("Misturador RGB"),
WS_OVERLAPPEDWINDOW, 100, 100, 500, 300, NULL, NULL, hInst, NULL);
ShowWindow(hMain, show);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg);
return 0;
}
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
static HWND hScrollR, hScrollG, hScrollB, hLblPreview;
static int rgbValues[3] = { 128, 128, 128 };
switch (msg) {
case WM_CREATE: {
HINSTANCE hInst = (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE);
for (int i = 0; i < 3; i++) {
HWND hScroll = CreateWindow(TEXT("scrollbar"), NULL,
WS_CHILD | WS_VISIBLE | SBS_HORZ,
50, 30 + i * 40, 300, 20, hWnd, (HMENU)(intptr_t)i, hInst, NULL);
SetScrollRange(hScroll, SB_CTL, 0, 255, FALSE);
SetScrollPos(hScroll, SB_CTL, 128, FALSE);
if (i == 0) hScrollR = hScroll;
else if (i == 1) hScrollG = hScroll;
else hScrollB = hScroll;
}
hLblPreview = CreateWindow(TEXT("static"), NULL,
WS_CHILD | WS_VISIBLE | SS_CENTER,
50, 160, 300, 80, hWnd, (HMENU)99, hInst, NULL);
return 0;
}
case WM_HSCROLL: {
int id = GetDlgCtrlID((HWND)lParam);
int pos = GetScrollPos((HWND)lParam, SB_CTL);
switch (LOWORD(wParam)) {
case SB_LINELEFT: pos -= 5; break;
case SB_LINERIGHT: pos += 5; break;
case SB_PAGELEFT: pos -= 20; break;
case SB_PAGERIGHT: pos += 20; break;
case SB_THUMBTRACK: pos = HIWORD(wParam); break;
}
pos = max(0, min(255, pos));
SetScrollPos((HWND)lParam, SB_CTL, pos, TRUE);
rgbValues[id] = pos;
InvalidateRect(hLblPreview, NULL, TRUE);
return 0;
}
case WM_CTLCOLORSTATIC: {
if ((HWND)lParam == hLblPreview) {
HBRUSH hBrush = CreateSolidBrush(RGB(rgbValues[0], rgbValues[1], rgbValues[2]));
// Note: Returning a dynamically created brush here requires careful lifecycle management.
// In production, cache this brush and delete the old one on color change.
SetBkColor((HDC)wParam, RGB(rgbValues[0], rgbValues[1], rgbValues[2]));
return (LRESULT)hBrush;
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
- Caixas de Edição (Edit Class)
Os controles de edição emitem WM_COMMAND com códigos de notificação específicos, como EN_CHANGE (conteúdo alterado), EN_SETFOCUS e EN_MAXTEXT (limite de buffer atingido).
Além da interação via usuário, a API fornece mensagens diretas para manipulação avançada de texto e área de transferência:
EM_GETSEL/EM_SETSEL: Manipulam o intervalo de texto selecionado.EM_REPLACESEL: Substitui o texto atualmente destacado.EM_GETLINECOUNT/EM_GETLINE: Navegação e extração em controles multilinha (ES_MULTILINE).WM_CUT,WM_COPY,WM_PASTE: Integração nativa com a área de transferência do sistema.
#include <windows.h>
#define ID_EDITOR_CTRL 500
LRESULT CALLBACK EditorWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmd, int show) {
WNDCLASS wc = { CS_HREDRAW | CS_VREDRAW, EditorWndProc, 0, 0, hInst, NULL,
LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1), NULL, TEXT("SimpleEditorApp") };
RegisterClass(&wc);
HWND hMain = CreateWindow(TEXT("SimpleEditorApp"), TEXT("Editor API Win32"),
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
600, 400, NULL, NULL, hInst, NULL);
ShowWindow(hMain, show);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK EditorWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
static HWND hEditArea;
switch (msg) {
case WM_CREATE:
hEditArea = CreateWindow(TEXT("edit"), NULL,
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | WS_BORDER |
ES_LEFT | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL,
0, 0, 0, 0, hWnd, (HMENU)ID_EDITOR_CTRL,
(HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE), NULL);
return 0;
case WM_SIZE:
MoveWindow(hEditArea, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_SETFOCUS:
SetFocus(hEditArea);
return 0;
case WM_COMMAND:
if (LOWORD(wParam) == ID_EDITOR_CTRL) {
if (HIWORD(wParam) == EN_ERRSPACE) {
MessageBox(hWnd, TEXT("Memória insuficiente para a operação."), TEXT("Erro"), MB_ICONERROR);
}
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
- Caixas de Listagem (Listbox Class)
Para que uma caixa de listagem notifique a janela pai sobre mudanças de seleção, ela deve ser criada com o estilo LBS_NOTIFY. O estilo LBS_SORT ordena alfabeticamente as entradas automaticamente. As notificações chegam via WM_COMMAND, onde LBN_SELCHANGE indica que o usuário alterou o item ativo.
Manipulação de Dados
A inserção de dados é realizada via LB_ADDSTRING (para listas ordenadas) ou LB_INSERTSTRING (para inserção em índices específicos). A recuperação de strings exige a chamada prévia de LB_GETTEXTLEN para alocação dinâmica de buffer, seguida por LB_GETTEXT.
#include <windows.h>
#include <strsafe.h>
#define ID_LST_DRIVES 201
#define ID_LBL_INFO 202
LRESULT CALLBACK ListerWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmd, int show) {
WNDCLASS wc = { 0 };
wc.lpfnWndProc = ListerWndProc;
wc.hInstance = hInst;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = TEXT("DriveListerApp");
RegisterClass(&wc);
HWND hMain = CreateWindow(TEXT("DriveListerApp"), TEXT("Unidades de Disco"),
WS_OVERLAPPEDWINDOW, 200, 200, 400, 300, NULL, NULL, hInst, NULL);
ShowWindow(hMain, show);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg);
return 0;
}
void PopulateDrives(HWND hList) {
TCHAR logicalDrives[256];
if (GetLogicalDriveStrings(256, logicalDrives) > 0) {
TCHAR* pDrive = logicalDrives;
while (*pDrive) {
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)pDrive);
pDrive += lstrlen(pDrive) + 1;
}
}
}
LRESULT CALLBACK ListerWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
static HWND hListBox, hInfoLabel;
switch (msg) {
case WM_CREATE: {
HINSTANCE hInst = (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE);
hListBox = CreateWindow(TEXT("listbox"), NULL,
WS_CHILD | WS_VISIBLE | LBS_STANDARD,
20, 20, 150, 200, hWnd, (HMENU)ID_LST_DRIVES, hInst, NULL);
hInfoLabel = CreateWindow(TEXT("static"), TEXT("Selecione uma unidade..."),
WS_CHILD | WS_VISIBLE | SS_LEFT,
190, 20, 180, 200, hWnd, (HMENU)ID_LBL_INFO, hInst, NULL);
PopulateDrives(hListBox);
return 0;
}
case WM_COMMAND:
if (LOWORD(wParam) == ID_LST_DRIVES && HIWORD(wParam) == LBN_SELCHANGE) {
int idx = (int)SendMessage(hListBox, LB_GETCURSEL, 0, 0);
if (idx != LB_ERR) {
TCHAR drivePath[4];
SendMessage(hListBox, LB_GETTEXT, idx, (LPARAM)drivePath);
UINT type = GetDriveType(drivePath);
TCHAR infoBuffer[128];
StringCchPrintf(infoBuffer, 128, TEXT("Unidade: %s\nTipo: %u"), drivePath, type);
SetWindowText(hInfoLabel, infoBuffer);
}
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}