Formularios en Componentes de Código


Técnicas Depuradas para Suministrar Formularios desde Componentes ActiveX con Visual Basic
Parte I, Formularios Modales en Proceso

Por Harvey Triana

Suministrar Formularios desde componentes es una buena estrategia de desarrollo. Los formularios ocupan espacio considerable en el ambiente de desarrollo de un proyecto. No es necesario mantener cada formulario en el proyecto principal, de hecho el único formulario de una aplicación podría ser el inicial, hablando técnicamente el Front End de la solución. Empaquetar formularios en componentes de código permitirá a una aplicación robusta evolucionar en tamaño y desarrollo libre de sobrecarga. Sin embargo, usar Formularios en Componentes tiene su intríngulis y suele ser un tema relativamente nuevo para programadores Visual Basic corrientes, quizá extraño y difícil. Puede que Ud. haya empacado ya muchos formularios en componentes de código, sin embargo, los modelos que sugiero en este articulo son altamente competentes, y representan una alternativa simple y robusta al mismo tiempo.

Inicialmente pense escribir este articulo para que cubriera los formularios modales y no modales, sin embargo el escrito se extendió tanto, que decidí dividirlo en dos partes: en (1) Formularios Modales en Componentes en Proceso, (2) Formularios no Modales en Componentes en Proceso, con alguna nota sobre formularios en componentes fuera de proceso.

Otra difícil decisión que tuve que tomar fue: ¿Escribo todo basado en interfaces múltiples, o uso el estilo tradicional?. Vaya decisión. Si me decidia por usar interfaces, quizás programadores noveles e intermedios en Visual Basic no leerían este articulo, o quiza lo leerían y perciban un extraño modo de hacer difícil las cosas fáciles. La programación orientada a Interfaces presenta unos perfiles si no avanzados, poco frecuentes entre programadores (en especial aquellos que vienen de la antigua OOP), y reviste mayores explicaciones que pueden terminar en desanimar al lector. De otro lado la programación tradicional será aceptada inmediatamente en todos los niveles de programadores Visual Basic, y finalmente, el articulo será útil. Bien, que me condenen al infierno de Dante los programadores avanzados en Visual Basic, use programación tradicional en los modelos de código de este articulo. Sin embargo, para expirar en parte mi pecado, escribí un ejemplo en un anexo para explicar exactamente como usar lo mismo pero con interfaces. Si me preguntan: ¿Cuál es mejor?, la respuesta es: La programación orientada a interfaces tiene mayores beneficios, pero en grandes desarrollos.


Introducción

Para iniciar en el tema, le podría sugerir indagar por «Creación de una DLL ActiveX»  y «Creación de un Componente EXE ActiveX», en la documentación estándar de Visual Basic (libros en pantalla para VB5 o MSDN para VB6). Esta documentación es ideal, lo robustecerá en conocimientos y lo pondrá al tanto de los beneficios y precauciones de usar formularios dentro de componentes. Sin embargo, a través de mi experiencia he delineado algunos modelos de código que contribuyen a solucionar sistemáticamente las necesidades de usar formularios en componentes.

En general, las fallas comunes de una mala programación de formularios en componentes provienen de dejar objetos sueltos, en particular por el entorno conceptual de iniciar y terminar un Formulario. En general, la clave del éxito de usar formularios en componentes se fundamente en que la clase que envuelve el formulario es la encargada de controlar la vida del formulario, y no el formulario quien dictamina su permanencia en memoria. Quizá se sorprenda, pero dominar las referencias a un objeto puede ser más difícil de lo presupuestado. Los modelos aquí expuestos suministran total dominio sobre la instancia del formulario.

Necesariamente los formularios se deben envolver en clases las cuales suministran la interfaz. ¿Cómo va el código dentro de la clase y dentro del formulario?; este articulo es una cátedra.

Pense escribir este articulo con un estilo más deductivo y formal, delegando los modelos de código en líneas generales, pero recordé unas palabras en el prologo de Bruce McKenney, autor de Hardcore Visual Basic:  «Ejemplos!!!! Maldición ». Esta realmente me parece una brillante sugerencia para escritos técnicos de programación. Bien, así se hará, explicare con un ejemplos reales y resaltaré aquellas partes que se deberán reemplazar para beneficiarse del código.


Formularios Modales en  Componentes en Proceso

Los formularios modales tienen una misión clásica: Una ventana que recoge datos suministrados por el usuario y los devuelve a la aplicación para que haga algo. Asi pues, los formularios modales son idóneos para ser empaquetados en componentes. Un programa bien diseñado (con mentalidad de objetos) no almacena formularios que se presentarán modales en su proyecto principal.

Ejecución proceso significa una DLL ActiveX, o un Control ActiveX, instalados en el PC del cliente. En general aquí hablaré de componentes de código, más precisamente DLLs ActiveX, sin embargo, los Controles ActiveX a la larga son componentes de código con ciertas particularidades: Básicamente: (1) tiene interfaz gráfica para ser perfilada en tiempo de diseño, y (2) el contenedor (generalmente un Formulario) tras bambalinas controla la vida del control. Los formularios empaquetados en controles ActiveX perfectamente pueden seguir las reglas de este articulo.

Por demás, bebiera seguir las normas de diseño de formularios modales, p.e. BorderStyle=Fixed (yo prefiero Fixed ToolWindow), TabStrips, y así sucesivamente. También es conveniente usar el prefijo dlg, en vez de frm, para los formularios que se presentaran modales.

Según mi criterio, la estrategia de código para suministrar formularios depende de dos casos: Formularios Modales Eventuales y Formularios Modales Frecuentes (en memoria).


1. Formularios Modales Eventuales Pasivos
Me refiero a aquellos que se usan muy pocas veces dentro de la aplicación y no suministran servicios activos (por ejemplo el Formulario Acerca De). Es decir, los podemos cargar en memoria cuando se solicitan, y eliminarlos cuando se cierran. Este caso es el más sencillo de programar, observe el código de la clase que envuelve un formulario dlgAbout:

'// CLASE       : cls_About
'// DESCRIPCIÖN : Envuelve un Formulario Modal Volatil Pasivo

Option Explicit

Public Sub ShowDialog(ClientApp As Object)
    Dim dlg As dlgAbout

    If Not TypeOf ClientApp Is App Then
       Exit Sub
    End If

    Set dlg = New dlgAbout
    Load dlg
    With dlg
        .FillLabels ClientApp
        .Show vbModal
    End With
    Unload dlg
End Sub

El código dentro del Formulario es el siguiente:

'// FORMULARIO  : dlgAbout
'// DESCRIPCIÖN : Muestra de un Formulario Modal Volatil Pasivo

Option Explicit

Public Sub FillLabels(App As App)
    lblTitle = App.Title
    '//...and so forth
End Sub

Private Sub cmdOk_Click()
    Hide
    DoEvents
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    If UnloadMode = vbFormControlMenu Then
       cmdOk.Value = True
    Else
       Unload Me
    End If
End Sub

Como el formulario es pasivo (no toma ni retorna datos), no se requiere de más código. Noté que el método ShowDialog tiene parámetros para configurar el formulario, y este a su vez tiene el método publico FillLabels. La lista de parámetros en este método la usaria para pasar la información con la cual se configurará el formulario.

Todo aquello que vaya en cursiva, dentro de los bloques de código, es lo que Ud. debiera modificar para emplear el modelo.

 

Finalmente, el código en el cliente que invoca el formulario (supongamos un botón cmdAbout) es:

Private Sub cmdAbout_Click()
    Dim obj As cls_About
    Set obj = New cls_About
    obj.ShowDialog App
    Set obj = Nothing
End Sub

 

2. Formularios Modales Eventuales que Retornan Datos

Otro es el caso para formularios eventuales que retornan datos al cliente. Simplemente delegamos los valores recogidos en controles como propiedades limpias de la clase. Además, el formulario deberá tener un botón Acept y uno Cancel. Normalmente, en beneficio de usar teclado, se sigue la lógica de que el botón Acept tiene la propiedad Default=True, y el botón Cancel tiene la propiedad Cancel=True. He aquí el ejemplo:


Se trata de un formulario dlgLoging, el cual recoge el nombre y contraseña del usuario. Normalmente este tipo de formulario se presenta una sola vez en el proceso de una aplicación. El modelo de código de la clase que envuelve este dialogo es el siguiente:

'// CLASE       : cls_Loging
'// DESCRIPCIÖN : Envuelve un formulario modal eventual que devuelve datos

Option Explicit

'//RETORNO
Public Acept As Boolean

'//RETORNO DEL CUADRO DE DIALOGO
Public User As String
Public Password As String

Public Sub ShowDialog()
    Dim dlg As dlgLoging

    Set dlg = New dlgLoging
    Load dlg
    With dlg
        .Show vbModal
        Acept = .Acept
        If Acept Then
           User = .txtUser
           Password = .txtPassword
        End If
    End With
    Unload dlg
End Sub

 

La clase retorna limpiamente User y Password como propiedades, siempre que el usuario usa el comando Aceptar.

Note que implemente las propiedades User y Password como variables publicas. En general, en diseño de objetos poco se hace, es más conveniente usar la pareja de procedimientos Get y Let. No obstante, en este caso puede ser una excepción y es perfectamente válido, ya que realmente son delegaciones simples y es el formulario quien debiera filtrar los datos si fuese necesario. Por ejemplo tras el botón Acepar, se podrían usar procedimientos de validación.

 

Los valores que retorna el dialogo provienen de valores de controles en el formulario, como TextBox, CheckBox, etc. En este ejemplo son dos TextBox. El modelo de código en el formulario es el siguiente:

'// FORMULARIO  : dlgLoging
'// DESCRIPCIÖN : Formulario modal eventual que devuelve datos

Option Explicit

'//RETORNO
Public Acept As Boolean

Private Sub cmdAcept_Click()
    '//Valida Datos. Si son validos los datos sigue con:
    Hide
    DoEvents
    Acept = True
End Sub

Private Sub cmdCancel_Click()
    Hide
    DoEvents
    Acept = False
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    If UnloadMode = vbFormControlMenu Then
       cmdCancel.Value = True
    Else
       Unload Me
    End If
End Sub

El código en el cliente debe seguir este modelo (evaluamos la propiedad Acept):

Private Sub cmdModalVolatil_Click()
    Dim obj As cls_Loging
    Set obj = New cls_Loging
    With obj
        .ShowDialog
        If .Acept Then
            MsgBox "Procesa el formulario anterior"
            Debug.Print "User ="; .User
            Debug.Print "Password ="; .Password
        End If
    End With
    Set obj = Nothing
End Sub

Este mismo ejemplo fue el que use en el Anexo I para mostrar la implementación usando interfaces múltiples.

3. Formularios Modales Frecuentes (En Memoria)
Son aquellos Formularios que se usarán frecuentemente, por ejemplo aquel formulario que pide los parámetros del filtro para una consulta. Como caso clásico tenemos el formulario Buscar. Es conveniente mantener estos formularios en memoria por razones de rapidez de la aplicación.
En este caso es conveniente jugar con eventos, dan una salida limpia e intuitiva. El ejemplo que seguiré será un formulario que lee un nombre y apellido de un empleado, un clásico dlgGetEmployee. El modelo de código en la clase que envuelve el dialogo es el siguiente (recuerde que las palabras en cursiva indican aquel código que cambia para un caso particular):

'//CLASE       : cls_GetEmployee
'//DESCRIPCIÖN : Envuelve un formulario modal en memoria

Option Explicit

'//FORMULARIO
Dim WithEvents dlg As dlgGetEmployee

'//RETORNO
Public Acept As Boolean

'//Retorno del cuadro de dialog
Public EmployeeFirstName As String
Public EmployeeLastName  As String

Private Sub Class_Terminate()
    If Not dlg Is Nothing Then
       Unload dlg
    End If
End Sub

Public Sub ShowDialog()
    If dlg Is Nothing Then
       Set dlg = New dlgGetEmployee
       Load dlg
    End If
    dlg.Show vbModal
End Sub

Private Sub dlg_Acept(Value As Boolean)
    Acept = Value
    If Value Then
       EmployeeFirstName = dlg.txtFirstName
       EmployeeLastName = dlg.txtLastName
    End If
End Sub

Como regla estándar, el formulario debe tener un botón Aceptar y un botón Cancelar. También, los valores que retorna el dialogo provienen de valores de controles en el formulario, como TextBox, CheckBox, etc. El modelo de código en el formulario es:

'// FORMULARIO  : dlgGetEmployee
'// DESCRIPCIÖN : Modelo de formulario modal en memoria

Option Explicit

'//EVENTS
Public Event Acept(Value As Boolean)

Private Sub cmdAcept_Click()
    '//Valida Datos. Si son validos los datos sigue con:
    Hide
    DoEvents
    RaiseEvent Acept(True)
End Sub

Private Sub cmdCancel_Click()
    Hide
    DoEvents
    RaiseEvent Acept(False)
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    If UnloadMode = vbFormControlMenu Then
       cmdCancel.Value = True
    Else
       Unload Me
    End If
End Sub

El código en el cliente debe seguir este modelo:

'//Cuadro de dialogo en memoria
Private dlgGetEmployee As cls_GetEmployee

Private Sub cmdGetEmployee_Click()
    With dlgGetEmployee
        .ShowDialog
        If .Acept Then
            MsgBox "Procesa el formulario anterior"
            Debug.Print "EmployeeFirstName ="; .EmployeeFirstName
            Debug.Print "EmployeeLastName ="; .EmployeeLastName
        End If
    End With
End Sub

Private Sub Form_Load()
    Set dlgGetEmployee = New cls_GetEmployee
End Sub

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

La clase se mantiene en memoria del cliente, pero el formulario solo se cargará la primera vez que es llamado. Esto es un buen diseño.


ANEXO I

Usando Programación Orientada a Interfaces Múltiples

La Programación Orientada a Interfaces Múltiples suministra un estilo de programación lejanamente superior a la programación tradicional. Representa el perfil mejor elaborado de programación de objetos y reutilización de código con Visual Basic (si bien, supera en varios aspectos al paradígma dogmático de la OOP).

Escribiré el ejemplo de «Formularios Modales Eventuales que Retornan Datos», usando interfaces. Se requieren tres pasos.

Paso 1: Definir la interfaz abstracta. Como repasará en el caso que traté (numeral 2), la clase solo suministra dos atributos: La propiedad Acept y el método ShowDialog. Bien, la interfaz abstracta, que he bautizado IVolatilModal, es así de simple:

'//CLASE : IVolatilModal
'//DESCRIPCIÓN : Interfaze que envuelve un formulario modal

Public Property Get Acept() As Boolean
End Property

Public Sub ShowDialog()
End Sub

Note que la propiedad Acept tiene solo el procedimiento Get, lo cual significa que la interfaz suministra la propiedad para solo lectura.

Recuerde que para las clases abstractas, la propiedad Insancing debe ser 2-PublicNotCreatable.


Paso 2: Implementar la interfaz en la clase que envuelve al formulario.
La clase que envuelve el formulario cambia ligeramente. Los datos que sede el cuadro de dialogo son atributos particulares de la interfaz que implementa IVolatiolModal, en este ejemplo llamaré a esta nueva interfaz CLoging. Aquí esta el código de la clase que envuelve el formulario:

'//CLASE       : CLoging
'//DESCRIPCIÓN : Modelo de clase que envuelve un formulario modal

Option Explicit

'//Implementa la interfaz
Implements IVolatilModal

'//Miembro heredado de la Implementación
Private Acept As Boolean

'//Retrono del cuadro de dialogo
Public User As String
Public Password As String


Private Property Get IVolatilModal_Acept() As Boolean
    IVolatilModal_Acept = Acept
End Property

Private Sub IVolatilModal_ShowDialog()
    Dim dlg As dlgLoging
    Set dlg = New dlgLoging
    Load dlg
    With dlg
        .Show vbModal
        Acept = .Acept
        If Acept Then
           User = .txtUser
           Password = .txtPassword
        End If
    End With
    Unload dlg
End Sub


NOTA. En mi privada norma de codificación (si se puede llamar Húngara), uso el prefijo cls_ClassName para clases con una única interfaz. Mientras que uso CClassName para clases con varias interfaces. No me ponga cuidado, solo es un pequeño capricho.

El código dentro del formulario ni su interfaz cambian, así que no hay necesidad de repetir estas líneas. Esto quiere decir que solo cambia la implementación del formulario dentro del componente.


Paso 3. Usar la Interfaz en el cliente.
Ahora debemos declarar un objeto para la interfaz y otro para la clase particular que implementa la interfaz. El código tiene esta forma (como ejemplo, un botón cmdLogging que muestra el formulario):

Private Sub cmdLogging_Click()
    Dim dlg As CLoging      '//Subclase
    Dim vm As IVolatilModal '//Superclase

    Set dlg = New CLoging
    Set vm = dlg
    With vm
        .ShowDialog
        If .Acept Then
            MsgBox "Procesa el formulario anterior"
            Debug.Print "User ="; dlg.User
            Debug.Print "Password ="; dlg.Password
        End If
    End With
    Set dlg = Nothing
End Sub

Este caso se vé muy elegante. Sin embargo, seré sincero, este caso es simple. Los demás modelos requieren algo más profundo y complicado para poder implementar interfaces. Por ejemplo, analice las consecuencias de los eventos. Buena suerte.

¿Porque emplear Interfaces Múltiples?

A parte de tener un código reutilizable, suministrar una plantilla de diseño, y tener un código listo para ser usado en desarrollos pesados, tenemos nada menos y nada más que poliformismo. Por ejemplo, podríamos agrupar formularios de acceso a datos que implementen la interfaz IVolatilModal (algo más elaborada) y administrarlos desde procedimientos únicos.