Arquitetura Win32: Gerenciamento de Janelas Filhas e Controles de Interface

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 via GetWindowLongPtr.
  • 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);

  1. 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.

  1. 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.

  1. 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);
}

  1. 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);
}

  1. 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);
}

Tags: Win32 API C gdi User32 Controles de Interface

Publicado em 10-8 13:26