A PRIMEIRA JANELA

O primeiro projeto tem gosto de "quero mais" e, geralmente, o quero mais se refere a um programa Windows. Acontece que estamos apenas no terceiro módulo do tutorial e não é aconselhável queimar etapas. Então, o jeito é partir para a janelinha mais simples que existe: uma caixa de diálogo.

Apesar de simples, muitos conceitos serão abordados. Analise-os com atenção, pois são essenciais para o sistema operacional Windows e vamos precisar deles daqui para frente. É importante entender todas as etapas e o mecanismo usado pelo sistema operacional.


  Um programa para o Windows

Hoje em dia, a maioria dos aplicativos são controlados por uma GUI (Graphical User Interface). Através do uso de janelas, que contém controles como campos de edição, botões, menus, etc, a interatividade do usuário torna-se mais fácil e intuitiva.

Você viu na Introdução que a organização de um programa C exige SEMPRE uma função principal, geralmente denominada de main, por onde o programa é iniciado. No caso de um programa para o Windows, esta função é a WinMain (poderia ser diferente?). As funções utilizadas num programa Windows podem ser de dois tipos: aquelas que você programar (suas funções) e aquelas que fazem parte do sistema operacional Windows e estão prontinhas para serem usadas.

Todas as funções "usáveis" do Windows estão agrupadas por tipo em arquivos com a extensão .DLL (dynamic-linked libraries). O conjunto destas DLLs é chamado de API (Application Programming Interface). As principais DLLs são kernel32.dll, user32.dll e gdi32.dll. A kernel32.dll contém funções API que lidam com a memória e com a administração de processos, a user32.dll possui funções que controlam a aparência da interface com o usuário e a gdi32.dll tem funções responsáveis por operações gráficas. São milhares de funções que, quando chamadas com os parâmetros corretos, criam as janelas e os controles da GUI (Ainda bem, já pensou ter que programar cada risquinho???).


  O primeiro programa GUI para Windows

Crie um novo projeto clicando em |Project/Create|. Dê-lhe um nome (eu o chamei de testedlg) e indique o diretório onde o projeto deve ser colocado. Em "Options" assinale "Single user". Até aqui, tudo igual ao módulo anterior. Mas, para um programa Windows, siga os passos a seguir:

  1. Em "Type of project" assinale "Windows Application".
  2. Clique em [Create] e no diálogo "Do you want to use the wizard to generate the application skeleton?" (Quer usar o wizard para gerar o esqueleto do aplicativo?) clique em [Yes].
  3. Na caixa de diálogo "Application characteristics", em "Type of apllication", assinale a opção "Dialog based" e depois clique em [Ok].
  4. Clique em [OK] na caixa de mensagem avisando que o projeto foi criado.
  5. Na janela "Compiler settings", clique em [Next]. Na janela "Linker settings", clique em [Next]. Na janela "Debugger settings", clique em [Finish].

Surpresa! A janela de edição mostra nosso programa, gentilmente preparado pelo lcc-win32. Mordomia pura, pois o programa está pronto. Basta compilá-lo com |Compiler/Compile testedlg.c| e rodá-lo com |Compiler/Execute testedlg.exe| para ver o resultado.

testedlg.exe

O programa montado pelo lcc

Não se assuste com todo este código, mesmo porque vamos cansar de vê-lo e, pode ter certeza, a gente acaba se acostumando. Observe que o programa possui três funções: WinMain, InitializaApp e a DialogFunc, destacadas em negrito. A InitializaApp não faz nada além de retornar 1 - serve apenas de gancho se quisermos configurar alguma coisa antes da caixa de diálogo ser mostrada. Vou tentar explicar em detalhes as outras duas, a WinMain e a DialogFunc.

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <string.h>
#include "testedlgres.h"

static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASS wc;
	INITCOMMONCONTROLSEX cc;

	memset(&wc,0,sizeof(wc));
	wc.lpfnWndProc = DefDlgProc;
	wc.cbWndExtra = DLGWINDOWEXTRA;
	wc.hInstance = hinst;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
	wc.lpszClassName = "testedlg";
	RegisterClass(&wc);
	memset(&cc,0,sizeof(cc));
	cc.dwSize = sizeof(cc);
	cc.dwICC = 0xffffffff;
	InitCommonControlsEx(&cc);

	return DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINDIALOG), NULL, (DLGPROC) DialogFunc);
}

static int InitializeApp(HWND hDlg,WPARAM wParam, LPARAM lParam)
{
	return 1;
}

static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) {
	case WM_INITDIALOG:
		InitializeApp(hwndDlg,wParam,lParam);
		return TRUE;
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
			case IDOK:
				EndDialog(hwndDlg,1);
				return 1;
			case IDCANCEL:
				EndDialog(hwndDlg,0);
				return 1;
		}
		break;
	case WM_CLOSE:
		EndDialog(hwndDlg,0);
		return TRUE;
	}
	return FALSE;
}

A função WinMain

A função WinMain é o ponto de entrada do programa. É uma função da API do Windows que pede quatro parâmetros, usando a convenção de chamada stdcall (APIENTRY especifica este tipo de chamada), e que retorna um inteiro (int):

		int APIENTRY WinMain(HINSTANCE hinst,
				HINSTANCE hinstPrev, 
				LPSTR lpCmdLine, 
				int nCmdShow);
  1. HINSTANCE hinst é o manipulador (handle) da instância do programa. Seu valor é sempre 0x400000 em hexadecimal e não é usado para nada... coisas do Windows.
  2. HINSTANCE hinstPrev refere-se a uma instância anterior do programa: outro manipulador misterioso. Contém sempre zero e também nunca é usado.
  3. LPSTR lpCmdLine é importante! É um ponteiro para uma string de caracteres que contém os argumentos da linha de comando.
  4. int nCmdShow contém um inteiro que indica se o programa foi chamado com a instrução de ficar oculto, ou aparecer normalmente, ou outras instruções que podem ser usadas quando a janela principal for criada.
int APIENTRY WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASS wc;
	INITCOMMONCONTROLSEX cc;

	memset(&wc,0,sizeof(wc));
	wc.lpfnWndProc = DefDlgProc;
	wc.cbWndExtra = DLGWINDOWEXTRA;
	wc.hInstance = hinst;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
	wc.lpszClassName = "testedlg";
	RegisterClass(&wc);
	memset(&cc,0,sizeof(cc));
	cc.dwSize = sizeof(cc);
	cc.dwICC = 0xffffffff;
	InitCommonControlsEx(&cc);

	return DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINDIALOG), NULL, (DLGPROC) DialogFunc);
}

A tarefa principal desta função é criar uma estrutura WNDCLASS, preenchê-la com dados e depois chamar a função da API DialogBox. WNDCLASS wc; cria a estrutura e todas as linhas com wc.qualquer coisa atribuem valores aos campos desta estrutura. Depois de pronta, esta estrutura precisa ser registrada como uma classe do sistema operacional Windows, o que é feito com RegisterClass(&wc);.

Uma classe, no Windows, é um conjunto de objetos de janela que compartilham um procedimento comum. Quando alguma mensagem ou evento referente a esta janela é detectado pelo sistema, é enviada uma mensagem à esta janela. Por exemplo, quando movemos o cursor do mouse sobre esta janela, o sistema envia uma mensagem do tipo WM_MOUSEMOVE para o procedimento da janela, informando-o do evento. Este sistema de troca de mensagens pode ser comparado ao um serviço SAC (serviço de atendimento ao consumidor). Não se confunda: SAC não é um acrônimo usado em informática - serve apenas como comparação.

Quando um programa está rodando, existe um caminhão de mensagens sendo constantemente enviadas e recebidas. Seria impossível gerenciar todas. Para nossa sorte, apenas tratamos as mensagens que nos interessam e, as restantes, passamos para o procedimento padrão (o SAC que se vire!).

Existem vários procedimentos padrão: para uma janela normal, há o DefWindowProc; para uma janela MDI, existe o MDIDefWindowProc; e para caixas de diálogo, como neste caso, há o procedimento DefDlgProc. Dá para perceber que, para cada tipo de janela, podemos contar com um SAC especializado ;)

Sabendo disso, informamos na nossa estrutura wc que o procedimento padrão de troca de mensagens é o DefDlgProc (wc.lpfnWndProc = DefDlgProc;), indicando ao sistema o SAC apropriado.

Os dados mais importantes da estrutura foram explicados. Agora está na hora de registrar a estrutura para que seja colocada à nossa disposição pelo sistema: chamamos a função da API RegisterClass(&wc); levando como parâmetro o ponteiro para a nossa estrutura wc. O Windows que se vire com o resto...

A última declaração da função WinMain vale uma explicação mais detalhada. Agora que temos uma estrutura registrada, chamamos a função da API DialogBox:

		DialogBox(hinst,
			MAKEINTRESOURCE(IDD_MAINDIALOG),
			NULL, (DLGPROC) DialogFunc);

O parâmetro hinst, que muitas funções da API ainda exigem, é o valor que recebemos do sistema quando chamamos a função WinMain. Depois usamos a macro MAKEINTRESOURCE para fazer com que o compilador transforme em ponteiro o valor de IDD_MAINDIALOG. Este valor foi definido nos recursos, que serão tema do próximo módulo. Por enquanto, deixa quieto...

O terceiro parâmetro é NULL. Na verdade, deveria ser o valor do manipulador (handle) da janela-mãe desta caixa de diálogo. As caixas de diálogo, normalmente, são acessórios do aplicativo e têm nesse parâmetro a indicação do manipulador da janela à qual pertencem. Acontece que estamos construindo uma caixa de diálogo autônoma, que não possui uma janela-mãe (pobrezinha, é órfã!), portanto podemos passar um NULL (nadinha de nada) como referência.

O quarto e último parâmetro é a indicação de que, caso existam mensagens importantes, estas devem ser direcionadas para a função DialogFunc, que será vista a seguir.


A função DialogFunc

Esta é a central de atendimento particular na nossa janela do tipo caixa de diálogo. É para ela que o sistema envia as mensagens importantes, todas num formato padrão: o manipulador da janela da caixa de diálogo (como se fosse o endereço do destinatário), a mensagem e mais dois parâmetros extras.

static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) {
	case WM_INITDIALOG:
		InitializeApp(hwndDlg,wParam,lParam);
		return TRUE;
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
			case IDOK:
				EndDialog(hwndDlg,1);
				return 1;
			case IDCANCEL:
				EndDialog(hwndDlg,0);
				return 1;
		}
		break;
	case WM_CLOSE:
		EndDialog(hwndDlg,0);
		return TRUE;
	}
	return FALSE;
}

Esta função, com o uso de uma declaração switch, faz a triagem das mensagens recebidas. Apenas três tipos de mensagens são tratadas pela nossa central de atendimento, as restantes são ignoradas:

  • WM_INITDIALOG: esta mensagem é enviada depois da janela da caixa de diálogo ter sido criada e antes de ser mostrada na tela. É aqui que se pode fazer uma chamada para a função InitializaApp, aquela que eu disse não estar fazendo nada neste programa (retorna apenas o valor 1). Vai ser usada mais pra frente, aguarde.
  • WM_COMMAND: esta mensagem é enviada quando um dos controles (ou janela-filha, se quisermos ser mais exatos) quiser notificar a caixa de diálogo de algum evento importante, do tipo um botão foi clicado, um checkbox foi selecionado, o texto de uma caixa de texto foi alterado, etc. Como a caixa de diálogo pode conter vários controles, usamos novamente uma declaração switch para poder tratá-los individualmente.
  • WM_CLOSE: esta mensagem é recebida quando o usuário clicar a opção "close" do menu ou quando digitar Ctrl+F4 para fechar a caixa de diálogo.

Observe que, com exceção da mensagem WM_INITDIALOG, todas as outras são respondidas com EndDialog, ou seja, seja lá onde você clicar, a caixa de diálogo será fechada.

Mais uma coisa. Caso não tenha percebido, todas as mensagens são precedidas por WM_, que vem de Windows Message. Como nomes são mais fáceis de memorizar (quando se sabe Inglês ;) do que números, os números de identificação das mensagens padrão do Windows foram traduzidos para WM_tipo de mensagem.


  Observações da vovó Vicki

Muita areia pro seu caminhãozinho? É, só que não tem escapatória: se quisermos brincar de aplicativo Windows, este é o caminho das pedras para chamar o sistema na chincha. E olha que é um aplicativozinho de nada (pus aplicativozinho só para dar uma força pro pessoal da terceira idade :0)))

Brincadeira a parte, a coisa não é tão complicada assim. Resumindo: a função WinMain aciona o sistema operacional para criar uma estrutura do tipo WNDCLASS. Depois colocamos os valores necessários na estrutura, principalmente o procedimento padrão de troca de mensagens (o SAC especializado, lembra?) e registramos tudo no sistemão do Windows. Criamos nossa própria central de mensagens (a função DialogFunc) para poder receber as mensagens do SAC, fazer a triagem e reagirmos apenas às que nos convierem.

Pois é, esta é a forma de fazer com que o sistema operacional trabalhe para nós e, o que é mais importante, da forma como NÓS determinamos. Para tanto é preciso ter uma boa fonte de referência da API do Windows. Aconselho fazer o download do win32hlp.exe nos mesmos endereços indicados para o download do lcc-win32 que, apesar de imenso (são 12,8 Mega), é essencial para nos orientar. Instale-o se você souber Inglês, senão... dê uma olhada nas tabelas NumaBoa. Não tem tudo, mas o principal está traduzido para o Português.

Se você se embananou, não se preocupe. Voltaremos ao assunto mais de uma vez.


  Roteiro do tutorial C
  1. Introdução - Considerações iniciais.
  2. O primeiro projeto - Conhecendo o lcc-win32.
  3. A primeira janela
  4. Diálogo personalizado - Tomando as rédeas.
  5. Caçando informações - Interagindo com o usuário.
  6. Bibliotecas e Tipos - Declarações e Definições. Protótipos de funções.
  7. Uma janela de verdade - O esqueleto de um aplicativo.
  8. Estruturas e Uniões - Ordem e economia de memória.
  9. Um projeto ambicioso - O Projeto Espião.


| AAAA | Página Inicial | Mapa do Site | Novidades | Busca | Indique esta página | Mestre da Teia | Voltar |
| Localizador || @ Info NumaBoa > Tutorial C > A primeira janela
Créditos: vovó Vicki

webdesign sobMedida by vickiSoft - /informatica/c/dlg.php (07.12.03) versão 1.0 de 08.12.03
Licença Creative Commons 1998-2006 Aldeia NumaBoa
Exceto onde especificamente declarado, todo material deste site é disponibilizado de acordo com a Licença Creative Commons.