Conheça SilentIdea e melhore seu aprendizado de novos conhecimentos

Visual Basic, VB .NET, ASP, Active X, Access, SQL Server

Subclassificação 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 subclassificação 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 subclassificação 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 subclassificação, é 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 da subclassificação 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 da subclassificação 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 a subclassificação, 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 subclassificação.
         ' 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 subclassificação; 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 subclassificações 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 subclassificação 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 subclassificação
         '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 subclassificação
         '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 subclassificação de janela em seus projetos. Mas, na prática, fazer a subclassificação 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 subclassificação 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:

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 subclassificação para podermos usar esta biblioteca.

    Do que foi falado até aqui, devemos lembrar que a subclassificação 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 subclassificação. Ao passar a usar a biblioteca 'Debug Object for AddressOf Subclassing' passaremos a ter dois tipos de código em nosso módulo de subclassificação: 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 subclassificação -, 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 subclassificação 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 subclassificação. 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 subclassificação 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 subclassificação, 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 tutorial lhe tenha sido útil e lhe abra novas possibilidades para exercer mais amplo controle das suas aplicações.

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

Caso queira publicar este tutorial em qualquer mídia, favor manter a referência ao site da Codelines.

Conheça SilentIdea e melhore seu aprendizado de novos conhecimentos