CAÇANDO JANELAS

No módulo anterior, Um projeto ambicioso, criamos o projeto Espião e adicionamos uma janela-filha. Esta janela-filha corresponde a um controle de árvore, do tipo que encontramos no Explorer do Windows, onde poderemos colocar informações hierarquizadas. Quais informações? Hehe, o resultado da nossa caça a janelas. Vai ser a nossa sala de troféus ;)


  As trombetas de caça

Nas caçadas clássicas, sai todo mundo a cavalo com um monte de cães correndo na frente, latindo como uns malucos, assim que o início da caçada é anunciada pelo trombeteiro. Gente, nem eu sabia que programar era uma atividade tão emocionante!

Tudo bem, mas onde está o trombeteiro? Resposta: no menu. Que tal trocar o item New, gerado automaticamente pelo Wedit, por "Rastrear", ou "Farejar", ou qualquer outra coisa que indique o início da caçada? Eu optei por "Farejar" e espero que meus cães perdigueiros não me façam passar vergonha.

Clique em |Design|Open/New|, abra "espiaores.h", expanda "menu" e dê um duplo clique em IDMAINMENU para abrir o editor de menus. Expanda "&File" e clique em "&New": este item está identificado por IDM_NEW e possui o valor 200. Troque &New por &Farejar e clique em [Apply] (eu aproveitei e mudei &File para &Espião, &E&xit para &Sair e eliminei o item de menu &Open). Feche o editor de menu e o editor de recursos.

O item de menu &Fareja continuou com a IDentificação IDM_NEW. É esta ID que precisamos interceptar para que a caçada seja iniciada. Acontece que o menu possui uma central de mensagens própria, a função MainWndProc_OnCommand. Como, para popular a árvore, já é possível prever que será necessário um trecho de código mais elaborado, vamos novamente usar a técnica do "top-down" e inserir uma chamada a uma função. Chamei esta função de criaTree:

void MainWndProc_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
	switch(id) {
	case IDM_NEW:
		criaTree(hwnd);
		break;
	case IDM_EXIT:
		PostMessage(hwnd,WM_CLOSE,0,0);
		break;
	}
}
  Criando itens da árvore

O código da função, chamada através do item de menu "Farejar" que é interceptada em MainWndProc_OnCommand, inicialmente "fareja" a janela do desktop (ou área de trabalho do Windows). Depois disso, o procedimento relaciona todas as janelas-filhas da janela encontrada. Duas direções precisam ser exploradas: as janelas-filha de uma dada janela e as janelas-irmãs de uma dada janela.

int criaTree(HWND mae)
{
	HWND Inicio = GetDesktopWindow();
	HWND hTree = GetDlgItem(mae,IDJANELATREE);
	TV_INSERTSTRUCT itemTree;
	HTREEITEM hNovoNo;

	memset(&itemTree,0,sizeof(itemTree));
	itemTree.hParent = TVI_ROOT;
	itemTree.hInsertAfter = TVI_LAST;
	itemTree.item.mask = TVIF_TEXT | TVIF_PARAM;
	itemTree.item.pszText = "Desktop";
	hNovoNo = TreeView_InsertItem(hTree,&itemTree);
	Inicio = GetWindow(Inicio,GW_CHILD);
	Scan(hTree,hNovoNo,Inicio);
	TreeView_Expand(hTree,hNovoNo,TVE_EXPAND);
	return 1;
}

A função criaTree é chamada com o parâmetro manipulador da janela-mãe. Ela começa requisitando ao Windows o manipulador da janela do desktop e armazenando este valor na variável do tipo HWND Inicio. Como também vamos precisar do manipulador da janela da árvore, armazenamos seu valor na variável do tipo HWND hTree que nos é retornada pela função GetDlgItem. A seguir, manifestamos a estrutura itemTree. Este tipo de estrutura está assim definido no Windows:

	typedef struct _TV_INSERTSTRUCT {
		HTREEITEM hParent;
		HTREEITEM hInsertAfter;
		TV_ITEM   item;
	} TV_INSERTSTRUCT, FAR *LPTV_INSERTSTRUCT;

Como cada nó da estrutura hierárquica da árvore também possui um manipulador próprio (o que é que não tem manipulador no Windows?), manifestamos a variável do tipo HTREEITEM hNovoNo.

É comum jogar estruturas para uma área da memória e referenciá-las através de ponteiros. Com a estrutura itemTree não vai ser diferente mas, para garantir que a área de memória esteja "limpinha", zeramos todos os bits com a chamada da função da linguagem C memset. Os parâmetros são o ponteiro para a área de memória onde está a estrutura (&itemTree), o valor de preenchimento (0) e o tamanho da estrutura ( sizeof(itemTree) ).

Tudo sob controle, começamos a atribuir valores para os campos da estrutura que nos interessam:

  • itemTree.hParent = TVI_ROOT indica que se trata de um item raiz (o valor de TVI_ROOT é definido no Windows como -65536). Obtemos o mesmo resultado se atribuirmos NULL a este campo.
  • itemTree.hInsertAfter = TVI_LAST indica que o item deve ser adicionado no final da lista (LAST = último). Outros valores possíveis são TVI_FIRST (inserir no começo da lista, pois FIRST = primeiro) e TVI_SORT (inserir o item em ordem alfabética. SORT = ordenar).
  • TV_ITEM é uma estrutura aninhada e contém muitos campos. Destes, foram escolhidos apenas dois:
    • itemTree.item.mask = TVIF_TEXT | TVIF_PARAM. O valor TVIF_TEXT indica que o campo pszText desta estrutura aninhada é válido e o valor TVIF_PARAM indica que o campo lParam da estrutura aninhada é válido.
    • itemTree.item.pszText = "Desktop" atribui a string "Desktop" ao campo lpzText.

Depois de atribuir os valores aos campos, chamamos a macro do Windows TreeView_InsertItem, que pede como parâmetros o manipulador da árvore e um ponteiro para a estrutura que especifica os atributos do item, e que retorna (dou-lhe uma, dou-lhe duas, dou-lhe três...) o manipulador do novo item se tudo correr bem; caso contrário, retorna NULL.

Tudo prontinho, chamamos a função Scan (discutida abaixo) para caçar as janelas existentes. De posse de todos os nós da árvore (a função criaTree prepara o raiz e a função Scan os nós restantes), chamamos a macro TreeView_Expand para mostrar a árvore completa.

Bem, depois de todas estas explicações, acho que já é possível inserir o código acima no seu programa... o susto não vai mais ser tão grande. Não se esqueça, programador profissional faz protótipo de função, então:

...
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
static HWND _stdcall janelaTree(HWND hWnd,int ID);
int criaTree(HWND parent);
...
  Farejando as janelas

Agora é a hora de rastrear as janelas-filha e janelas-irmãs. Como o desktop é a janela raiz de todas as janelas e não possui janelas-irmãs, toca achar as janelas-filha. A função GetWindow é perfeita, retornando (dou-lhe uma, dou-lhe duas...) o manipulador da primeira janela-filha. Depois a coisa fica um pouco mais elaborada e merece novamente uma função própria. Chamei-a de Scan porque ela vai "escanear" e povoar nossa árvore com dados:

void Scan(HWND hTree,HTREEITEM hNoPai,HWND Inicio)
{
	HWND hwnd = Inicio, hwnd1;
	TV_INSERTSTRUCT novoItemTree;
	HTREEITEM htiNovoNo;
	char bufTxt[256],NomeClasseBuf[256],Saida[1024];

	while (hwnd != NULL) {
		SendMessage(hwnd,WM_GETTEXT,250,(LPARAM) bufTxt);
		GetClassName(hwnd,NomeClasseBuf,250);
		wsprintf(Saida,"\"%s\" %s",bufTxt,NomeClasseBuf);
		memset(&novoItemTree,0,sizeof(novoItemTree));
		novoItemTree.hParent = hNoPai;
		novoItemTree.hInsertAfter = TVI_LAST;
		novoItemTree.item.mask = TVIF_TEXT | TVIF_PARAM;
		novoItemTree.item.pszText = (LPSTR) Saida;
		novoItemTree.item.lParam = (LPARAM) hwnd;
		htiNovoNo = TreeView_InsertItem(hTree,&novoItemTree);
		if((hwnd1=GetWindow(hwnd,GW_CHILD))!=NULL)
			Scan(hTree,htiNovoNo,hwnd1);
		hwnd=GetWindow(hwnd,GW_HWNDNEXT); 
	}
}

Analisando o loop criado com while (hwnd != NULL), que diz "enquanto(hwnd não for NULL)", verificamos que a função Scan é chamada recursivamente enquanto existirem janelas-irmãs. Os passos são:

  1. Enviamos a mensagem WM_GETTEXT (pegue o texto) através da chamada à função SendMessage para obter o texto da janela em questão. O texto é armazenado no array de caracteres bufTxt manifestado acima.
  2. Obtemos o nome da classe da janela com GetClassName. O nome da classe é armazenado no array de caracteres NomeClasseBuf manifestado acima.
  3. Formatamos o texto da janela e o nome da classe com a função da linguagem C wsprintf e colocamos o resultado na variável Saida, também um array de caracters manifestado acima.
  4. Limpamos a área de memória reservada para a estrutura novoItemTree, do tipo TV_INSERTSTRUCT.
  5. Atribuímos valores a determinados campos da estrutura TV_INSERTSTRUCT, da mesma forma como foi descrito para a janela do desktop.
  6. Criamos um novo nó na árvore contendo as informações da estrutura.
  7. Neste ponto, verificamos se a janela em questão possui janelas-filha com GetWindow e GW_CHILD. Se possuir, chamamos recursivamente a função Scan para popular a árvore com esta(s) janela(s).
  8. Obtemos o manipulador da próxima janela-irmã com GetWindow e GW_HWNDNEXT. Se houver nova janela, o loop é repetido, caso contrário, é encerrado.
  Mostrando os troféus

Salve o código fonte. Compile e execute o projeto espião. Clique em |Farejar| para mostrar o resultado da caça. Espantoso! Só que, se clicarmos algumas vezes em |Farejar|, uma porção de nós repetidos vão aparecer. É melhor dar uma conferida.

Analisando melhor a função criaTree percebemos que sempre adicionamos nós no fim da lista mas... nunca limpamos a árvore. Taí. É preciso começar a popular uma árvore "zerada". Além disso, a árvore é redesenhada a cada novo nó adicionado. Soma-se ao trabalho desnecessário uma imagem que fica tremendo. Mas é tudo coisa pouca, fácil de corrigir:

int criaTree(HWND mae)
{
	HWND Inicio = GetDesktopWindow();
	HWND hTree = GetDlgItem(mae,IDJANELATREE);
	TV_INSERTSTRUCT itemTree;
	HTREEITEM hNovoNo;

	SendMessage(hTree,WM_SETREDRAW,0,0);
	TreeView_DeleteAllItems(hTree);
	memset(&itemTree,0,sizeof(itemTree));
	itemTree.hParent = TVI_ROOT;
	itemTree.hInsertAfter = TVI_LAST;
	itemTree.item.mask = TVIF_TEXT | TVIF_PARAM;
	itemTree.item.pszText = "Desktop";
	hNovoNo = TreeView_InsertItem(hTree,&itemTree);
	Inicio = GetWindow(Inicio,GW_CHILD);
	Scan(hTree,hNovoNo,Inicio);
	SendMessage(hTree,WM_SETREDRAW,1,0);
	return 1;
}

A função SendMessage com os parâmetros WM_SETREDRAW e 0 (zero) desliga a atualização de tela, ou seja, a imagem fica "congelada". A seguir, a macro TreeView_DeleteAllItems apaga todos os nós da árvore - o usuário não percebe nada porque ele continua vendo a imagem anterior (que foi congelada). Se é assim, podemos continuar populando a árvore tranquilamente sem que a imagem seja redesenhada toda santa vez que um novo nó for adicionado :) Nos finalmentes, ativamos novamente a atualização de tela com a mesma função SendMessage, só que com os parâmetros WM_SETREDRAW e 1.

Yeeees. Você já sabe, é salvar, compilar e executar. O resultado deve ser algo parecido com

Troféus de caça

Observações da vovó Vicki

O programa espião não é uma obra perfeitamente acabada, mas até que está legal. Mais legal ainda é ter servido de exemplo para dar uma idéia do potencial da programação com linguagem C para o sistema operacional Windows. C é uma linguagem especialmente robusta e flexível, ideal para se programar para qualquer tipo de sistema operacional. E o Linux está precisando de bons programadores...

Bão, filosofias à parte, este tutorial ainda tem um módulo para reforçar os conceitos de eventos e mensagens. Vamos colocar informações referentes às janelas "caçadas" na barra de status.


  Roteiro do tutorial C
  1. Introdução - Considerações iniciais.
  2. O primeiro projeto - Conhecendo o lcc-win32.
  3. A primeira janela - Um programa para o Windows.
  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 > Caçando janelas
Créditos: vovó Vicki

webdesign sobMedida by vickiSoft - /informatica/c/projeto2.php (13.12.03) versão 1.0 de 14.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.