lunes, 25 de junio de 2018

Compartir macros y Userforms

El tema de hoy tiene que ver con la posibilidad de llamar a diferentes Userforms o subrutinas desde distintos procesos, según algún criterio común a todos.

Primer Caso: Imaginemos la siguiente situación: tenemos  varios userforms y todos utilizan una misma lista de clientes. Para no repetirla podremos mostrarla en otro formulario al que tengan acceso los anteriores.

Entonces vamos a crear un Userform con una lista (ListBox) y en evento doble clic de la misma volcaremos el registro seleccionado al formulario que lo llamó.

Veamos cómo programarlo:
Aquí tenemos 3 formularios (Clientes, Ventas y Cobros) que utilizarán la misma lista que se encuentra en el formulario UF_Lista.
En un módulo tendremos las subrutinas que llaman a cada formulario y al inicio una declaración de variable pública (llamaUf)

Entonces cada formulario tendrá un botón de llamada con las siguientes instrucciones, donde se guardará un número para indicar de qué formulario se trata (1 para Clientes, 2 para Ventas y 3 para Cobros)
     Private Sub CommandButton2_Click()
     llamaUf = 3
     UF_Lista.Show
     End Sub


Solo falta ver cómo se devuelve la información al control Cliente de cada formulario luego de la selección de la lista:

El evento que utilizamos en el ListBox es el DobleClick y el bucle es del tipo Select Case... End Select.
   Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
   If ListBox1.ListIndex = -1 Then
       MsgBox "Debes seleccionar un elemento con doble clic"
       Exit Sub
   End If
   Select Case llamaUf
   Case Is = 1
       UF_Clie.TextBox1 = ListBox1.List(ListBox1.ListIndex)
   Case Is = 2
       UF_Ventas.TextBox3 = ListBox1.List(ListBox1.ListIndex)
   Case Is = 3
       UF_Cobros.TextBox4 = ListBox1.List(ListBox1.ListIndex)
   End Select
   'se cierra el uf
   Unload Me
   End Sub

NOTA: si son pocas las opciones también se puede utilizar un bucle del tipo If...Elseif....End If
   If llamaUf = 1 Then
       UF_Clie.TextBox1 = ListBox1.List(ListBox1.ListIndex)
   Elseif llamaUf = 2 Then
      UF_Ventas.TextBox3 = ListBox1.List(ListBox1.ListIndex)
   End If




Segundo Caso: una misma macro es llamada desde varios procesos diferentes.
Aquí tendremos algo similar a lo anterior pero los procesos no llamarán a un formulario sino a una macro común a todos, que se encontrará ubicada en un módulo.

Imaginemos esta situación: tenemos 3 hojas con información y en algún proceso una hoja debe ser ordenada por una columna en especial. En otro proceso otra hoja también debe ser ordenada pero no necesariamente por la misma columna sino por otra y así con todas las hojas.

Entonces, primero vamos a declarar las variables públicas al inicio de un módulo (*) para guardar la siguiente información:
- nombre de la hoja que debe ser ordenada.
- rango ocupado por la tabla de datos.
- columna por la que hay que ordenar esa tabla.

(*) Se declaran como Public si van a ser utilizadas fuera del módulo donde están declaradas y con Dim si van a ser utilizadas en subrutinas dentro del mismo módulo.

     Public hojax As String   'para indicar quién llamó a la macro de orden
     Dim colOrden As String, rgoOrd As String

Ya con eso, cada botón o proceso que llame al ordenamiento, tendrá estas instrucciones donde irán cambiando el nombre de las hojas:

     Sub ordenaClie()
     hojax = "Clientes"      'podría ser: hojax = ActiveSheet.Name si es un proceso que se ejecutará desde la hoja activa.
     Call macroOrdena
     End Sub

La macroOrdena primero selecciona la hoja, guarda las referencias de rango y columna a ordenar y luego llama a la subrutina de orden propiamente dicha.

Sub macroOrdena()
'se selecciona la hoja solicitada
Sheets(hojax).Select
'se establece la última fila con datos de la hoja activa
filx = Range("A" & Rows.Count).End(xlUp).Row

'según la hoja será el rango y criterio de orden
If ActiveSheet.Name = "Clientes" Then
    colOrden = "D2:D" & filx
    rgoOrd = "A1:F" & filx
ElseIf ActiveSheet.Name = "Ventas" Then
    colOrden = "E3:E" & filx
    rgoOrd = "A2:E" & filx
ElseIf ActiveSheet.Name = "Cobros" Then
    colOrden = "D2:D" & filx
    rgoOrd = "A1:D" & filx
Else
    'en otras hojas no se ejecuta
    Exit Sub
End If

'se ejecuta el ordenamiento
Call ordenando
Range("A1").Select
End Sub

Sub ordenando()
Application.ScreenUpdating = False
    Range(rgoOrd).Select
    ActiveWorkbook.Worksheets(hojax).Sort.SortFields.Clear
    ActiveWorkbook.Worksheets(hojax).Sort.SortFields.Add Key:=Range(colOrden) _
        , SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
    With ActiveWorkbook.Worksheets(hojax).Sort
        .SetRange Range(rgoOrd)
        .Header = xlYes
        .MatchCase = False
        .Orientation = xlTopToBottom
        .SortMethod = xlPinYin
        .Apply
    End With
End Sub

Como se puede observar, la última subrutina es la que nos genera la grabadora de macros cuando necesitamos ordenar un rango. Y en lugar de tenerla repetida por cada hoja aquí la tenemos 1 sola vez.

En el libro de ejemplo, se agregó otra que vuelve todas las hojas a su orden normal, en este caso por col A. Y esa macro también,  luego de asignar a cada hoja su rango,  utiliza la subrutina anterior llamada 'ordenando'. 

Ver VIDEO N° 9

Descargar el ejemplo desde aquí.





2 comentarios:

  1. Hola!!, está genial!!!!....Pregunta: y si lo que quiero usar es un combobox (con mi lista de clientes) en distintos userforms, en vez de un listbox?

    ResponderBorrar
  2. Si Gra, también es posible. Enviarás a cada control el elemento que hayas seleccionado en el Combobox. Por ejemplo:
    UF_Clie.TextBox1 = ComboBox1.Text
    Sdos!

    ResponderBorrar