Un Manejador de Eventos Para VB6 – Parte 1

Una clase que maneje los eventos de componentes ActiveX puede ir mas allá de la directiva Event

Por Harvey Triana

 

Introducción

 

Una clase que maneje los eventos de componentes ActiveX puede ir mas allá de la directiva Event. Las razones son las siguientes: (1) Se pueden implementar fácilmente colecciones de objetos que generen eventos, (2) Se pueden lanzar eventos desde un formulario u objeto que cargado desde la interfaz Form u Object, (3) El cliente puede recibir eventos aun sin la directiva WithEvents, (4) Puede escribir tantos eventos como desee o modificarlos sin afectar la librería de tipos del componente, y por ende la compatibilidad binaria. Quizás habrá otros servicios que yo no me he imaginado. Cada uno de estos enunciados se explica con detalle en este articulo.

 

La Clase CEventsHandler

 

Tan interesantes enunciados que dan la introducción de este articulo, se logran a través de la clase CEventsHandler, he aquí su código:

 

'//==============================================================

'// NAME        : CEventsHandler

'// AUTHOR      : Harvey Triana (harvey@geocoweb.net)

'// DESCRIPTION :

'//==============================================================

Option Explicit

 

Public Event RaisedEvent( _

             Sender As Object, _

             EventName As String, _

             EventArgs As Variant)

 

Public Sub Trigger( _

    Sender As Object, _

    EventName As String, _

    EventArgs As Variant)

   

    RaiseEvent RaisedEvent(Sender, EventName, EventArgs)

End Sub

'//==============================================================

 

Basta leer el código anterior para vislumbrar la idea, el objeto dentro del componente hace un llamado al método Trigger para lanzar cualquier evento, dado un nombre y unos argumentos en una matriz Variant llamada EventsArg. ¿Se le parece algo a VB.NET?, no es extraño, pues la primera vez que vi como manejaba los eventos VB.NET, se me ocurrió la idea para VB6, aunque si bien, es bastante diferente en detalle.

 

El empleo de la clase CEventsHandler puede ser laborioso, aunque es grato saber que soluciona con holgura algunas limitaciones técnicas de WithEvents, y que la estrategia aplica de la misma forma en todos los casos.

 

Eventos desde una Colección de objetos

 

VB6 tiene una forma elegante de manejar objetos en jerarquía, y es a través de una colección implementada y gobernada por una clase y clases hijo de esta. Por supuesto no se trata de “herencia” tal como la define OOP, pero es extremadamente útil para tener ventajas de programación de objetos con VB6. Si usted ha leído bastante sobre VB6, se acordara de la casa de ladrillo, a eso me refiero. Sin embargo esta implementación de jerarquía de objetos encuentra una limitación: ¿Cómo manejar eventos desde los objetos hijo?. Puesto que no podemos declarar Private WithEvents colAlgo As Collection, o cosa que se le parezca. He aquí la solución para recibir eventos desde los objetos hijo. Voy a exponer un ejemplo sencillo, para vislumbrar la técnica.

 

Digamos que tenemos una clase que se llama CPersons, con dos propiedades básicas, Nombre y Apellido, y un método que inicia una evaluación en tiempo, por ejemplo la llegada de la persona a la oficina. El código de la clase CPerson es el siguiente:

 

'//==============================================================

'// NAME        : CPerson

'// AUTHOR      : Harvey Triana (harvey@geocoweb.net)

'// DESCRIPTION : Ejemplo del articulo vb6EventsHndler

'//               www.mvps.org/vexpert

'//==============================================================

Option Explicit

 

Private m_FirstName As String

Private m_LastName  As String

 

Private WithEvents TimerSample As Timer

Private f As frmTimer

 

Public Property Let FirstName(v As String)

    m_FirstName = v

End Property

 

Public Property Get FirstName() As String

    FirstName = m_FirstName

End Property

 

Public Property Let LastName(v As String)

    m_LastName = v

End Property

 

Public Property Get LastName() As String

    LastName = m_LastName

End Property

 

Private Sub Class_Initialize()

    Set f = New frmTimer

    Load f

    Set TimerSample = f.tmrControl

End Sub

 

Private Sub Class_Terminate()

    Unload f

End Sub

 

Public Sub StartSample()

    Randomize Timer

    TimerSample.Interval = Rnd * 5000

    TimerSample.Enabled = True

End Sub

 

Private Sub TimerSample_Timer()

    TimerSample.Enabled = False

   

    '//PROBLEMA:

    ' Se requiere lanzar un evento aquí que suministre

    ' el tiempo de llegada de la persona:

    ' ...

End Sub

'//==============================================================

 

Se crea una referencia a un proceso Timer, el cual esta envuelto en un Form. Solo basta agregar un Form al proyecto, y adicionar un Timer (los nombres respectivamente son: nombre del formulario: frmTimer; nombre del Timer: tmrControl).  Este Timer se emplea como una muestra para visualizar el comportamiento de eventos por cada objeto de CPerson. El problema a resolver es el siguiente: “Se requiere lanzar un evento aquí que suministre el tiempo de llegada de la persona”. Esto se resalta en color violeta en el código anterior (ver procedimiento TimerSample_Timer). Puesto que los objetos CPerson harán parte de un objeto Collection en el cliente, no podemos simplemente escribir en Declaraciones: Public Event TimeArrive(DateTime As Date), y esperar que nuestro codigo funcione.  La clase CEventsHandler resuelve esto, después de implementada la estrategia, el código luce así:

 

'//==============================================================

'// NAME        : CPerson

'// AUTHOR      : Harvey Triana (harvey@geocoweb.net)

'// DESCRIPTION : Ejemplo del articulo vb6EventsHndler

'//               www.mvps.org/vexpert

'//==============================================================

Option Explicit

 

Private m_FirstName As String

Private m_LastName  As String

 

Private WithEvents TimerSample As Timer

Private f As frmTimer

 

'//EH Implementación

Private m_EventsHandler As CEventsHandler

 

'//EH Implementación

Public Property Set EventsHandler(v As CEventsHandler)

    Set m_EventsHandler = v

End Property

 

Public Property Let FirstName(v As String)

    m_FirstName = v

End Property

 

Public Property Get FirstName() As String

    FirstName = m_FirstName

End Property

 

Public Property Let LastName(v As String)

    m_LastName = v

End Property

 

Public Property Get LastName() As String

    LastName = m_LastName

End Property

 

Private Sub Class_Initialize()

    Set f = New frmTimer

    Load f

    Set TimerSample = f.tmrControl

End Sub

 

Private Sub Class_Terminate()

    Unload f

    '//EH Implementación

    Set m_EventsHandler = Nothing

End Sub

 

Public Sub StartSample()

    Randomize Timer

    TimerSample.Interval = Rnd * 5000

    TimerSample.Enabled = True

End Sub

 

Private Sub TimerSample_Timer()

    TimerSample.Enabled = False

    '//EH Implementación

    m_EventsHandler.Trigger Me, "ArriveTime", Time

End Sub

'//==============================================================

 

 

Las líneas de código que se adicionaron aparecen en color violeta, y precedidas por el comentario EH Implementación. Ahora CPerson tiene un nuevo miembro: EventsHandler, el cual es una a propiedad de objeto explicita. La clase tampoco crea una instancia de este objeto, el cual será pasado desde el cliente. Veamos un cliente de muestra, un formulario que crea una colección de objetos CPerson, y que muestra la técnica de CEventsHandler.

 

'//==============================================================

'// NAME        : CForm1

'// AUTHOR      : Harvey Triana (harvey@geocoweb.net)

'// DESCRIPTION : Ejemplo del articulo vb6EventsHandler

'//               www.mvps.org/vexpert

'//==============================================================

Option Explicit

 

Private colPersons As Collection

 

'//EH Implementación

Private WithEvents EventsHandler As CEventsHandler

 

'//EH Implementación

Private Sub EventsHandler_RaisedEvent( _

    Sender As Object, _

    EventName As String, _

    EventArgs As Variant)

 

    Select Case EventName

        Case "ArriveTime"

             Print "Llego " & Sender.FirstName & " " & _

                   Sender.LastName & " a las " & EventArgs

    End Select

End Sub

 

Private Sub Form_Load()

    '//EH Implementación

    Set EventsHandler = New CEventsHandler

   

    Call LoadPersonsCollection

End Sub

 

Private Sub LoadPersonsCollection()

    Dim o As CPerson

   

    '//ejemplo

   

    Set colPersons = New Collection

   

    Set o = New CPerson

    o.FirstName = "Jonh"

    o.LastName = "Lennon"

    colPersons.Add o

    Set o = Nothing

   

    Set o = New CPerson

    o.FirstName = "Elenor"

    o.LastName = "Rigby"

    colPersons.Add o

    Set o = Nothing

 

    Set o = New CPerson

    o.FirstName = "Paul"

    o.LastName = "McCarney"

    colPersons.Add o

    Set o = Nothing

   

    '//mostrar el formulario cliente

    Show

   

    '//EH Implementación

    For Each o In colPersons

        Set o.EventsHandler = EventsHandler

        o.StartSample

    Next

End Sub

 

Private Sub Form_Unload(Cancel As Integer)

    Set colPersons = Nothing

   

    '//EH Implementación

    Set EventsHandler = Nothing

End Sub

'//==============================================================

 

Este formulario crea tres personas desde Form_Load, luego pasa una referencia de un objeto EventsHandler a cada objeto CPerson, y a su vez inicia un proceso Timer. Al ejecutar el proyecto, se visualiza que cada objeto CPerson lanza el evento de manera independiente, y que se puede saber “quien” lanza el evento a través del objeto Sender de los argumentos del evento RaisedEvent (evento lanzado) de la clase CEventsHandler. 

 

Este formulario crea tres personas desde Form_Load, luego pasa una referencia de un objeto EventsHandler a cada objeto CPerson, y a su vez inicia un proceso Timer. Al ejecutar el proyecto, se visualiza que cada objeto CPerson lanza el evento de manera independiente, y que se puede saber “quien” lanza el evento a través del objeto Sender de los argumentos del evento RaisedEvent (evento lanzado) de la clase CEventsHandler.

 

 

Conclusión

 

Observe algunos detalles del ejemplo anterior.  Primero, la clase CPerson no tiene declaración de eventos. Segundo, el evento RaisedEvent se puede emplear para manejar un sin numero de eventos, basta un Select Case al nombre del evento para diferenciarlos. Tercero, el argumento EventArgs por ser un Variant puede contener un dato, o muchos (un array o matriz),  es meramente circustancial, ya que al tener el objeto Sender, este contiene toda la información del objeto que envió el evento.

 

El este articulo mostre como se pueden implementar fácilmente colecciones de objetos que generen eventos. En la segunda parte del ariculo, diré como se pueden lanzar eventos desde un formulario u objeto que cargado desde la interfaz Form u Object.