Crear un formulario de usuario

Los formularios de usuario son cuadros de diálogo personalizados que se utilizan para recoger datos de entrada del usuario al inicio de una macro. El Constructor de Formularios de Usuario incluido en Code VBA crea automáticamente Formularios de Usuario. Los formularios de usuario generados tienen un código de validación de entrada añadido para asegurar que toda la entrada requerida es del tipo correcto. Esto hará que la macro sea más fiable. Tener los controles y el código de validación generados ahorra mucho tiempo que tendría que gastar para crear el formulario de usuario manualmente. El formulario de usuario puede modificarse tanto para hacer el diseño visualmente más atractivo moviendo y redimensionando los controles. Además se puede extender el código vba del userform para cumplir requisitos adicionales.

Uso del constructor de UserForm

El Constructor de UserForm se inicia desde el menú Código VBA:

Nombre del formulario de usuario: Controla Nombre Tipo de datos Tipo de control Requerido Seleccionar nombres... Cree creador de formularios de usuario

En el UserForm Builder especificará

  • UserForm Name: dar un nombre al formulario que sea una abreviatura de los datos que se van a introducir. En el ejemplo el nombre era 'Pedido
  • La rejilla de controles es el área en la que se especifican los controles que se desean incluir en el formulario de usuario que se está creando. Cada control se especifica en una línea separada con propiedades:
    • Nombre: se utilizará como etiqueta para el control de entrada.
    • Tipo de datos: si el usuario introduce un valor de tipo incorrecto en el control, al pulsar el botón Aceptar aparece un mensaje al usuario indicando que el valor de ese campo es incorrecto y devuelve el cursor a ese campo para que el usuario lo corrija.
    • Tipo de control: permite especificar qué tipo de control se prefiere. Un booleano (sí/no) puede representarse de forma fácil de usar mediante un control
    • Obligatorio: si el usuario no introduce un valor en el control, al pulsar el botón Aceptar aparece un mensaje para el usuario indicando que falta un valor para ese campo y coloca el cursor en ese campo.
  • Seleccionar nombres...: abre una cuadro de entrada de selección de rango para permitirle seleccionar uno o más nombres de su hoja Excel. Esto es útil si su cuadro de diálogo se crea para rellenar un formulario o tabla de Excel existente.
  • Create: crea el UserForm - imagen de abajo - e inserta el código para iniciar el userform.
creador de formularios de usuario

UserForm Código VBA

Además del objeto UserForm, el constructor también añade tres piezas de código:

Código para abrir / mostrar el formulario de usuario

Asumiendo que el cursor estaba dentro del procedimiento Sub Demo, después de presionar OK en el UserForm Builder el procedimiento se vería como se muestra a continuación.


Sub Demo()
    Dim udtOrder As Order
    With udtOrder
        .Client = ""
        .EntryDate = Date
        .Product = ""
        .Attention = True
    End With
    ufmOrder.FillList "cboProduct", Array("v1", "v2", "v3")
    ufmOrder.SetValues udtOrder
    ufmOrder.Show
    If Not ufmOrder.IsCancelled Then
        ufmOrder.GetValues udtOrder
        ''continue process after OK here
        With udtOrder

        End With
    End If
    Unload ufmOrder
End Sub

Para obtener una interfaz limpia se utiliza un Tipo Definido por el Usuario que comprende los controles de datos con sus definiciones de tipo en el formulario de usuario. Se puede alterar el código entre el primer With y End With para cambiar los valores iniciales o por defecto. El paso de los valores al formulario se realiza en el método SetValues

El procedimiento FillList se utiliza para pasar una matriz de valores al cuadro de lista Producto. Los valores del array ("v1",...) son sólo un ejemplo que tendrás que adaptar.

El método Show es la forma estándar de abrir el UserForm.

El usuario puede pulsar OK o Cancelar el diálogo. Esto se determina comprobando la propiedad .IsCancelled.

Si el usuario pulsó OK la macro continuará utilizando los datos que el usuario proporcionó en el formulario de usuario. Para ello añadirá su propio código de proceso entre el segundo With y End With.

Tipo definido por el usuario para una interfaz limpia

El tipo definido por el usuario generado proporciona una interfaz limpia entre la macro y el formulario de usuario. Si el procedimiento desde el que llama al formulario de usuario está en un módulo estándar, el tipo definido por el usuario se colocará allí. Si no, se colocará en un módulo llamado 'modTypes'. En el ejemplo, la declaración del tipo tiene este aspecto:


Public Type Order
    Client As String
    EntryDate As Date
    Product As String
    Attention As Boolean
End Type
Nota - Constructor de tipos definidos por el usuario
Similar al constructor explicado aquí Code VBA también incluye un Constructor de Tipos Definidos por el Usuario:

Validación de UserForm y otro código

Dentro del UserForm generado hay todavía bastante código que se utiliza para:

Manejo de OK y Cancelar

El siguiente código muestra el manejo de Ok y Cancelar. El botón Cerrar también se maneja adecuadamente como Cancelar sin necesidad de código adicional. La variable pública IsCancelled se utiliza para comunicar a la macro de llamada si el usuario ha pulsado Ok o Cancelar.


Public IsCancelled As Boolean

Private Sub UserForm_Initialize()
    IsCancelled = True
End Sub

Private Sub btnCancel_Click()
    Me.Hide
End Sub

Private Sub btnOk_Click()
    If IsInputOk Then
        IsCancelled = False
        Me.Hide
    End If
End Sub

Pasar datos al formulario de usuario

Los datos se pasan al formulario de usuario y se obtienen de él mediante SetValues y GetValues respectivamente. Ambos utilizan la variable User Defined Type.


Public Sub SetValues(udtOrder As Order)
    With udtOrder
        SetValue Me.txtClient, .Client
        SetValue Me.txtEntryDate, .EntryDate
        SetValue Me.cboProduct, .Product
        SetValue Me.cbxAttention, .Attention
    End With
End Sub

Public Sub GetValues(ByRef udtOrder As Order)
    With udtOrder
        .Client = GetValue(Me.txtClient, TypeName(.Client))
        .EntryDate = GetValue(Me.txtEntryDate, TypeName(.EntryDate))
        .Product = GetValue(Me.cboProduct, TypeName(.Product))
        .Attention = GetValue(Me.cbxAttention, TypeName(.Attention))
    End With
End Sub

SetValues y GetValues se aplican mediante los procedimientos que se indican a continuación:


Private Sub SetValue(ctl As MSForms.Control, value As Variant)
    On Error GoTo HandleError
    ctl.value = value
HandleExit:
    Exit Sub
HandleError:
    Resume HandleExit
End Sub

Private Function GetValue(ctl As MSForms.Control, strTypeName As String) As Variant
    On Error GoTo HandleError
    Dim value As Variant
    value = ctl.value
    If IsNull(value) And strTypeName <> "Variant" Then
        Select Case strTypeName
        Case "String"
            value = ""
        Case Else
            value = 0
        End Select
    End If
HandleExit:
    GetValue = value
    Exit Function
HandleError:
    Resume HandleExit
End Function

Validación del formulario de usuario

Cuando el usuario pulsa OK, los datos del UserForm son validados por la función IsInputOk. Esto comprueba para cada control de entrada IsInputControl si tiene un valor HasValue en caso de que sea necesario IsRequired. A continuación, comprueba si el valor es del tipo correcto IsCorrectType - como se especificó en UserForm Builder. Si no se supera alguna de las dos pruebas, se envía un mensaje al usuario y se pone el foco en el control cuyo valor ha fallado ctl.SetFocus.


Private Function IsInputOk() As Boolean
    Dim ctl As MSForms.Control
    Dim strMessage As String
    IsInputOk = False
    For Each ctl In Me.Controls
        If IsInputControl(ctl) Then
            If IsRequired(ctl) Then
                If Not HasValue(ctl) Then
                    strMessage = ControlName(ctl) & " must have value"
                End If
            End If
            If Not IsCorrectType(ctl) Then
                strMessage = ControlName(ctl) & " is not correct"
            End If
        End If
        If Len(strMessage) > 0 Then
            ctl.SetFocus
            GoTo HandleMessage
        End If
    Next
    IsInputOk = True
HandleExit:
    Exit Function
HandleMessage:
    MsgBox strMessage
    GoTo HandleExit
End Function

Observe que IsCorrectType utiliza la función ControlDataType que simplemente devuelve el tipo para el control dado utilizando las sentencias Select Case insertadas por el UserForm Builder. Un enfoque similar pero más simple se sigue para IsRequired.


Private Function IsCorrectType(ctl As MSForms.Control) As Boolean
    Dim strControlDataType As String, strMessage As String
    Dim dummy As Variant
    strControlDataType = ControlDataType(ctl)
    On Error GoTo HandleError
    Select Case strControlDataType
    Case "Boolean"
        dummy = CBool(GetValue(ctl, strControlDataType))
    Case "Byte"
        dummy = CByte(GetValue(ctl, strControlDataType))
    Case "Currency"
        dummy = CCur(GetValue(ctl, strControlDataType))
    Case "Date"
        dummy = CDate(GetValue(ctl, strControlDataType))
    Case "Double"
        dummy = CDbl(GetValue(ctl, strControlDataType))
    Case "Decimal"
        dummy = CDec(GetValue(ctl, strControlDataType))
    Case "Integer"
        dummy = CInt(GetValue(ctl, strControlDataType))
    Case "Long"
        dummy = CLng(GetValue(ctl, strControlDataType))
    Case "Single"
        dummy = CSng(GetValue(ctl, strControlDataType))
    Case "String"
        dummy = CStr(GetValue(ctl, strControlDataType))
    Case "Variant"
        dummy = CVar(GetValue(ctl, strControlDataType))
    End Select
    IsCorrectType = True
HandleExit:
    Exit Function
HandleError:
    IsCorrectType = False
Resume HandleExit
End Function

Private Function ControlDataType(ctl As MSForms.Control) As String
    Select Case ctl.Name
    Case "txtClient": ControlDataType = "String"
    Case "txtEntryDate": ControlDataType = "Date"
    Case "cboProduct": ControlDataType = "String"
    Case "cbxAttention": ControlDataType = "Boolean"
    End Select
End Function

Private Function IsRequired(ctl As MSForms.Control) As Boolean
    Select Case ctl.Name
    Case "txtClient", "txtEntryDate", "cboProduct", "cbxAttention"
        IsRequired = True
    Case Else
        IsRequired = False
    End Select
End Function