Gostou da Página?
Clique no botão abaixo para indicar esta página para um amigo!


Quer saber mais?
Torne-se um MCP em Visual Basic
Faça um treinamento na Búfalo Informática

SubClassing de Janelas


Um programa feito em Visual Basic reage ao conjunto dos eventos disparados pelos
objetos usados na sua criação. Ao programador cabe tratar estes eventos e garantir que a
aplicação cumpra seus objetivos respondendo adequadamente a eles. A maior parte do
tempo, os eventos expostos pelos objetos são suficientes para que o programador tenha 
controle daquilo que a sua aplicação faz. No entanto, à medida que as aplicações se 
sofisticam, as necessidades de controle sobre o que está acontecendo no ambiente também se
sofisticam. 
A partir de então, o programador pode não mais encontrar nos eventos expostos 
pelos objetos os meios suficientes para o controle da aplicação. É neste momento que se
descobre que, para além do que nos é exibido pelo Visual Basic e pelas interfaces públicas 
dos objetos, há todo um conjunto de acontecimentos que podem ser detectados e tratados pelas
nossas aplicações. A forma de termos acesso a esses acontecimentos e de reagirmos a eles é 
o que se chama subClassing de janelas. Trata-se de uma técnica que pode ser aplicada 
não só a Forms, mas também a qualquer elemento da interface gráfica que possua a propriedade
hWnd. Para o Windows, um botão , um frame ou uma   caixa de texto também são janelas.
Levando-se isto em conta, podemos usar a subClassing de janelas para, por exemplo,
detectar e tratar os eventos que afetam a um componente e modificar a forma como ele reage a
certos eventos. Isto pode nos livrar da necessidade de recriar um componente já existente
apenas para modificar alguns detalhes no modo como ele se comporta.

Antes de nos lançarmos no tema da subClassing, é preciso introduzir algumas informações
básicas sobre o modo como uma aplicação se comunica com o Windows , mas que geralmente 
não faz parte dos conhecimentos do programador em Visual Basic. Saber um pouco do que se
passa por detrás da cena do Visual Basic irá ajudar o leitor a se situar melhor no assunto e,
futuramente, poderá servir como subsídio para usar com mais propriedade algumas APIs
relacionadas com o sistema de mensagens do Windows. 


Toda vez que uma aplicação é iniciada, o Windows cria uma fila de mensagens para ela. 
Aquilo que chamamos aqui de mensagem é equivalente ao que chamamos de evento na programação
em Visual Basic. A aplicação , ao iniciar-se, entra em um loop, que vai ser o centro do seu
controle até que seja finalizada. Este loop recebe o nome de laço de mensagem. O papel do
laço de mensagem é verificar junto ao Windows se há alguma mensagem na fila de mensagens 
da aplicação. A cada passo do loop, a aplicação interroga o Windows fazendo uma chamada à
função da API GetMessage. Se houver alguma mensagem, o Windows retorna a chamada a GetMessage
com as informações que identificam a mensagem e com os parâmetros necessários para
processá-la. Se não houver nenhuma mensagem, o Windows deixa a aplicação à espera até 
que algo aconteça que resulte na adição de uma mensagem à sua fila. Durante este tempo, o
controle pode ser passado para uma outra aplicação que tenha mensagens para processar.

Ao fazer a chamada a GetMessage, a aplicação passa por referência uma variável do tipo
composto MSG, que é definido desta forma:

Public Type MSG
hwnd As Long     'identificador da janela destinatária da mensagem
message As Long  'identificador da mensagem
wParam As Long   'significado depende da mensagem
lParam As Long   'significado depende da mensagem 
time As Long     'hora em que a mensagem foi posta na fila
pt As POINTAPI   'posição do mouse quando a mensagem poi posta na fila
End Type

Quando o Windows retorna uma mensagem como resposta à chamada a GetMessage, os campos da
variável do tipo MSG são preenchidos com informações sobre a mensagem. Veja acima os
comentários nos campos.

Além das informações que retorna na variável MSG, GetMessage também retorna diretamente um
valor. No momento em que recebe o retorno de GetMessage, a aplicação testa por este valor. 
Se o valor for diferente de zero, trata-se de uma mensagem comum. Se o valor for igual a 
zero, trata-se de uma mensagem do tipo WM_QUIT, que significa o encerramento do programa.
Neste caso a aplicação sai do laço de mensagem e termina. Parece o bastante, não? Parece,
mas ainda falta um pouco. 

Quando a aplicação recebe do Windows as informações relativas a uma mensagem, parece contar
com tudo para processá-la. Mas as mensagens destinam-se às janelas da aplicação, e quem 
faz a entrega das mensagens às janelas é o próprio Windows e não o laço de mensagens. O 
que faz então o laço de mensagem com estas informações? Normalmente o que é feito é uma 
chamada à API TranslateMessage, que faz algumas traduções de teclado necessárias em alguns
casos. Em seguida, a mensagem é devolvida para o Windows com uma chamada à 
API DispatchMessage . Quando se programa em linguagem C, este também pode ser o momento 
para realizar outras ações, mas que não merecem ser consideradas aqui. O que se segue é
que o Windows usa o campo hwnd de MSG para identificar a janela destinatária da mensagem 
e chama uma função residente dentro da aplicação, que serve como porta de entrada para as
mensagens destinadas à janela. Esta função costuma ser chamada de 'Procedimento de Janela'
e é invisível num programa feito em Visual Basic. A mensagem é entregue ao procedimento de
janela que recebe como argumentos os campos hwnd, message, wParam e lParam de MSG.  
O Windows sabe qual função chamar para fazer a entrega da mensagem, porque, no momento de
criação da janela, o programa informa ao Windows o endereço desta função. Ufa! Finalmente
chegamos ao ponto em que podemos entrar no tema de subClassing de janelas.

Como vimos acima, o Windows entrega as mensagens destinadas a uma janela chamando o seu
procedimento de janela. Todo procedimento de janela recebe a seguinte lista de argumentos e
retorna um Long : ByVal hwnd As Long , ByVal lMsg As Long, ByVal wParam As Long, 
ByVal lParam As Long . O truque de subClassing consiste em criar, dentro de um 
módulo de código do Visual Basic, um outro procedimento que tenha este mesmo formato e
informar ao Windows de que ele é o procedimento a ser chamado para receber as mensagens
destinadas a uma determinada janela. Desta forma, receberemos, num procedimento codificado em
Visual Basic, todas as mensagens que o Windows enviar à janela e não apenas aquelas expostas
na forma de eventos pelo Visual Basic.  Veja a seguir como isto é feito.

A forma como informamos um novo procedimento de janela ao Windows é fornecendo o endereço
de memória que o procedimento ocupa dentro do nosso programa. A partir da versão 5 do
Visual Basic , o operador AddressOf foi incluído no nosso arsenal. Com ele, podemos obter
e passar o endereço de um procedimento como argumento. Há várias funções da API que 
esperam um endereço de procedimento como argumento e que, antes do surgimento do operador
AddressOf, estavam impossibilitadas de serem chamadas diretamente por código em Visual Basic.
SetWindowLong é uma dessas funções, e é justamente através dela que informamos ao Windows o
endereço do procedimento que desejamos usar para subclassificar uma janela. A chamada a
SetWindowLong é feita passando como argumento o valor da propriedade hwnd da janela, a
constante GWL_WNDPROC sinalizadora do tipo de informação que está sendo passada, 
e o endereço do novo procedimento de janela. Veja abaixo como é declarada a 
API SetWindowLong, a constante GWL_WNDPROC e como é feita a chamada à função:

Public Const GWL_WNDPROC = -4
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _
		ByVal hWnd As Long, _
		ByVal nIndex As GWL_nIndex, _
		ByVal dwNewLong As Long) As Long
lpfnAntigo = SetWindowLong( _
	  MeuForm.hWnd, _
	  GWL_WNDPROC, _
		  AddressOf NovoProcedimento)

No código acima, lpfnAntigo é uma variável do tipo Long usada para armazenar o valor
de retorno de SetWindowLong . Esse valor corresponde ao endereço do velho procedimento
de janela que estamos substituindo pelo nosso. Ao terminar o subClassing, devemos
restabelecer o endereço do procedimento conforme o encontramos. Além disto, este endereço 
será usado como argumento para a função CallWindowProc, que chamaremos dentro do novo 
procedimento para repassar ao antigo as mensagens que resolvermos não processar ou que
queiramos que tenham o tratamento padrão - a maioria. A constante GWL_WNDPROC indica que o
valor informado no terceiro argumento deve substituir o endereço do procedimento da janela
identificada pelo argumento hWnd. O argumento 'AddressOf NovoProcedimento' , como o próprio
nome indica, é o endereço do procedimento substituto. O nome usado para a criação do
procedimento é arbitrário. O indispensável é que a lista de parâmetros e o tipo de valor
retornado correspondam ao formato abaixo. Veja a forma que o novo procedimento pode assumir:


Public Function NovoProcedimento(ByVal hWnd As Long, ByVal lMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case lMsg
 ' Aqui você testa pelos identificadores das
 ' mensagens a serem interceptadas.
  Case WM_DESTROY:
	 ' A janela está sendo destruída, hora de
	 ' desfazer a subClassing.
	 ' Chama o antigo procedimento para fazer
	 ' o tratamento padrão da mensagem
	 Call CallWindowProc(hWnd, lpfnAntigo, hWnd, lMsg, wParam, lParam)
	 'restaura o antigo procedimento
	 SetWindowLong hWnd, GWL_WNDPROC, lpfnAntigo
	 Exit Function
End Select
' Se não quiser que o procedimento original 
' processe a mensagem interceptada, deve 
' sair da função retornando 0 antes de chegar
' aqui.
' Chama o antigo procedimento para fazer o 
' tratamento padrão da mensagem e retorna o seu
' valor para o Windows
NovoProcedimento = CallWindowProc(hWnd, lpfnAntigo, hWnd, lMsg, wParam, lParam)
End Function

Abaixo está a declaração para CallWindowProc usada dentro de NovoProcedimento: 
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Obs.: No código de NovoProcedimento, WM_DESTROY é o nome de uma constante usada para 
identificar a mensagem que a janela recebe antes de ser destruída. Se quiser ter uma 
idéia de parte das mensagens que uma aplicação pode receber, entre na Ajuda e tecle 'WM_' .
Você terá como retorno uma lista de nomes de constantes usadas para identificar mensagens. O
valor para muitas dessas constantes pode ser encontrado no add-in API Text Viewer do VB, mas
para outras só mesmo consultando os arquivos de inclusão do Visual C++ - arquivos com extensão
'.h' presentes no diretório 'Include' do Visual C++. Para essas últimas, no entanto, os valores
não estão no formato de declarações de constantes do VB, você terá que usar apenas o valor
encontrado e fazer a declaração em VB. Examinando as descrições das mensagens, você terá
informações sobre o significado de cada mensagem, dos parâmetros wParam e lParam, que a 
acompanham, e dos valores que podem ser retornados pela função que processe a mensagem. 


Em NovoProcedimento, vemos um Select Case usado para testar os valores do identificador da
mensagem recebido no argumento lMsg. Aqui o programador decide o que fazer com as 
mensagens que chegam. Há vários modos do novo procedimento reagir à mensagem que chega:
processá-la e retornar 0 para o Windows sem deixar que o procedimento antigo dê o tratamento
normal que daria não fosse a subClassing; não processar e chamar o procedimento antigo
para processá-la; não processá-la e retornar ao Windows sem chamar o procedimento antigo;
chamar o procedimento antigo antes de processá-la; chamar o procedimento antigo após
processá-la. 

Para iniciarmos a exemplificação vamos citar duas mensagens que costumam aparecer em
subclassing de janelas: WM_COMMAND e WM_NOTIFY. Estas mensagens são enviadas pelos
controles contidos em uma janela para notificá-la de que algum evento lhes ocorreu. Alguns
controles enviam mensagens WM_COMMAND enquanto outros usam WM_NOTIFY. Como muitos podem ser
os eventos que afetam a um controle, é preciso que haja alguma forma de sabermos qual evento
o controle está notificando. Os parâmetros wParam e lParam contêm esta e outras informações
que usamos para o processamento da mensagem. No caso específico de WM_NOTIFY, o parâmetro
lParam contém o endereço de memória para uma variável composta do tipo NMHDR , que nos 
fornece a informação sobre qual o evento notificado e qual o controle disparador do evento.
Vejamos um exemplo prático do uso de WM_NOTIFY para superarmos uma deficiência do controle
ListView do Visual Basic. Sabemos que ListView dispara um evento ItemClick sempre que 
um item é selecionado, mas nenhum evento nos é dado quando o mesmo item perde a seleção 
numa lista de seleção múltipla. No caso de outros controles da janela estarem exibindo 
informações relativas ao item atualmente selecionado, quando deixa de haver qualquer 
seleção, as informações exibidas perdem o seu sentido e devem ser removidas para não 
confundir o usuário. Através da subClassing e da interceptação das mensagens do 
tipo WM_NOTIFY, podemos detectar eventos LVN_ITEMCHANGED, que a listview dispara na 
janela mãe sempre que um item sofre modificação. Examinando as informações passadas
em lParam, podemos saber qual o item que está sendo mudado e para que estado está
sendo feita esta mudança. Veja abaixo o código usado para isto.

Public Function NovoProcedimento(ByVal hWnd As Long, ByVal lMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Select Case lMsg
  ' Aqui você testa pelos identificadores das
  ' mensagens a serem interceptadas.
  Case WM_NOTIFY:
	 Dim nmh As NMHDR
	 ' obtém os dados para a verificar qual
	 ' evento está sendo notificado pela 
	 ' mensagem. Isto é feito transferindo os
	 ' dados do endereço de memória apontado
	 ' por lParam para uma variável que possa
	 ' ser manipulada em VB
	 MoveMemory nmh, ByVal lParam, Len(nmh)

	 Select Case nmh.code 'testa pelos identificadores 
						  'de eventos
		Case LVN_ITEMCHANGED
		   Dim nmlv As NMLISTVIEW
		   ' No caso de mensagens de notificação 
		   ' enviadas por uma ListView, o parâmetro
		   ' lParam aponta para o endereço
		   ' de memória de uma variável
		   ' do tipo NMLISTVIEW.
		   ' Este tipo contém logo no seu início
		   ' um membro NMHDR e o restante dos 
		   ' membros contêm outras informações 
		   ' sobre o evento.
		   ' Transferimos os dados localizados 
		   ' no endereço de memória apontado por
		   ' lParam para dentro de uma variável 
		   ' que podemos manipular em VB
		   MoveMemory nmlv, ByVal lParam, Len(nmlv)
		   ' A seguir, testamos o membro uChanged
		   ' da variável nmlv, que indica o tipo
		   ' de mudança ocorrida no item.
		   ' A constante LVIF_STATE sinaliza uma
		   ' mudança de estado.
		   If nmlv.uChanged And LVIF_STATE Then
			  ' Verifica se a mudança de estado
			  ' é de um estado de não seleção para
			  ' um estado de seleção: o membro uOldState
			  ' sinaliza o estado anterior, enquanto
			  ' o membro uNewState sinaliza o novo 
			  ' estado do item. A constante LVIS_SELECTED
			  ' é usada para fazer o teste.
			  If (nmlv.uOldState And LVIS_SELECTED) And _
				 (0 = (nmlv.uNewState And LVIS_SELECTED)) Then
				 'A seleção foi removida do item.
				 ' Aqui você escreve o código para
				 ' as providências que devem ser 
				 ' tomadas após a perda de seleção
			  End If
		   End If
	 End Select
  Case WM_DESTROY:
	 'A janela está sendo destruída, 
	 'hora de desfazer a subClassing
	 'Chama o antigo procedimento para 
	 'fazer o tratamento padrão da mensagem
	 Call CallWindowProc(lpfnAntigo, hWnd, lMsg, wParam, lParam)
	 'Restaura o antigo procedimento
	 SetWindowLong hWnd, GWL_WNDPROC, lpfnAntigo
	 Exit Function
End Select
' Se não quiser que o procedimento
' original processe a mensagem interceptada,
' deve sair da função retornando 0 
' antes de chegar aqui.
' Chama o antigo procedimento para 
' fazer o tratamento padrão da mensagem
' e retorna o seu valor para o Windows
NovoProcedimento = CallWindowProc(lpfnAntigo, hWnd, lMsg, wParam, lParam)
End Function

O exemplo acima serve apenas como ilustração. Num caso real o problema é um pouco mais
complicado. Numa listview de seleção múltipla, o usuário pode produzir a desseleção de
múltiplos itens de uma única vez ao clicar num novo item fora da seleção atual ou mesmo 
numa área vazia da lista. Você não vai querer processar um evento de mudança de estado 
para cada item que perder a seleção. O melhor é receber o aviso das mudanças de estado 
quando todos os itens já tiverem assumido seu estado final. É aí que aparecem algumas 
funções da API que você pode usar para comunicar-se com a fila de mensagens da aplicação 
e resolver este problema. 

Ao receber a primeira mensagem de mudança de estado, várias outras já estarão enfileiradas
para serem processadas. Se o seu interesse é o de processar somente a última, quando 
todos os estados de itens estiverem mudados, a solução pode ser criar uma mensagem
própria e postá-la no final da fila. Para criar sua mensagem, você utiliza a API RegisterWindowMessage, que retorna um número capaz de identificá-la de modo único dentro 
do sistema de mensagens do Windows. Este identificador somente fará sentido enquanto sua
aplicação estiver sendo executada, perdendo a validade logo em seguida Feito isto, ao 
receber a primeira mensagem com identificador LVN_ITEMCHANGED, você pode usar a função
da API PostMessage e postar sua mensagens na fila de mensagens da janela. Crie um flag 
para sinalizar o envio da sua mensagem, assim você não vai continuar a enviá-la a cada nova
notificação do evento LVN_ITEMCHANGED que chegar. Quando finalmente sua mensagem chegar,
desligue o flag e examine o estado da listview. Se nada estiver selecionado, limpe os 
controles dependentes; se houver alguma seleção, tome a ação apropriada. Abaixo está 
o código do novo procedimento com todas estas complicações adicionais. Para fazer o 
download de um exemplo completo contendo todas as declarações de APIs e variáveis
usadas clique aqui .


Public Function NovoProcedimento(ByVal hWnd As Long, ByVal lMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case lMsg
  ' Aqui você testa pelos identificadores das
  ' mensagens a serem interceptadas.
  Case WM_NOTIFY:
	 Dim nmh As NMHDR
	 ' obtém os dados para a verificar qual
	 ' evento está sendo notificado pela 
	 ' mensagem. Isto é feito transferindo os
	 ' dados do endereço de memória apontado
	 ' por lParam para uma variável que possa
	 ' ser manipulada em VB
	 MoveMemory nmh, ByVal lParam, Len(nmh)
	 Select Case nmh.code 'testa pelos identificadores de eventos
		Case LVN_ITEMCHANGED
		   Dim nmlv As NMLISTVIEW
		   ' No caso de mensagens de notificação 
		   ' enviadas por uma ListView, o parâmetro
		   ' lParam aponta para o endereço
		   ' de memória de uma variável
		   ' do tipo NMLISTVIEW.
		   ' Este tipo contém logo no seu início
		   ' um membro NMHDR e o restante dos 
		   ' membros contêm outras informações 
		   ' sobre o evento.
		   ' Transferimos os dados localizados 
		   ' no endereço de memória apontado por
		   ' lParam para dentro de uma variável 
		   ' que podemos manipular em VB
		   MoveMemory nmlv, ByVal lParam, Len(nmlv)
		   ' A seguir, testamos o membro uChanged
		   ' da variável nmlv, que indica o tipo
		   ' de mudança ocorrida no item.
		   ' A constante LVIF_STATE sinaliza uma
		   ' mudança de estado.
		   If nmlv.uChanged And LVIF_STATE Then
			  ' Verifica se a mudança de estado
			  ' é de um estado de não seleção para
			  ' um estado de seleção: o membro uOldState
			  ' sinaliza o estado anterior, enquanto
			  ' o membro uNewState sinaliza o novo 
			  ' estado do item. A constante LVIS_SELECTED
			  ' é usada para fazer o teste.
			  If (nmlv.uOldState And LVIS_SELECTED) And _
			  (0 = (nmlv.uNewState And LVIS_SELECTED)) Then
				 'A seleção foi removida do item.
				 'Aqui enviamos nossa mensagem própria - se
				 'ainda não foi enviada.
				 If b_MinhaMsgEnviada = False Then
					 PostMessage hWnd, g_MINHAMSG, 0, 0
					 b_MinhaMsgEnviada = True
				 End If
			  End If
		   End If
	 End Select
  Case g_MINHAMSG:
	 b_MinhaMsgEnviada = False
	 'verifica se há itens selecionados na listview e
	 'faz o que tiver que ser feito
  Case WM_DESTROY:
	 'A janela está sendo destruída, 
	 'hora de desfazer a subClassing
	 'Chama o antigo procedimento para 
	 'fazer o tratamento padrão da mensagem
	 Call CallWindowProc(hWnd, lpfnAntigo, hWnd, lMsg, wParam, lParam)
	 'restaura o antigo procedimento
	 SetWindowLong hWnd, GWL_WNDPROC, lpfnAntigo
	 Exit Function
End Select
' Se não quiser que o procedimento
' original processe a mensagem interceptada,
' deve sair da função retornando 0 
' antes de chegar aqui.
' Chama o antigo procedimento para 
' fazer o tratamento padrão da mensagem
' e retorna o seu valor para o Windows
NovoProcedimento = CallWindowProc(hWnd, lpfnAntigo, hWnd, lMsg, wParam, lParam)
End Function

Chegamos ao ponto em que foi exposto o suficiente para você poder a usar a
técnica de subClassing de janela em seus projetos. Mas, na prática, fazer a 
subClassing tal como foi mostrado até aqui introduz um sério problema para a
depuração do aplicativo. Todas as vezes que algo der errado no seu programa e o 
VB entrar em break mode  para acusar o erro, a subClassing provocará um erro fatal 
que fará o Visual Basic vir abaixo. Para corrigir este problema e possibilitar maior
facilidade de uso desta importante técnica no Visual Basic, a Microsoft criou uma dll 
que deve ser usada somente em tempo de desenvolvimento do aplicativo. Trata-se da 
biblioteca ‘Debug Object for AddressOf Subclassing’. Para poder usá-la em seus projetos,
você deve: 

clicar aqui para baixá-la. Depois, movê-la para o seu diretório System; 
entrar na janela 'Executar' do Windows e digitar regsvr32.exe dbgwproc.dll para 
registrá-la; 
para cada projeto em que for usá-la, marcar uma referência para
‘Debug Object for AddressOf Subclassing’ na lista 'References' do projeto; 
entrar na janela de propriedades do projeto e criar um argumento de compilação
condicional com o nome DEBUGWINDOWPROC . O argumento deve ter seu valor 
igualado a -1 durante a fase de desenvolvimento do aplicativo. Quando for gerar
o executável final, mude o valor deste argumento para 0. Veja na figura abaixo como fica.


 

O argumento de compilação condicional terá seu valor testado dentro do código do
programa para produzir a compilação de alguns trechos de código na fase de 
desenvolvimento e de outros quando da geração do executável final. Quando for gerar
os discos de distribuição da aplicação, não esqueça de remover o arquivo dbgwproc.dll 
da lista de arquivos criada pelo instalador, porque ele já não será mais necessário e 
também de mudar o valor do argumento DEBUGWINDOWPROC para 0 . Caso isto não seja feito,
uma mensagem de aviso será dada sempre que o seu executável iniciar. Agora, vamos ver
o que muda no módulo de subClassing para podermos usar esta biblioteca. 

Do que foi falado até aqui, devemos lembrar que a subClassing se inicia no momento
em que chamamos a função SetWindowLong e passamos um novo endereço de procedimento de 
janela em substituição ao procedimento original da janela que queremos subclassificar. 
SetWindowLong nos retorna o endereço do procedimento de janela que está em uso e o 
armazenamos em uma variável para poder chamá-lo sempre que necessário. Também utilizamos
este endereço de procedimento para restabelecê-lo como o endereço do procedimento da 
janela ao final da subClassing. Ao passar a usar a biblioteca 
'Debug Object for AddressOf Subclassing' passaremos a ter dois tipos de código em 
nosso módulo de subClassing:
um para a fase de desenvolvimento e outro para a fase final do aplicativo. As instruções de
teste #If dirigem o compilador para compilar este ou aquele código, conforme o valor que
tenhamos atribuído ao argumento de compilação condicional DEBUGWINDOWPROC. Este argumento,
então, passa a funcionar como um sinalizador da fase em que estamos na criação do 
aplicativo. No código abaixo - usado para iniciar a subClassing -, as instruções 
que estão na parte em que o #If avalia o argumento DEBUGWINDOWPROC como True serão 
compiladas na fase de desenvolvimento, e as que estão após o #Else serão compiladas
na fase de geração do executável final. As instrucões dentro do #Else serão executadas
quando atribuirmos 0 ao valor de DEBUGWINDOWPROC na janela Project Properties sinalizando
o encerramento da fase de depuração.

#If DEBUGWINDOWPROC Then
  On Error Resume Next
  Set m_SCHook = CreateWindowProcHook
  If Err Then
	 MsgBox Err.Description
	 Err.Clear
	 UnSubClass
	 Exit Sub
  End If
  On Error GoTo 0
  With m_SCHook
	 .SetMainProc AddressOf NovoProcedimento
	 m_wndprcNext = SetWindowLong(hWnd, GWL_WNDPROC, .ProcAddress)
	 .SetDebugProc m_wndprcNext
  End With
#Else
  m_wndprcNext = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf NovoProcedimento)
#End If


No código acima, a parte True do #If é a que merece explicação, porque a parte que 
vem após o #Else não é outra coisa além do que já vínhamos fazendo para iniciar a
subClassing anteriormente. A variável m_SCHook, que aparece recebendo o valor de
retorno da chamada ao método CreateWindowProcHook é declarada para armazenar um objeto
da classe WindowProcHook exportada pela dll dbgwproc. O papel de m_SCHook é o de 
intermediar a subClassing. Ao invés de chamarmos diretamente SetWindowLong, 
como vínhamos fazendo, passamos o endereço de NovoProcedimento para o método SetMainProc da
classe WindowProcHook. Em seguida, fazemos a chamada a SetWindowLong usando como 
argumento para o endereço o valor retornado pela propriedade ProcAddress de WindowProcHook. 
A seguir é chamado o método SetDebugProc e passado como argumento o endereço do antigo
procedimento de janela retornado pela chamada a SetWindowLong. Disto tudo resulta que, 
quando o VB entrar no estado de Break,, WindowProcHook chamará não o procedimento usado
para a subClassing da janela, que estará interrompido pelo VB, mas o procedimento 
original da janela. Isto será suficiente para evitar o indesejável erro fatal que poria o
Visual Basic abaixo impedinto a depuração. 

Observe que, para cada janela subclassificada, você precisará ter um objeto
diferente da classe WindowProcHook. Se você tem várias janelas para subclassificar
e quer uma implementação reutilizável do módulo de subClassing, veja o 
exemplo subcldbg , onde apresentamos uma nova versão do módulo e utilizamos uma 
coleção para armazenar os objetos da classe WindowProcHook que forem sendo criados. 
No módulo, há comentários que ajudam a entender o papel de cada parte do código, 
mas inicie observando os comentários presentes nas declarações das APIs SetProp, GetProp e
RemoveProp.

Últimas observações: quando você subclassifica janelas, está terminantemente proibido
de usar os comandos Stop, End e de interromper a execução da aplicação clicando no botão 
ou menu 'Break'. Não seguir estas regras exporá sua aplicação ao risco de terminar
anormalmente e levar o Visual Basic junto com ela.

Esperamos que este breve artigo lhe tenha sido útil e lhe abra novas possibilidades 
para exercer mais amplo controle das suas aplicações.

Codelines Ltda. (http://www.codelines.com)