[titulo]
Formularios en Componentes de Código Técnicas Depuradas para Suministrar Formularios desde Componentes ActiveX con Visual Basic
Parte I, Formularios Modales en Proceso [/titulo]
|
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 C
ClassName 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.