Clase para subclasificar formularios

Clase para añadir nuevos eventos a los formularios, mediante subclasificación.
En teoría valdrá para cualquier control que tenga HWND.

 

Publicado: 18/Abr/2001
Actualizado: 21/Abr/2001



El código que te muestro a continuación puede servirte para añadir nuevos eventos a los formularios.
Si quieres, puedes modificar la clase para añadir nuevos eventos, aunque los que ahora mismo tiene, son más que suficientes para ampliar la funcionalidad de los formularios.

A grandes rasgos, lo que la clase hace es subclasificar un formulario y usar eventos para "lanzar" los eventos que la clase produce, de forma que puedas saber, por ejemplo si la posición del formulario ha cambiado, si el ratón entra o sale del formulario, etc.
No te voy a explicar cómo funciona... (al menos por ahora), ya que llevo bastante tiempo queriendo publicar esta clase, (desde el 21 de Marzo de 1999), y no lo he hecho por la pereza que da algunas veces tener que explicar todo... espero que me disculpes y te prometo que algún día de estos explicaré cómo funciona todo...

Para usar la clase, añade la clase: cWndSubclass.cls y el módulo: MSubClass.bas al proyecto y declara una variable con WithEvents, para poder interceptar los eventos producidos.
Al cargarse el formulario crea la instancia y asigna el formulario a subclasificar usando el método Hook.
Cuando descargues el formulario hay que llamar al mismo método, pero sin parámetros.

Nota:
Es muy importante hacer la llamada al método Hook, (sin parámetros), al descargar el formulario, si no quieres que se quede el gancho perdido en el limbo.

Nota adicional:
Te recomiendo guardar todo antes de probar a subclasificar, por lo que pueda ocurrir.

 

Los eventos que se añaden al formulario:

Esta es una lista de los eventos que produce la clase, además del procedimiento MSG al que irán a parar todos los mensajes no procesados.

Evento Descripción
Activate Se ha restaurado el formulario.
Deactivate Se ha minimizado el formulario.
DisplayChange Ha cambiado la pantalla.
FontChange La fuente ha cambiado.
LowMemory Hay poca memoria.
MenuSelected Se ha seleccionado un elemento de un menú.
MouseEnter El cursor del ratón ha entrado en el formulario.
MouseEnterOn El cursor del ratón ha entrado en el control especificado.
MouseLeave El cursor del ratón ha salido del formulario.
MouseLeaveOn El cursor del ratón ha salido del control indicado.
MouseWheel Se está usando la rueda del ratón.
Move Se ha movido el formulario.
MSG Recibe los mensajes no interceptados por la clase.
SetCursor El cursor del ratón está en otra ventana y no es capturado por el formulario.
WindowPosChange Ha cambiado la posición del formulario, los valores son los extremos exactos con respecto a la pantalla.

En el código de la clase, encontrarás explicación más amplia... realmente la descripción de cada mensaje... aunque en inglés, ya que está sacado de la MSDN library.


El código:

Este es el código del formulario, a continuación estará el código de la clase y del módulo BAS.
Espero que te sea de utilidad y que me mandes los comentarios que creas conveniente sobre esta clase y sobre todo las posibles mejoras y fallos que puedas encontrar... que, casi seguro que los habrá.

Nos vemos.
Guillermo
P.S.
Usa este link para bajarte el listado de la clase y del formulario de prueba: subclass_form.zip 14.3 KB


Vamos a ver los listados completos.

En primer lugar el del formulario de prueba:


'------------------------------------------------------------------------------
' Form de prueba                                                    (24/Nov/00)
' Revisado (probado) para publicar en mis páginas                   (18/Abr/01)
'
' Prueba de subclasificación de formularios para añadir nuevos eventos, etc.
'
' ©Guillermo 'guille' Som, 1998-2001
'------------------------------------------------------------------------------

Option Explicit

Private WithEvents mSubclassForm As cWndSubclass

Private Sub mSubclassForm_Activate()
    ' Cuando se activa
    Label2(2).Caption = "Activate"
End Sub

Private Sub mSubclassForm_Deactivate()
    ' Cuando se desactiva
    Label2(2).Caption = "Deactivate"
End Sub

Private Sub mSubclassForm_DisplayChange(ByVal BitsPerPixel As Long, ByVal cxScreen As Long, ByVal cyScreen As Long)
    ' DisplayChange
End Sub

Private Sub mSubclassForm_FontChange()
    ' FontChange
End Sub

Private Sub mSubclassForm_LowMemory()
    ' LowMemory
End Sub

Private Sub mSubclassForm_MenuSelected(ByVal mnuItem As Long, mnuFlags As eWSCMF, ByVal hMenu As Long)
    ' MenuSelected
    List1.AddItem "Has seleccionado el menú: " & mnuItem
End Sub

Private Sub mSubclassForm_MouseEnter()
    ' Cuando entra el ratón en el formulario
    Label1 = "MouseEnter"
End Sub

Private Sub mSubclassForm_MouseEnterOn(unControl As Object)
    ' Cuando entra el ratón en uno de los controles
    Label1.Caption = "MouseEnterOn: " & unControl.Name
End Sub

Private Sub mSubclassForm_MouseLeave()
    ' Cuando sale el ratón
    Label1 = "MouseLeave"
End Sub

Private Sub mSubclassForm_MouseLeaveOn(unControl As Object)
    ' Cuando sale el ratón de un control
    Label1.Caption = "MouseLeaveOn: " & unControl.Name
End Sub

Private Sub mSubclassForm_MouseWheel(ByVal wKeys As Long, ByVal zDelta As Long, ByVal xPos As Long, ByVal yPos As Long)
    ' Cuando se mueve la rueda del ratón
    Label2(2).Caption = "Se ha movido la rueda del ratón"
End Sub

Private Sub mSubclassForm_Move(ByVal wLeft As Long, ByVal wTop As Long)
    ' Cuando se mueve el formulario
    Label2(0).Caption = "Posición del formulario: Left= " & wLeft & ", Top= " & wTop
End Sub

Private Sub mSubclassForm_MSG(ByVal uMSG As eWSCWM, ByVal wParam As Long, ByVal lParam As Long)
    ' Se supone que aquí llegarán todos los mensajes no capturados.
'    If uMSG = WM_MOVE Then
'        List1.AddItem "Moviendo el formulario..."
'    End If
'    '
'    If uMSG = WM_SETFOCUS Then
'        List1.AddItem "WM_SETFOCUS"
'    ElseIf uMSG = WM_MOUSEWHEEL Then
'        ' cuando se mueve la rueda del ratón (que tenga rueda, claro...)
'        List1.AddItem "WM_MOUSEWHEEL"
'    End If
End Sub

Private Sub mSubclassForm_SetCursor(unControl As Object, ByVal HitTest As eWSCHitTest, ByVal MouseMsg As Long)
    'Label2(2).Caption = "SetCursor: " & unControl.Name & ", HitTest:" & HitTest & ", MouseMsg: " & MouseMsg
End Sub

Private Sub mSubclassForm_WindowPosChanged(ByVal wLeft As Long, ByVal wTop As Long, ByVal wWidth As Long, ByVal wHeight As Long)
    ' Cuando cambia la posición de la ventana
    Label2(1).Caption = "WndPosChanged: L:" & wLeft & ", T:" & wTop & ", W:" & wWidth & ", H:" & wHeight
End Sub

Private Sub Form_Load()
    Dim i As Long
    
    For i = 0 To 2
        Label2(i).Caption = ""
    Next
    
    Set mSubclassForm = New cWndSubclass
    '
    ' Iniciar la subclasificación
    mSubclassForm.Hook Me
End Sub

Private Sub Form_Unload(Cancel As Integer)
    ' Desactivar el gancho
    mSubclassForm.Hook
End Sub

Private Sub mnuAbrir_Click()
    Label2(2).Caption = "Has seleccionado el menú Abrir"
End Sub

Private Sub mnuGuardar_Click()
    Label2(2).Caption = "Has seleccionado el menú Guardar"
End Sub

Private Sub mnuSalir_Click()
    Label2(2).Caption = "Has seleccionado el menú Salir"
    If MsgBox("¿quieres terminar el programa?", vbYesNo + vbQuestion, "Cerrar el ejemplo") = vbYes Then
        Unload Me
    End If
End Sub

El código del módulo BAS:


'------------------------------------------------------------------------------
' Módulo para subclasificación (subclassing)                        (26/Jun/98)
' Revisado (probado) para publicar en mis páginas                   (18/Abr/01)
'
' Modificado para usar con la clase CWndSubClass                    (21/Mar/99)
'
' ©Guillermo 'guille' Som, 1998-2001
'
' Para más información sobre subclasificación:
' En la documentación de Visual Basic:
'   Pasar punteros de función a los procedimientos de DLL y a las bibliotecas de tipos
' En la MSDN Library (o en la Knowledge Base):
'   HOWTO: Subclass a UserControl
'       Article ID: Q179398
'   HOWTO: Hook Into a Window's Messages Using AddressOf
'       Article ID: Q168795
'   HOWTO: Build a Windows Message Handler with AddressOf in VB5
'       Article ID: Q170570
'------------------------------------------------------------------------------
Option Explicit

' Un array de la clase que se usará para subclasificar ventanas
' y el último elemento de clases en el array; empieza a contar por uno
Private mWSC() As cWndSubclass          ' Array de clases
Private mnWSC As Long                   ' Número de ventanas subclasificadas

Private Const GWL_WNDPROC = (-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
    ' 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 a la clase, usando para
    ' ello un procedimiento público llamado unMSG con los siguientes parámetros:
    ' ByVal uMSG As Long, ByVal wParam As Long, ByVal lParam As Long
    '
    ' Para un mejor uso, usar la clase en el formato:
    '   Dim WithEvents laClase As CWndSubClass
    '
    Static i As Long
    
    ' Buscar el índice de esta clase en el array
    ' NOTA: Esto se hará para cada uno de los mensajes recibidos,
    '       por tanto, no sería conveniente tener demasiadas ventanas o controles
    '       subclasificados, con idea de que no tarde demasiado en procesarlos
    '$Por hacer:
    '   Sería conveniente poner un límite máximo de ventanas a subclasificar
    i = IndiceClase(hWnd)
    
    If i Then
        With mWSC(i)
            WndProc = CallWindowProc(.PrevWndProc, hWnd, uMSG, wParam, lParam)
            ' Producir el evento del mensaje recibido
            .unMSG uMSG, wParam, lParam
        End With
    End If
End Function

Public Sub Hook(ByVal WSC As cWndSubclass, ByVal unControl As Object)
    ' Subclasificar la ventana o control indicado
    
    '--------------------------------------------------------------------------
    ' Nota:
    ' En este procedimiento no se hace chequeo de que el objeto pasado tenga
    ' la propiedad hWnd, ya que se comprueba en el método Hook de la clase,
    ' por tanto no se debería llamar a este método sin antes hacer una comprobación
    ' de que estamos pasando un objeto-ventana (que tenga la propiedad hWnd)
    '
    
    ' Comprobar si ya está subclasificada esta ventana
    Dim claseActual As Long
    Dim claseLibre As Long
    
    ' Buscar el índice de esta clase en el array
    ' y si hay alguna clase liberada anteriormente
    claseActual = IndiceClase(unControl.hWnd, claseLibre)
    
    If claseActual = 0 Then
        ' Si hay un índice que ya no se usa...
        If claseLibre Then
            ' se usará ese índice
            claseActual = claseLibre
        Else
            ' Crear una nueva clase
            mnWSC = mnWSC + 1
            ReDim Preserve mWSC(1 To mnWSC)
            claseActual = mnWSC
        End If
    End If
    
    ' Aquí se está haciendo referencia a una clase ya existente,
    ' para que no queden referencias "sueltas", en el evento Terminate de la clase
    ' se llama al procedimiento de liberación de la subclasificación en el que se
    ' borrará la referencia a la clase indicada, por tanto no se debería modificar
    ' esa forma de actuar.
    '--------------------------------------------------------------------------
    ' Nota:
    '   En lugar de hacer una referencia a la clase, se podría usar un puntero a
    '   la misma usando ObjPtr, pero esto implicaría usar la función CopyMemory
    '   para poder acceder a las propiedades de la clase, y no sé si esto
    '   incrementaría el tiempo de procesamiento, pero...
    '   "los expertos" así lo hacen... así que se supone que tendrá sus ventajas;
    '   aunque si se siguen "las reglas" indicadas, no tendría que dar problemas.
    '   Además la intención de esta clase es formar parte de un componente (DLL)
    '   y el código no estaría disponible a las aplicaciones cliente...
    '   Por eso, te aconsejo que no hagas experimentos,
    '   si no sabes las consecuencias que esa pruebas pueden tener, el que avisa...
    '
    ' Ver el siguiente artículo en la Knowledge Base de Microsoft para un ejemplo
    ' de un UserControl subclasificado usando punteros a objetos:
    '   HOWTO: Subclass a UserControl, Article ID: Q179398
    '
    Set mWSC(claseActual) = WSC
    
    ' Subclasificar la ventana, (form o control), pasada como parámetro y
    ' guardar el procedimiento anterior
    With mWSC(claseActual)
        .hWnd = unControl.hWnd
        .PrevWndProc = SetWindowLong(.hWnd, GWL_WNDPROC, AddressOf WndProc)
    End With
End Sub

Public Sub unHook(ByVal WSC As cWndSubclass)
    ' Des-subclasificar la clase indicada
    Static claseActual As Long
    
    ' Buscar el índice de esta clase en el array
    claseActual = IndiceClase(WSC.hWnd)
    
    ' Si ya estaba subclasificada esta clase
    If claseActual Then
        With mWSC(claseActual)
            ' Restaurar la función anterior de procesamiento de mensajes
            Call SetWindowLong(.hWnd, GWL_WNDPROC, .PrevWndProc)
            ' Poner a cero el indicador de que se está usando
            .hWnd = 0&
        End With
        
        ' Quitar la referencia a esta clase
        Set mWSC(claseActual) = Nothing
        
        ' Si es la última del array...
        If mnWSC = claseActual Then
            ' Eliminar este item y ajustar el array
            mnWSC = mnWSC - 1
            ' Si no hay más, eliminar el array
            If mnWSC = 0 Then
                Erase mWSC
            Else
                ' Ajustar el número de elementos del array
                ReDim Preserve mWSC(1 To mnWSC)
            End If
        End If
    End If
End Sub

Private Function IndiceClase(ByVal elhWnd As Long, Optional ByRef Libre As Long = 0) As Long
    ' Este procedimiento buscará el índice de la clase que tiene el hWnd indicado
    ' También, si se especifica, devolverá el índice de una clase que esté libre.
    ' Nota: Es importante que el último parámetro sea por referencia,
    '       ya que en él se devolverá el valor del índice libre.
    '
    Static i As Long
    
    IndiceClase = 0
    
    ' Recorrer todo el array
    For i = 1 To mnWSC
        With mWSC(i)
            ' Si coinciden los hWnd, es que ya se está usando una subclasificación
            If .hWnd = elhWnd Then
                ' usar esta misma clase
                ' pero si el hWnd es cero, será uno libre
                If elhWnd = 0 Then
                    Libre = i
                Else
                    IndiceClase = i
                End If
                Exit For
                
            ' Comprobar si hay algún "hueco" en el array,
            ' por ejemplo de una clase previamente liberada.
            ' Hay que tener en cuenta que estos procedimientos están en un BAS
            ' y sus valores se mantienen entre varias llamadas a las clases.
            ElseIf .hWnd = 0& Then
                Libre = i
            End If
        End With
    Next
End Function

El código de la clase:


'------------------------------------------------------------------------------
' cSubclass                                                         (21/Mar/99)
'
' Clase para subclasificar ventanas (formularios o controles con hWnd)
'
' Última revisión:  08/Ago/99
' Revisado (probado) para publicar en mis páginas                   (18/Abr/01)
'
' ©Guillermo 'guille' Som, 1999-2001
'------------------------------------------------------------------------------
' Basado en el código de un ejemplo del 26/Jun/98
' para detectar los mensajes del menú seleccionado.
'
' También he usado como fuente de inspiración un artículo de Francesco Balena:
'   Subclass Forms to Create New Events
' Aunque no me gustó la forma de enfocar la forma de gestionar varias instancias
' de la clase y me decidí por el enfoque del array.
' La verdad es que algunos de los eventos producidos están "inspirados" en el
' artículo de Balena.
' Todo hay que decirlo: La clase casi la terminé antes de conseguir el código
' del citado artículo, si hubiese tenido ese código antes, seguramente la
' hubiese hecho de otra forma... ¡menos mal que no fue así!
' ya que al menos me "esforcé" en hacerlo por mis medios...
'------------------------------------------------------------------------------
Option Explicit

' Para los TwipsPerPixels del objeto Screen
Private twX As Long, twY As Long
' Para poder interceptar el mensaje WM_MOUSELEAVE
Private mtTME As tTRACKMOUSEEVENT

Private mWndType As Object              ' La ventana que se subclasifica
Private mhWnd As Long                   ' El hWnd de la ventana subclasificada
Private mPrevWndProc As Long            ' La función anterior de procesamiento
                                        ' de mensajes de la ventana a subclasificar

'------------------------------------------------------------------------------
' Enumeración con los mensajes de windows (Window Messages)
'
' Esta lista está sacada de WinUser.h,
' algunas declaraciones están en el fichero Win32API.txt
'
' En los casos que se indique #if ... es que son para otras versiones de Windows:
' #if(WINVER >= 0x0400)     Será Windows NT 4 y superior y Windows 98
' #if(WINVER >= 0x0500)     Será Windows 2000 y superior (Windows NT 5) (creo)
Public Enum eWSCWM

    WM_NULL = &H0
    WM_CREATE = &H1
    WM_DESTROY = &H2
    WM_MOVE = &H3
    WM_SIZE = &H5
    
    WM_ACTIVATE = &H6
    
    WM_SETFOCUS = &H7
    WM_KILLFOCUS = &H8
    WM_ENABLE = &HA
    WM_SETREDRAW = &HB
    WM_SETTEXT = &HC
    WM_GETTEXT = &HD
    WM_GETTEXTLENGTH = &HE
    WM_PAINT = &HF
    WM_CLOSE = &H10
    WM_QUERYENDSESSION = &H11
    WM_QUIT = &H12
    WM_QUERYOPEN = &H13
    WM_ERASEBKGND = &H14
    WM_SYSCOLORCHANGE = &H15
    WM_ENDSESSION = &H16
    WM_SHOWWINDOW = &H18
    WM_WININICHANGE = &H1A
    ' #if(WINVER >= 0x0400)
    WM_SETTINGCHANGE = WM_WININICHANGE
    ' #endif /* WINVER >= 0x0400 */
    WM_DEVMODECHANGE = &H1B
    WM_ACTIVATEAPP = &H1C
    WM_FONTCHANGE = &H1D
    WM_TIMECHANGE = &H1E
    WM_CANCELMODE = &H1F
    WM_SETCURSOR = &H20
    WM_MOUSEACTIVATE = &H21
    WM_CHILDACTIVATE = &H22
    WM_QUEUESYNC = &H23
    
    WM_GETMINMAXINFO = &H24
    
    WM_PAINTICON = &H26
    WM_ICONERASEBKGND = &H27
    WM_NEXTDLGCTL = &H28
    WM_SPOOLERSTATUS = &H2A
    WM_DRAWITEM = &H2B
    WM_MEASUREITEM = &H2C
    WM_DELETEITEM = &H2D
    WM_VKEYTOITEM = &H2E
    WM_CHARTOITEM = &H2F
    WM_SETFONT = &H30
    WM_GETFONT = &H31
    WM_SETHOTKEY = &H32
    WM_GETHOTKEY = &H33
    WM_QUERYDRAGICON = &H37
    WM_COMPAREITEM = &H39
    ' #if(WINVER >= 0x0500)
    WM_GETOBJECT = &H3D
    ' #endif /* WINVER >= 0x0500 */
    WM_COMPACTING = &H41
    
    WM_WINDOWPOSCHANGING = &H46
    WM_WINDOWPOSCHANGED = &H47
    
    WM_POWER = &H48
    
    WM_COPYDATA = &H4A
    WM_CANCELJOURNAL = &H4B
    
    ' #if(WINVER >= 0x0400)
    WM_NOTIFY = &H4E
    WM_INPUTLANGCHANGEREQUEST = &H50
    WM_INPUTLANGCHANGE = &H51
    WM_TCARD = &H52
    WM_HELP = &H53
    WM_USERCHANGED = &H54
    WM_NOTIFYFORMAT = &H55
    '
    '#define NFR_ANSI                             1
    '#define NFR_UNICODE                          2
    '#define NF_QUERY                             3
    '#define NF_REQUERY                           4
    '
    WM_CONTEXTMENU = &H7B
    WM_STYLECHANGING = &H7C
    WM_STYLECHANGED = &H7D
    WM_DISPLAYCHANGE = &H7E
    WM_GETICON = &H7F
    WM_SETICON = &H80
    ' #endif /* WINVER >= 0x0400 */
    '
    WM_NCCREATE = &H81
    WM_NCDESTROY = &H82
    WM_NCCALCSIZE = &H83
    WM_NCHITTEST = &H84
    WM_NCPAINT = &H85
    WM_NCACTIVATE = &H86
    WM_GETDLGCODE = &H87
    WM_NCMOUSEMOVE = &HA0
    WM_NCLBUTTONDOWN = &HA1
    WM_NCLBUTTONUP = &HA2
    WM_NCLBUTTONDBLCLK = &HA3
    WM_NCRBUTTONDOWN = &HA4
    WM_NCRBUTTONUP = &HA5
    WM_NCRBUTTONDBLCLK = &HA6
    WM_NCMBUTTONDOWN = &HA7
    WM_NCMBUTTONUP = &HA8
    WM_NCMBUTTONDBLCLK = &HA9
    '
    'WM_KEYFIRST = &H100
    WM_KEYDOWN = &H100
    WM_KEYUP = &H101
    WM_CHAR = &H102
    WM_DEADCHAR = &H103
    WM_SYSKEYDOWN = &H104
    WM_SYSKEYUP = &H105
    WM_SYSCHAR = &H106
    WM_SYSDEADCHAR = &H107
    'WM_KEYLAST = &H108
    '
    ' #if(WINVER >= 0x0400)
    WM_IME_STARTCOMPOSITION = &H10D
    WM_IME_ENDCOMPOSITION = &H10E
    WM_IME_COMPOSITION = &H10F
    'WM_IME_KEYLAST = &H10F
    ' #endif /* WINVER >= 0x0400 */
    '
    WM_INITDIALOG = &H110
    WM_COMMAND = &H111
    WM_SYSCOMMAND = &H112
    WM_TIMER = &H113
    WM_HSCROLL = &H114
    WM_VSCROLL = &H115
    WM_INITMENU = &H116
    WM_INITMENUPOPUP = &H117
    WM_MENUSELECT = &H11F
    WM_MENUCHAR = &H120
    WM_ENTERIDLE = &H121
    '
    ' #if(WINVER >= 0x0500)
    WM_MENURBUTTONUP = &H122
    WM_MENUDRAG = &H123
    WM_MENUGETOBJECT = &H124
    WM_UNINITMENUPOPUP = &H125
    WM_MENUCOMMAND = &H126
    ' #endif /* WINVER >= 0x0500 */
    '
    WM_CTLCOLORMSGBOX = &H132
    WM_CTLCOLOREDIT = &H133
    WM_CTLCOLORLISTBOX = &H134
    WM_CTLCOLORBTN = &H135
    WM_CTLCOLORDLG = &H136
    WM_CTLCOLORSCROLLBAR = &H137
    WM_CTLCOLORSTATIC = &H138
    
    'WM_MOUSEFIRST = &H200
    WM_MOUSEMOVE = &H200
    WM_LBUTTONDOWN = &H201
    WM_LBUTTONUP = &H202
    WM_LBUTTONDBLCLK = &H203
    WM_RBUTTONDOWN = &H204
    WM_RBUTTONUP = &H205
    WM_RBUTTONDBLCLK = &H206
    WM_MBUTTONDOWN = &H207
    WM_MBUTTONUP = &H208
    WM_MBUTTONDBLCLK = &H209
    ' #if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)
    WM_MOUSEWHEEL = &H20A
    'WM_MOUSELAST = &H20A
    ' #else
    'WM_MOUSELAST = &H209
    ' #endif /* if (_WIN32_WINNT < 0x0400) */
    
    WM_PARENTNOTIFY = &H210
    WM_ENTERMENULOOP = &H211
    WM_EXITMENULOOP = &H212
    
    ' #if(WINVER >= 0x0400)
    WM_NEXTMENU = &H213
    WM_SIZING = &H214
    WM_CAPTURECHANGED = &H215
    WM_MOVING = &H216
    
    WM_POWERBROADCAST = &H218
    WM_DEVICECHANGE = &H219
    ' #endif /* WINVER >= 0x0400 */
    
    WM_MDICREATE = &H220
    WM_MDIDESTROY = &H221
    WM_MDIACTIVATE = &H222
    WM_MDIRESTORE = &H223
    WM_MDINEXT = &H224
    WM_MDIMAXIMIZE = &H225
    WM_MDITILE = &H226
    WM_MDICASCADE = &H227
    WM_MDIICONARRANGE = &H228
    WM_MDIGETACTIVE = &H229
    WM_MDISETMENU = &H230
    WM_DROPFILES = &H233
    WM_MDIREFRESHMENU = &H234
    
    ' #if(WINVER >= 0x0400)
    WM_IME_SETCONTEXT = &H281
    WM_IME_NOTIFY = &H282
    WM_IME_CONTROL = &H283
    WM_IME_COMPOSITIONFULL = &H284
    WM_IME_SELECT = &H285
    WM_IME_CHAR = &H286
    ' #endif /* WINVER >= 0x0400 */
    ' #if(WINVER >= 0x0500)
    WM_IME_REQUEST = &H288
    ' #endif /* WINVER >= 0x0500 */
    ' #if(WINVER >= 0x0400)
    WM_IME_KEYDOWN = &H290
    WM_IME_KEYUP = &H291
    ' #endif /* WINVER >= 0x0400 */
    '
    ' #if(_WIN32_WINNT >= 0x0400)
    WM_MOUSEHOVER = &H2A1
    WM_MOUSELEAVE = &H2A3
    ' #endif /* _WIN32_WINNT >= 0x0400 */
    
    WM_CUT = &H300
    WM_COPY = &H301
    WM_PASTE = &H302
    WM_CLEAR = &H303
    WM_UNDO = &H304
    WM_RENDERFORMAT = &H305
    WM_RENDERALLFORMATS = &H306
    WM_DESTROYCLIPBOARD = &H307
    WM_DRAWCLIPBOARD = &H308
    WM_PAINTCLIPBOARD = &H309
    WM_VSCROLLCLIPBOARD = &H30A
    WM_SIZECLIPBOARD = &H30B
    WM_ASKCBFORMATNAME = &H30C
    WM_CHANGECBCHAIN = &H30D
    WM_HSCROLLCLIPBOARD = &H30E
    WM_QUERYNEWPALETTE = &H30F
    WM_PALETTEISCHANGING = &H310
    WM_PALETTECHANGED = &H311
    WM_HOTKEY = &H312
    '
    ' #if(WINVER >= 0x0400)
    WM_PRINT = &H317
    WM_PRINTCLIENT = &H318
    '
    WM_HANDHELDFIRST = &H358
    WM_HANDHELDLAST = &H35F
    '
    WM_AFXFIRST = &H360
    WM_AFXLAST = &H37F
    ' #endif /* WINVER >= 0x0400 */
    '
    WM_PENWINFIRST = &H380
    WM_PENWINLAST = &H38F
    '
    ' #if(WINVER >= 0x0400)
    WM_APP = &H8000
    ' #endif /* WINVER >= 0x0400 */
    
    ' NOTE: All Message Numbers below 0x0400 are RESERVED.
    
    ' Private Window Messages Start Here:
    WM_USER = &H400
End Enum

Public Enum eWSCHitTest
    ' WM_NCHITTEST and MOUSEHOOKSTRUCT Mouse Position Codes
    HTERROR = (-2)
    HTTRANSPARENT = (-1)
    HTNOWHERE = 0
    HTCLIENT = 1
    HTCAPTION = 2
    HTSYSMENU = 3
    HTGROWBOX = 4
    HTSIZE = HTGROWBOX
    HTMENU = 5
    HTHSCROLL = 6
    HTVSCROLL = 7
    HTMINBUTTON = 8
    HTMAXBUTTON = 9
    HTLEFT = 10
    HTRIGHT = 11
    HTTOP = 12
    HTTOPLEFT = 13
    HTTOPRIGHT = 14
    HTBOTTOM = 15
    HTBOTTOMLEFT = 16
    HTBOTTOMRIGHT = 17
    HTBORDER = 18
    HTREDUCE = HTMINBUTTON
    HTZOOM = HTMAXBUTTON
    HTSIZEFIRST = HTLEFT
    HTSIZELAST = HTBOTTOMRIGHT
End Enum

Public Enum eWSCMF
    ' Menú Flags para WM_MENUSELECT
    'MF_UNCHECKED = &H0&
    MF_GRAYED = &H1&
    MF_DISABLED = &H2&
    MF_BITMAP = &H4&
    MF_CHECKED = &H8&
    MF_POPUP = &H10&
    MF_HILITE = &H80&
    MF_OWNERDRAW = &H100&
    MF_SYSMENU = &H2000&
    MF_MOUSESELECT = &H8000&
End Enum

' Valores de fuSource para el mensaje WM_ENTERIDLE
Public Enum eWSCMSFG
    MSGF_DIALOGBOX = 0
    MSGF_MENU = 2
End Enum

' Mensajes varios
Public Enum eWSCMisc
    ' WM_ACTIVATE state values
    WA_INACTIVE = 0
    WA_ACTIVE = 1
    WA_CLICKACTIVE = 2
    
    ' wParam for WM_POWER window message and DRV_POWER driver notification
    PWR_OK = 1
    PWR_FAIL = (-1)
    PWR_SUSPENDREQUEST = 1
    PWR_SUSPENDRESUME = 2
    PWR_CRITICALRESUME = 3
    
    ' WM_SYNCTASK Commands
    ST_BEGINSWP = 0
    ST_ENDSWP = 1
    
    ' SendMessageTimeout values
    SMTO_NORMAL = &H0
    SMTO_BLOCK = &H1
    SMTO_ABORTIFHUNG = &H2
    
    ' WM_MOUSEACTIVATE Return Codes
    MA_ACTIVATE = 1
    MA_ACTIVATEANDEAT = 2
    MA_NOACTIVATE = 3
    MA_NOACTIVATEANDEAT = 4
    
    ' WM_SIZE message wParam values
    SIZE_RESTORED = 0
    SIZE_MINIMIZED = 1
    SIZE_MAXIMIZED = 2
    SIZE_MAXSHOW = 3
    SIZE_MAXHIDE = 4
    
    ' WM_NCCALCSIZE return flags
    WVR_ALIGNTOP = &H10
    WVR_ALIGNLEFT = &H20
    WVR_ALIGNBOTTOM = &H40
    WVR_ALIGNRIGHT = &H80
    WVR_HREDRAW = &H100
    WVR_VREDRAW = &H200
    WVR_REDRAW = (WVR_HREDRAW Or WVR_VREDRAW)
    WVR_VALIDRECTS = &H400
    
    ' Key State Masks for Mouse Messages
    MK_LBUTTON = &H1
    MK_RBUTTON = &H2
    MK_SHIFT = &H4
    MK_CONTROL = &H8
    MK_MBUTTON = &H10
    
    ' Constantes para el menú del sistema
    SC_RESTORE = &HF120&
    SC_MOVE = &HF010&
    SC_SIZE = &HF000&
    SC_MINIMIZE = &HF020&
    SC_MAXIMIZE = &HF030&
    SC_CLOSE = &HF060&
    
    ' Track Mouse Event
    HOVER_DEFAULT = &HFFFFFFFF
    
    ' #if(_WIN32_WINNT >= 0x0400)
    WHEEL_DELTA = 120                   ' /* Value for rolling one detent */
    ' #endif /* _WIN32_WINNT >= 0x0400 */
    ' #if(_WIN32_WINNT >= 0x0400)
    '#define UINT_MAX      =&hffffffff    /* maximum unsigned int value */
    '#define WHEEL_PAGESCROLL                (UINT_MAX) /* Scroll one page */
    WHEEL_PAGESCROLL = &HFFFFFFFF
    ' #endif /* _WIN32_WINNT >= 0x0400 */
    
    '// begin_pbt
    PBT_APMQUERYSUSPEND = &H0           ' Request for permission to suspend.
    PBT_APMQUERYSTANDBY = &H1
    '
    PBT_APMQUERYSUSPENDFAILED = &H2     ' Suspension request denied.
    PBT_APMQUERYSTANDBYFAILED = &H3
    '
    PBT_APMSUSPEND = &H4                ' System is suspending operation.
    PBT_APMSTANDBY = &H5
    '
    PBT_APMRESUMECRITICAL = &H6         ' Operation resuming after critical suspension.
    PBT_APMRESUMESUSPEND = &H7          ' Operation resuming after suspension.
    PBT_APMRESUMESTANDBY = &H8
    '
    PBTF_APMRESUMEFROMFAILURE = &H1&
    '
    PBT_APMBATTERYLOW = &H9             ' Battery power is low.
    PBT_APMPOWERSTATUSCHANGE = &HA      ' Power status has changed.
    '
    PBT_APMOEMEVENT = &HB               ' OEM-defined event occurred.
    PBT_APMRESUMEAUTOMATIC = &H12       ' Operation resuming automatically after event.
    '// end_pbt
    
End Enum

' WM_WINDOWPOSCHANGING/CHANGED struct pointed to by lParam
Private Type WINDOWPOS
    hWnd As Long
    hWndInsertAfter As Long
    X As Long
    Y As Long
    cx As Long
    cy As Long
    flags As Long
End Type

Public Enum eTME
    ' Track Mouse Event
    TME_HOVER = &H1
    TME_LEAVE = &H2
    TME_QUERY = &H40000000
    TME_CANCEL = &H80000000
End Enum

Private Type tTRACKMOUSEEVENT
    cbSize As Long
    dwFlags As eTME 'Long
    hWndTrack As Long
    dwHoverTime As Long
End Type

Private Declare Function TrackMouseEvent Lib "User32" _
    (lpEventTrack As tTRACKMOUSEEVENT) As Long

'Private Declare Function DefWindowProc Lib "User32" Alias "DefWindowProcA" _
    (ByVal hWnd As Long, ByVal wMsg As Long, _
    ByVal wParam As Long, ByVal lParam As Long) As Long

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
    (Destination As Any, Source As Any, ByVal Length As Long)

'------------------------------------------------------------------------------
' *** Los eventos producidos por la clase ***

'------------------------------------------------------------------------------
' Eventos genéricos, no directamente relacionados con la ventana subclasificada

' Permite interceptar todos los mensajes producidos
Public Event MSG(ByVal uMSG As eWSCWM, ByVal wParam As Long, ByVal lParam As Long)

' Indica el menú seleccionado
Public Event MenuSelected(ByVal mnuItem As Long, mnuFlags As eWSCMF, ByVal hMenu As Long)
' Ha cambiado el tamaño del objeto Screen y/o el número de colores
Public Event DisplayChange(ByVal BitsPerPixel As Long, ByVal cxScreen As Long, ByVal cyScreen As Long)
' Se ha añadido o quitado alguna fuente del sistema
Public Event FontChange()
' El sistema indica que hay pocos recursos disponibles
Public Event LowMemory()
' Este evento se producirá mientras se está seleccionando un menú o en un diálogo
' dentro del formulario
'Public Event EnterIDLE(ByVal IsIDLE As Boolean, ByVal fuSource As Long, ByVal hWnd As Long)
'Public Event EnterIDLE(ByVal IsIDLE As Boolean, ByVal fuSource As eWSCMSFG, ByVal hWnd As Long)

'------------------------------------------------------------------------------
' Eventos directamente relacionados con la ventana subclasificada

' La ventana ha cambiado de posición, indica la posición y tamaño
Public Event WindowPosChanged(ByVal wLeft As Long, ByVal wTop As Long, ByVal wWidth As Long, ByVal wHeight As Long)
' Se ha movido esta ventana, devuelve la posición en twips
Public Event Move(ByVal wLeft As Long, ByVal wTop As Long)
' El ratón sale de la ventana subclasificada
Public Event MouseLeave()
' Indica de que control sale, si la ventana contiene otros controles
Public Event MouseLeaveOn(unControl As Object)
' El ratón entra en la ventana
Public Event MouseEnter()
' Indica en que control entra, si la ventana contiene controles
Public Event MouseEnterOn(unControl As Object)
' Indica si se ha movido la rueda del ratón y los parámetros correspondientes
Public Event MouseWheel(ByVal wKeys As Long, ByVal zDelta As Long, ByVal xPos As Long, ByVal yPos As Long)
'
Public Event SetCursor(unControl As Object, ByVal HitTest As eWSCHitTest, ByVal MouseMsg As Long)
' Se activa la ventana
Public Event Activate()
' Se desactiva la ventana
Public Event Deactivate()

Private Sub Class_Initialize()
    ' Esto no es necesario, pero lo uso para saber que cuando se inicia la clase
    ' estas variables locales están a "cero"
    mhWnd = 0
    mPrevWndProc = 0
    
    '--------------------------------------------------------------------------
    ' Para devolver los valores en twips,
    ' ya que el valor devuelto originalmente será en pixels
    '
    ' Se hace en este evento para no tener que calcularlo en cada mensaje.
    '$Por hacer: (22/Mar/99)
    '   Si se cambia la resolución habría que cambiar estos valores...
    '--------------------------------------------------------------------------
    twX = Screen.TwipsPerPixelX
    twY = Screen.TwipsPerPixelY
End Sub

Private Sub Class_Terminate()
    ' Comprobar si están iniciadas las variables
    ' y de ser así dejar de subclasificar el formulario
    If mhWnd Then
        MSubclass.unHook Me
    End If
End Sub

Public Sub Hook(Optional ByVal NewValue As Object = Nothing)
    '--------------------------------------------------------------------------
    ' Este método se debe usar para iniciar o terminar la subclasificación
    '
    ' El parámetro se usará para indicar la ventana a subclasificar,
    ' se podría haber usado un valor Long para el hWnd de la ventana, pero,
    ' es que este objeto se usa para comprobar los controles incluidos
    ' ya que en los mensajes procesados se puede procesar si el ratón sale o
    ' entra en algún control incluido en la colección controls,
    ' por tanto sólo tiene utilidad real si ese objeto tiene una colección Controls
    ' (por ejemplo un formulario o un control de usuario)
    '
    ' Nota: Se puede subclasificar un form o cualquier otro control con hWnd
    '--------------------------------------------------------------------------
    
    On Local Error GoTo ErrHook
    
    If NewValue Is Nothing Then
        ' Quitar el 'gancho' del form
        If Me.hWnd <> 0 Then
            MSubclass.unHook Me
        End If
    
    ' Si no tiene hWnd producirá error
    ElseIf NewValue.hWnd = 0 Then
        '
    Else
        ' Si ya había una copia funcionando, quitar el gancho       (08/Ago/99)
        If Me.hWnd <> 0 Then
            MSubclass.unHook Me
        End If
        ' Asignar el nuevo gancho a la ventana a subclasificar
        MSubclass.Hook Me, NewValue
        
        Set mWndType = NewValue
        
    End If
    
    Exit Sub
ErrHook:
    ' Nada que hacer si se pasa un parámero erróneo.
    ' Devolver un error indicándolo
    On Local Error GoTo 0
    With Err
        .Description = "El parámetro debe ser una ventana (que tenga la propiedad hWnd) " & _
                       "o Nothing para dejar de interceptar los mensajes." & _
                       vbCrLf & vbCrLf & _
                       "El tipo del parámetro es: " & TypeName(NewValue)
                       
        .Source = "cSubclass.Hook"
        .Raise .Number
    End With
End Sub

Friend Sub unMSG(ByVal uMSG As eWSCWM, ByVal wParam As Long, ByVal lParam As Long)
    ' Este procedimiento será el que reciba los mensajes y se encargará
    ' de producir los eventos correspondientes
    
    ' Usar variables estáticas para mayor rapidez
    Static tWP As WINDOWPOS
    Static bMouseEnter As Boolean
    Static ControlAnt As Control        ' Control en el que se produjo el MouseMove
                                        ' para los casos en los que la ventana
                                        ' subclasificada tenga otros controles
    
    ' Si se produce un error que no se pare la aplicación...
    On Local Error Resume Next
    
'------------------------------------------------------------------------------
' Si está en modo IDLE (no hay mensajes)
'If uMSG = WM_ENTERIDLE Then
'    '--------------------------------------------------------------------------
'    ' Se ha entrado en un estado en el que no hay mensajes
'
'    '
'    ' fuSource = wParam;    // idle-source flag
'    ' hwnd = (HWND) lParam; // handle of dialog box or owner window
'    '
'    ' Value of wParam.  Specifies whether the message is the result of a dialog box
'    '                   or a menu being displayed.
'    ' This parameter can be one of the following values:
'    '   MSGF_DIALOGBOX    The system is idle because a dialog box is displayed.
'    '   MSGF_MENU         The system is idle because a menu is displayed.
'    '
'    RaiseEvent EnterIDLE(True, wParam, lParam)
'    '
''------------------------------------------------------------------------------
'' Hay otros mensajes aparte del IDLE
'Else
'    '--------------------------------------------------------------------------
'    ' Indicar que ya no está en modo IDLE
'    RaiseEvent EnterIDLE(False, 0&, 0&)
        
    '--------------------------------------------------------------------------
    ' Evento genérico
    RaiseEvent MSG(uMSG, wParam, lParam)
    
    
    ' Según el tipo de mensaje, producir eventos...
    Select Case uMSG
    
    '--------------------------------------------------------------------------
    ' Selección de un menú
    Case WM_MENUSELECT
        ' Los parámetros son:   mnuItem         mnuFlags        mnuHandle
        RaiseEvent MenuSelected(LoWord(wParam), HiWord(wParam), lParam)
    
    '--------------------------------------------------------------------------
    ' Cambio en la posición y tamaño de la ventana
    ' La posición y tamaño se indicará en twips
    Case WM_WINDOWPOSCHANGED 'WM_WINDOWPOSCHANGING
        '----------------------------------------------------------------------
        ' Nota: Los valores de twX y twY se asignan en el evento Initialize
        
        '--------------------------------------------------------------------------
        ' Copiar el valor referenciado por lParam en un UDT
        CopyMemory tWP, ByVal lParam, Len(tWP)
        With tWP
            '                           Left      Top       Width      Height
            RaiseEvent WindowPosChanged(.X * twX, .Y * twY, .cx * twX, .cy * twY)
        End With
    
    '--------------------------------------------------------------------------
    ' Se ha movido la ventana
    ' La posición se indicará en twips
    Case WM_MOVE
        With tWP
            .X = LoWord(lParam)
            .Y = HiWord(lParam)
            RaiseEvent Move(.X * twX, .Y * twY)
        End With
    
    '--------------------------------------------------------------------------
    ' Ha cambiado la resolución de la pantalla o el número de colores
    Case WM_DISPLAYCHANGE
        '                         BitsPerPixels
        '                                 New horizontal resolution of the screen
        '                                                 New vertical resolution of the screen
        RaiseEvent DisplayChange(wParam, LoWord(lParam), HiWord(lParam))
    
    '--------------------------------------------------------------------------
    ' Se han añadido o quitado fuentes del sistema
    Case WM_FONTCHANGE
        RaiseEvent FontChange
    
    '--------------------------------------------------------------------------
    ' El sistema está compactando la memoria porque tiene poca disponible
    Case WM_COMPACTING
        RaiseEvent LowMemory
    
    '--------------------------------------------------------------------------
    ' The WM_ACTIVATEAPP message is sent when a window belonging to a different
    ' application than the active window is about to be activated.
    ' The message is sent to the application whose window is being activated
    ' and to the application whose window is being deactivated.
    ' fActive = (BOOL) wParam;        // activation flag
    '
    ' Specifies whether the window is being activated or deactivated.
    ' This parameter is TRUE if the window is being activated;
    ' it is FALSE if the window is being deactivated.
    '
    ' Si se activa o desactiva la ventana
    Case WM_ACTIVATEAPP
        If wParam Then
            RaiseEvent Activate
        Else
            RaiseEvent Deactivate
        End If
        
    '--------------------------------------------------------------------------
    ' Si se ha movido la rueda de los ratos con ruedas
    Case WM_MOUSEWHEEL
        'fwKeys = LOWORD(wParam);    // key flags
        'zDelta = (short) HIWORD(wParam);    // wheel rotation
        'xPos = (short) LOWORD(lParam);    // horizontal position of pointer
        'yPos = (short) HIWORD(lParam);    // vertical position of pointer
        RaiseEvent MouseWheel(LoWord(wParam), HiWord(wParam), LoWord(lParam), HiWord(lParam))
        '
        ' fwKeys:
        ' Indicates whether various virtual keys are down.
        ' This parameter can be any combination of the following values:
        ' (estos valores están en la enumeración: eWSCMisc)
        ' MK_CONTROL    Set if the ctrl key is down.
        ' MK_LBUTTON    Set if the left mouse button is down.
        ' MK_MBUTTON    Set if the middle mouse button is down.
        ' MK_RBUTTON    Set if the right mouse button is down.
        ' MK_SHIFT      Set if the shift key is down.
        '
        ' zDelta:
        ' Indicates the distance that the wheel is rotated,
        ' expressed in multiples or divisions of WHEEL_DELTA, which is 120.
        ' A positive value indicates that the wheel was rotated forward,
        ' away from the user;
        ' a negative value indicates that the wheel was rotated backward,
        ' toward the user.
    
    '--------------------------------------------------------------------------
    ' Cuando el ratón sale del área de la ventana indicada
    Case WM_MOUSELEAVE
        ' Si este mensaje se intercepta en un control incluido en el form
        ' además del propio form, cuando se entre en un control se producirá
        ' un MouseLeave en el form (y viceversa)
        RaiseEvent MouseLeave
        bMouseEnter = False
    
    '--------------------------------------------------------------------------
    ' Cuando el ratón se mueve, es porque está en la ventana,
    ' comprobar si es la primera vez que entra y así lanzar el evento MouseEnter
    Case WM_MOUSEMOVE
        ' Cuando se produce un MouseMove, es decir que el mouse está en el control
        ' hay que indicar que se vuelva a detectar la salida del control
        If Not bMouseEnter Then
            bMouseEnter = True
            With mtTME
                .dwFlags = TME_LEAVE
                .cbSize = Len(mtTME)
                .hWndTrack = mhWnd
            End With
            Call TrackMouseEvent(mtTME)
            '
            RaiseEvent MouseEnter
            '
            ' Comprobar si está asignado el control anterior
            If Not (ControlAnt Is Nothing) Then
                RaiseEvent MouseLeaveOn(ControlAnt)
            End If
        End If
        
    '--------------------------------------------------------------------------
    ' The WM_SETCURSOR message is sent to a window if the mouse causes the cursor
    ' to move within a window and mouse input is not captured.
    ' Parámetros:
    '   hwnd = (HWND) wParam;       // handle to window with cursor
    '   nHittest = LOWORD(lParam);  // hit-test code
    '   wMouseMsg = HIWORD(lParam); // mouse-message identifier
    Case WM_SETCURSOR
        Dim i As Long
        Dim tControl As Control
        Dim nHittest As Long
        Dim wMouseMsg As Long
        
        nHittest = LoWord(lParam)
        wMouseMsg = HiWord(lParam)
        
        ' Este bucle sólo funcionará si la ventana subclasificada
        ' tiene una colección de controles, (por ejemplo un form)
        For Each tControl In mWndType.Controls
            With tControl
                ' Si no es este control o el control no soporta el hWnd
                If .hWnd <> wParam Then
                    '
                Else
                    RaiseEvent SetCursor(tControl, nHittest, wMouseMsg)
                    ' Si el mensaje del ratón es que se mueve...
                    If wMouseMsg = WM_MOUSEMOVE Then
                        ' Comprobar si ya había entrado en el control
                        If ControlAnt.hWnd <> wParam Then
                            ' Si ya se había asignado
                            If Not (ControlAnt Is Nothing) Then
                                ' Se ha salido de este control
                                RaiseEvent MouseLeaveOn(ControlAnt)
                            End If
                        End If
                        ' Asignar este control para posteriores comprobaciones
                        Set ControlAnt = tControl
                        RaiseEvent MouseEnterOn(tControl)
                    End If
                    Exit For
                End If
            End With
        Next
    
    End Select
    
'    '
'End If

End Sub

Friend Property Get hWnd() As Long
    hWnd = mhWnd
End Property

Friend Property Let hWnd(ByVal NewValue As Long)
    mhWnd = NewValue
End Property

Friend Property Get PrevWndProc() As Long
    PrevWndProc = mPrevWndProc
End Property

Friend Property Let PrevWndProc(ByVal NewValue As Long)
    mPrevWndProc = NewValue
End Property

Private Function LoWord(ByVal Numero As Long) As Long
    ' Devuelve el LoWord del número pasado como parámetro
    LoWord = Numero And &HFFFF&
End Function

Private Function HiWord(ByVal Numero As Long) As Long
    ' Devuelve el HiWord del número pasado como parámetro
    HiWord = Numero \ &H10000 And &HFFFF&
End Function

Volver a Visual Basic Avanzado

ir al índice