Formularios en Componentes de Código


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

Por Harvey Triana

En la primara parte de este articulo trate de los formularios empaquetados en componentes de código y que se presentarán como modales. Bien, ese caso es relativamente sencillo comparado con la utilización de formularios no modales en componentes. Realmente esto requiere un dominio de Visual Basic algo más depurado.

Existe una precaución en la utilización de formularios no modales en componentes en proceso, pero realmente no hay porque prescindir de este recurso si se usa un modelo de código bien diseñado. El ejemplo más frecuente y util de formularios no modales en DLLs, lo representan las ToolBar emergentes, por lo tanto expondré este diseño. De otra parte, también es posible manejar en código formularios instansiables no modales.

 
Formularios No Modales en  Componentes en Proceso

Explicaré a través de ejemplos los dos casos respectivamente: (1) Formularios con una única instancia, y (2) Formularios que pueden suministrar varias instancias.

Formulario No Modal en Componente en Proceso, Unica Instancia
El caso clásico lo representan los formularios de herramientas flotantes (emergentes), conocidos comúnmente como ToolBars. Una Toolbar permite al usuario escoger opciones a partir de iconos, en una interfaz cómoda. Para un mejor desempeño de la aplicación, la mejor estrategia es mantener el formulario en memoria.
El ejemplo que expondré es un sencillo Form, con Name=frmToolBoxSample y que incorpora una matriz de cuatro CommandButton, con Name=cmdOption. Sigue esta ilustración:

Normalmente usaremos controles especializados como el Toolbar de la biblioteca Microsoft Windows Common Controls, no obstante, para simplificar el asunto, este sencillo ejemplo es suficientemente claro. El siguiente modelo suministra el código para la clase que envuelve un formulario flotante:

'//CLASE:        cls_ToolBox
'//DESCRIPCIÖN : Envuelve un Formulario No Modal Flotante

Option Explicit

'//EVENTS
Public Event ReturnOption(Value As Integer, Name As String)

'//FORM
Private WithEvents frm As frmToolBoxSample

'//API for floating window
'//Based in a Tip of MVP Joe LeVasseur, VBONLINE
Private Declare Function SetWindowPos Lib "user32" ( _
                ByVal hwnd As Long, ByVal hWndInsertAfter As Long, _
                ByVal x As Long, ByVal y As Long, ByVal cx As Long, _
                                    ByVal cy As Long, ByVal wFlags As Long) As Long

Private Const SWP_NOMOVE     As Long = &H2
Private Const SWP_NOSIZE     As Long = &H1
Private Const SWP_NOACTIVATE As Long = &H10
Private Const SWP_SHOWWINDOW As Long = &H40
Private Const HWND_TOPMOST   As Long = -1
Private Const HWND_NOTOPMOST As Long = -2

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

Private Sub frm_ReturnOption(Value As Integer, Name As String)
    '//Delega el evento
    RaiseEvent ReturnOption(Value, Name)
End Sub

Public Sub ShowForm(Optional Floating As Boolean = True)
    If frm Is Nothing Then
       Set frm = New frmToolBoxSample
       Load frm
    End If
    '//Muestra el formulario
    If Floating Then
       If SetWindowPos( _
          frm.hWnd, HWND_TOPMOST, 0, 0, 0, 0, _
          SWP_NOMOVE Or SWP_NOSIZE Or SWP_SHOWWINDOW Or SWP_NOACTIVATE _
       ) Then
         frm.SetFocus
       End If
    Else
       frm.Show vbModeless
    End If
End Sub

El código anterior se ve un poco extenso, pero solo se debe a la declaración y constantes de la API SetWindowPos, cuya función es habilitar como flotante (emergente) el formulario que envuelve. El método ShowForm podría llevar otros argumentos que permitan configurar de manera particular el ToolBox. Note que la clase suministra un evento al cliente: ReturnOption.

El siguiente modelo es el código para el formulario:

'//FORM        : TOOLBOX SAMPLE
'//DESCRIPCIÖN : Formulario No Modal Flotante (opcional)

Option Explicit

Public Event ReturnOption(Value As Integer, Name As String)

Private Sub cmdOption_Click(Index As Integer)
    RaiseEvent ReturnOption(Index, cmdOption(Index).Caption)
End Sub

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

El código que usará en el cliente será:

Option Explicit

'//OBJETOS
Private WithEvents tbSample As cls_ToolBox

'//algun comando que muestra el ToolBox
Private Sub cmdToolBoxSample_Click()
    tbSample.ShowForm
End Sub

Private Sub Form_Load()
    Set tbSample = New cls_ToolBox
End Sub

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

Private Sub tbSample_ReturnOption(Value As Integer, Name As String)
    '//Este evento se genera cuando se selecciona un ítem
    Me.SetFocus '//Importante cuando se usa una ventana flotante
    MsgBox "Fue seleccionada la opción: " & Value
    '//Hace algo con los parámetros retornados
End Sub

Como podrá analizar, al usar eventos el código se hace flexible e intuitivo. Las palabras en cursiva seria lo que el programador modificaría para construir su ToolBox particular.

Formulario No Modal en Componente en Proceso, Unica Instancia, No Flotante
Este caso es idéntico al expuesto anteriormente, la excepción es que no requiere la implementación para hacer flotante la ventana. Quitamos todo lo referente a la API SetWindowPos, y tenemos el código. Realmente este tipo de formulario es poco frecuente en aplicaciones.
Formulario No Modal en Componente en Proceso, Múltiples Instancias
Este es el caso más complicado de formularios en componentes de código. En la documentación de Visual Basic hacen reiteradas advertencias de su utilización, vea la siguiente nota textual:

medieval.gif (1144 bytes)

La presentación de formularios no modales desde componentes en proceso requiere comunicación con el bucle de mensajes del cliente. No todos los clientes lo permiten. Para ver una explicación de esta limitación, vea "Presentar formularios desde componentes de código" en "Generar componentes de código".

En general, escribiremos componentes en proceso para mostrar formularios no modales en clientes de Visual Basic y herramientas que suministren Visual Basic para Aplicaciones. Para más información, consultar «Presentar formularios desde componentes de código» de la documentación MSDN o Libros en Pantalla (VB5). También podría buscar documentación para la palabra «NonModalAllowed».

Antes de preocuparse por saber que quiere decir «comunicación con el bucle de mensajes del cliente», por cierto una documentación algo ambigua, un buen diseño con código solucionará el problema fácilmente. Se requiere un fuerte control en la clase que administra los formularios. La técnica que muestro a continuación se basa en la colección Forms del componente (DLL) y en identificadores programados en los formularios. Aplico esta técnica en una de mis mejores aplicaciones de gestión.
Básicamente nos basamos en una clase que controla la creación y eliminación global de instancias de un tipo de formulario especifico. El formulario deberá tener una propiedad que lo identifique como único, y que esta propiedad se conserve durante su vida. En los casos que he empleado formularios instanciables en componentes, esta propiedad, o Id de Formulario, ha sido la clave de un registro en una basa de datos.
En el siguiente ejemplo, expongo un caso al extremo simple, un formulario con un TextBox multi-línea, el cual cubre toda el área del formulario. Un capturador de notas rústico. El código de la clase cls_ModelessNotes, la cual envuenve los formularios, es el siguiente:

'// CLASE       : cls_ModelessNotes
'// DESCRIPCION : Modelo de clase que envuelve un formulario no modal

Option Explicit

'//EVENTS
Public Event Busy(Value As Boolean)
Public Event CustomError(Index As Long)

'//Filter another Forms in component
Private Const FormClassName As String = "frmNotes"

Public Sub ShowModeless(IdForm As Long, IdWell As Variant)
    Dim frm  As Form
    Dim Cont As Boolean

    Cont = True
    RaiseEvent Busy(True)

    For Each frm In Forms
        If frm.Name = FormClassName Then
           If IdForm = frm.IdForm Then
              frm.SetFocus
              Cont = False
              Exit For
           End If
        End If
    Next
    If Cont Then
       Call LoadInstance(IdForm, IdWell)
    End If
    RaiseEvent Busy(False)
End Sub

Private Function LoadInstance( _
        IdForm As Long, _
        IdWell As Variant _
      ) As Boolean

    Dim frm      As frmNotes
    Dim Cont     As Boolean
    Dim ErrIndex As Long

    Set frm = New frmNotes
    Load frm
    frm.IdForm = IdForm

    '//Validate iniatization
    Cont = frm.Init(IdWell, ErrIndex)
    If Not Cont Then
       Unload frm
       Set frm = Nothing
       RaiseEvent CustomError(ErrIndex)
    Else
       frm.Show vbModeless
    End If
    Set frm = Nothing
    LoadInstance = Cont
End Function

Private Sub Class_Terminate()
    Call UnloadAll
End Sub

Public Sub UnloadAll()
    Dim frm As Form
    For Each frm In Forms
        If frm.Name = FormClassName Then
           Unload frm
        End If
    Next
End Sub

Se agregaron los eventos Busy y CustomError, los cuales serán útiles en un desarrollo más pesado. El evento Busy servirá para que el cliente haga algo como mostrar un reloj de espera, un mensaje, etc. El evento CustumError servirá para pasar al cliente códigos de error (CustomError es para simplificar el asunto).

medieval.gif (1144 bytes)

Es preferible usar la tecnología de control de errores de Visual Basic, para más información, consultar «Generar y tratar errores en los componentes ActiveX», de la documentación MSDN o Libros en Pantalla (VB5).

El parámetro IDWell que se suministra en el método ShowModeless es solo una muestra de una clave que se pasa al componente para personalizar el formulario.

El código dentro del formulario del cual se crearán instancias (lo he llamado  frmNotes) es el siguiente:

'//FORMULARIO  : frmNotes
'//DESCRIPCIÓN : Modelo de formulario no modal

Option Explicit

'//KEY FORM
Private m_IdForm As Long

Private Sub Form_Resize()
    If Not Me.WindowState = vbMinimized Then
       txtMemo.Move 0, 0, Me.ScaleWidth, Me.ScaleHeight
    End If
End Sub

Public Property Get IdForm() As Long
    IdForm = m_IdForm
End Property

Public Property Let IdForm(ByVal v As Long)
    m_IdForm = v
End Property

Public Function Init(IdWell As Variant, ErrIndex As Long) As Boolean
    '//Required if the form must be configurate
    '//Sample:
    On Error GoTo ErrHandler

    Me.Caption = "Form of Key " & m_IdForm
    txtMemo = "IdWell = " & IdWell
    Init = True
    Exit Function

ErrHandler:
    ErrIndex = Err.Number
End Function

Finalmente, el código de lado del cliente es el siguiente:

Option Explicit

'//Formulario no modal multiples instancias
Private WithEvents mn As cls_ModelessNotes

Private Sub cmsModelessNotes_Click()
    '//Ejemplos
    mn.ShowModeless 12345, "DOR00012_E"
    mn.ShowModeless 12377, "ADORX018_X"
End Sub

Private Sub Form_Load()
    Set mn = New cls_ModelessNotes
End Sub

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


Notas sobre Formularios Componentes Fuera de Proceso

En general, los formularios en componentes fuera de proceso pueden seguir las mismas reglas expuestas en estos dos artículos. Si le inquieta el tema le sugiero "Comportamiento fuera de proceso de los formularios modales y no modales" de la documentación Visual Basic. Los formularios modales tienen poca aplicación, dado que se ejecutan en un proceso diferente, es decir serán modales para el componente, no para el cliente. Los formularios no modales pueden ser tener funciones interesantes aunque su implantación podría ser algo compleja (se supone que deben servir a varios clientes). En fin, quiza merezca una tercera parte este articulo, para cubrir este caso.