Subclasificando ventanas

Pues eso: subclasificando que es gerundio
En este ejemplo para los mensajes recibidos al seleccionar un menú.

 

Revisión del 26/Jun/98



Es que me he "picado" un poco con esto de interceptar los mensajes de Windos y he empezado a "indagar" en este tema, que antes sólo se podía hacer con controles OCX especiales para esta tarea al estilo del MsgBlaster y otros parecidos, pero que ahora es posible con código de Visual Basic gracias a AddressOf y al API de Windows, por supuesto.
Ni que decir tiene que sólo con el VB5 y superior.

El ejemplo este que vamos a ver es para mostrar en un label un mensaje cada vez que seleccionemos un item de un menú.
Los valores y el "conocimiento" de los valores que hay que usar, los he tomado de un ejemplo que la gente de Softcircuits tienen (o tenían) en sus páginas y que acompañaban a un control OCX para "subclasificar" los mensajes de Windows.

La parte importante de este ejemplo está en el módulo bas, que lo he hecho genérico para que se pueda usar con cualquier formulario y para los mensajes que queramos, ya que esos mensajes se comprueban en el propio formulario.
En los ejemplos que he visto por la red, normalmente los mensajes recibidos se procesan en el procedimiento que "intercepta" los mensajes de Windows, pero lo he ampliado para no tener que ir creando un módulo BAS para cada caso particular.
El único requisito es tener un procedimiento público en el form que se llame: miMSG y que reciba los parámetros para saber que es lo que Windows nos quiere decir: uMSG, wParam y lParam.

 
Nota:
En el módulo que uso en este ejemplo, (el primero que hago de forma más o menos genérica), sólo se puede controlar un formulario, es decir, si en el mismo proyecto tienes varios formularios en los que quieres "interceptar" los mensajes recibidos de Windows, con este módulo BAS no lo puedes hacer, salvo que dejes de interceptar en uno y lo hagas en otro, (más abajo explico cómo podrías hacerlo), aunque ya estoy "por la labor" de crear un módulo genérico que permita manejar varios forms e incluso saber que mensajes "quiere" procesar el formulario.
Pero esto será en otra ocasión, ahora estoy "estudiando" el tema.
 

Ahora vamos a ver cómo funciona todo esto, aunque antes un par de consejos y un poco de explicación.

Antes de empezar a "recibir" o procesar los mensajes recibidos, hay que indicarle a Windows que empiece la función, esto se hace mediante un "gancho" (Hook que llaman los ingleses a esto); esto siempre hay que hacerlo antes de nada y para indicarle a Windows que deje de estar enganchados a nosotros, debemos quitar ese gancho.
Para estas dos tareas, el módulo BAS tiene dos procedimientos: HookForm y unHookForm, (ahora explicaré cómo usarlos).
Lo importante de todo esto es que se debe llamar a unHookForm cuando no necesitemos que Windows nos envíe los mensajes. Si estamos probando en el IDE es importantísimo quitar el gancho, sino el VB se quedará más colgado que... (busca tu un ejemplo, no lo voy a hacer yo todo...), así que si te pones a experimentar con esto de la subclasificación y esas cosas, es recomendable que le des a guardar de continuo, sobre todo antes de darle a F5 y también te recomendaría que compilaras de forma completa, bien pulsando Ctrl+F5 o bien modificando el modo de compilación en el menú Herramientas/Opciones solapa General y en el cuadro Compilar quitar la marca en Compilar bajo petición, si tienes la versión inglesa del VB, será: Tools/Options/General/Compile on Demand
Con esto lo que consigues es que si hay alguna variable no declarada o algo "raro", lo detecte antes de ejecutar la aplicación.

Ahora te voy a explicar un poco cómo funcionan los procedimientos HookForm y unHookForm que he incluido en el módulo BAS.
El HookForm recibe un form como parámetro, esto lo hago para tener una referencia al form de llamada, con idea de que se pueda llamar como te de la gana y no tener que forzarte a nada en particular, salvo la de crear un procedimiento público que se llame miMSG, tal como he explicado antes.
En este procedimiento se comprueba si antes se ha asignado el valor de la variable que "apunta" al form, de ser así, se llama a unHookForm con idea de quitar el "gancho" anterior, esto lo he puesto por si quisieras tener varios forms subclasificados, bueno realmente no podrías, ya que sólo se permite uno a la vez, si quisieras tener varios, tendrías que crear varios procedimientos Hook y unHook y otros tantos para "interceptar" los mensajes de Windows, pero por simplicidad vamos a dar por hecho de que sólo se subclasifica un form a la vez. Y a pesar de que esto se de por hecho, el procedimiento se encarga de comprobar de que así sea.

Una vez que no necesitemos recibir los mensajes de Windows, debemos llamar a unHookForm, esto se suele hacer al cerrar el formulario, lo he puesto en el QueryUnload para que no haya más problemas de los que pudiera haber.

En el procedimiento miMSG del form es donde debemos "evaluar" que mensajes de los recibidos queremos "procesar", en el ejemplo de hoy es WM_MENUSELECT, en otras ocasiones iremos viendo otros, todo dependerá de la información que pueda "captar" por la red o por los artículos del MSDN, aunque si no quieres esperar, puedes darte una vueltecilla por la MSDN Library que está en la red y buscar artículos referentes a este tema.

Vamos a ver los listados y a pasar a la acción.

En primer lugar el del módulo BAS:

'------------------------------------------------------------------
'Módulo para subclasificación (subclassing)             (26/Jun/98)
'
'
'©Guillermo 'guille' Som, 1998
'------------------------------------------------------------------
Option Explicit

'Para almacenar el form de llamada y el hWnd del form
Private elForm As Form
Private elhWnd As Long

Public PrevWndProc As Long
Public Const GWL_WNDPROC As Long = (-4&)

Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
    (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, _
    ByVal MSG As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
    (ByVal hWnd As Long, ByVal nIndex As Long, _
    ByVal dwNewLong As Long) As Long


Public Function WndProc(ByVal hWnd As Long, ByVal uMSG As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    WndProc = CallWindowProc(PrevWndProc, hWnd, uMSG, wParam, lParam)
    'Los mensajes de Windows llegarán aquí
    'Lo que hay que hacer es "capturar" los que se necesiten,
    'en este caso se devuelven los mensajes al form, usando para
    'ello un procedimiento público llamado miMSG con los
    'siguientes parámetros:
    'ByVal uMSG As Long, ByVal wParam As Long, ByVal lParam As Long
    'la copia del form se hará al crear el Hook, es importante que
    'sólo se subclasifiquen ventanas cuando no halla ninguna activa
    '(de esto se encarga HookForm y unHookForm)
    '
    'Nos aseguramos que el form aún está disponible
    If Not elForm Is Nothing Then
        elForm.miMSG uMSG, wParam, lParam
    End If
End Function

Public Sub HookForm(ByVal unForm As Form)
    'unForm será el form de llamada,
    'para llamar a este procedimiento: HookForm Me
    '
    'Si aún existía una subclasificación
    If Not elForm Is Nothing Then
        unHookForm
    End If
    Set elForm = unForm
    elhWnd = unForm.hWnd
    PrevWndProc = SetWindowLong(elhWnd, GWL_WNDPROC, AddressOf WndProc)
    'Es importante recordar que se debe llamar a unHookForm antes
    'de cerrar el form... sobre todo si se usa en el IDE
End Sub

Public Sub unHookForm()
    Dim Ret As Long
    'Para llamar a este procedimiento: unHookForm
    '
    'Siempre se debe llamar primero a HookForm y después se llama
    'a este otro para dejar de interceptar los mensajes de Windows
    'Si haces pruebas en el IDE, no te olvides de llamar a este
    'procedimiento, cerrando la aplicación con el botón "Stop"
    'no se llamará a este procedimiento.
    '
    'Si el valor de elhWnd es cero es que no se ha usado
    If elhWnd <> 0 Then
        Ret = SetWindowLong(elhWnd, GWL_WNDPROC, PrevWndProc)
    End If
    'Quitamos la referencia al form
    Set elForm = Nothing
    'Asignamos el valor cero a elhWnd
    elhWnd = 0
End Sub

Ahora vamos a ver el código usado en el Form:
El form, además de los menús indicados (ver los Case xx de miMSG), tendrá un Label para mostrar el mensaje de la selección realizada.

'------------------------------------------------------------------
'Prueba de subclasificación                             (26/Jun/98)
'Se comprobarán los mensajes enviados por cambio en la selección
'de los menús
'
'©Guillermo 'guille' Som, 1998
'------------------------------------------------------------------
Option Explicit

'Este es el mensaje enviado por Windows cuando se selecciona
'un menú
Const WM_MENUSELECT As Long = &H11F&

'Para el menú del sistema
Const SC_RESTORE  As Long = &HF120&
Const SC_MOVE     As Long = &HF010&
Const SC_SIZE     As Long = &HF000&
Const SC_MINIMIZE As Long = &HF020&
Const SC_MAXIMIZE As Long = &HF030&
Const SC_CLOSE    As Long = &HF060&


Private Sub Form_Load()
    'Iniciar la subclasificación
    HookForm Me
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    'Terminar la subclasificación
    unHookForm
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Set Form1 = Nothing
End Sub


Public Sub miMSG(ByVal uMSG As Long, ByVal wParam As Long, ByVal lParam As Long)
    Dim sMsg As String
    'Aquí llegarán los mensajes que se quieren interceptar
    'usar con Select Case o varios If... ElseIf...
    If uMSG = WM_MENUSELECT Then
        Select Case wParam And &HFFFF&
            'Estos son los valores definidos por VB
            'Siempre empiezan por 1
            'Case 1
            '    sMsg = "El menú de ficheros"
            Case 2
                sMsg = "Has seleccionado Abrir..."
            Case 3
                sMsg = "Has seleccionado Guardar"
            Case 4
                sMsg = "Has seleccionado Guardar como..."
            Case 6
                sMsg = "Has seleccionado Imprimir..."
            Case 8
                sMsg = "Has seleccionado Salir"
            '
            'Case 9
            '    sMsg = "El menú de edición"
            Case 10
                sMsg = "Has seleccionado Deshacer"
            Case 12
                sMsg = "Has seleccionado Copiar"
            Case 13
                sMsg = "Has seleccionado Cortar"
            Case 14
                sMsg = "Has seleccionado Pegar"
            Case 16
                sMsg = "Has seleccionado Seleccionar todo..."
            'Estos corresponden al menú del sistema
            Case SC_RESTORE
                sMsg = "Restaurar la ventana"
            Case SC_MOVE
                sMsg = "Mover la ventana con el teclado"
            Case SC_SIZE
                sMsg = "Cambiar el tamaño de la ventana con el teclado"
            Case SC_MINIMIZE
                sMsg = "Minimizar la ventana"
            Case SC_MAXIMIZE
                sMsg = "Maximizar la ventana"
            Case SC_CLOSE
                sMsg = "Cerrar esta ventana y de paso el programa"
            Case Else
                sMsg = ""
        End Select
    'Mostrar el mensaje en el Label
        Label1 = sMsg
    End If
End Sub

Volver a Visual Basic Avanzado

ir al índice