martes, 12 de julio de 2022

60 - Combinar filas en un rango. Descombinarlas.

Combinar celdas es una de las tareas frecuentes en Excel. Ya sea que realicemos algún formulario o formato donde se deben ubicar diferentes campos en diferentes posiciones dentro del formato.

En el ejemplo, se trata de combinar solo las celdas que abarcan las col D:F de cierto formulario para el registro de Nombres y Apellidos.


La herramienta que utilizamos manualmente sería la de Combinar Celdas del menú Inicio, Alineación.

Con VBA, el método Merge es el que nos combina un rango establecido y Unmerge para descombinarlo.

A continuación diferentes macros para realizar esta tarea, según condiciones:

Ejemplo 1:  combinar celdas de una sola fila.

Sub combina()

[D6:F6].Merge

End Sub


Ejemplo 2: combinar todas las celdas de un rango en una sola. Se conservará solo el texto de la primera celda del rango seleccionado.

Sub combina_1()

Range("D6:F14").Merge

End Sub

Ejemplo 3: dentro de un rango, combinar las celdas de cada fila en un rango independiente. Colocando la expresión Merge en True.

Sub combina_2()

[D6:F14].Merge (True) 

End Sub     


NOTA: Podemos mejorar el código centrando cada fila combinada, de este modo:

Sub combina_Filas()

With [D6:F14]

    .Merge (True)                  'combina cada fila del rango en una sola celda.

    .HorizontalAlignment = xlCenter

End With

End Sub



Para descombinarlas, utilizaremos el método UnMerge. La instrucción será la misma para todas las situaciones, indicando el rango a descombinar.

Ejemplo 4:
                        Sub descombina()
                        [D6:F14].UnMerge
                        End Sub


Descargar ejemplo desde aquí.

Acceso al VIDEO N° 60 desde aquí.





martes, 31 de mayo de 2022

59 - Userform 'flotante'

Siguiendo con el tema de los objetos o controles que se moverán acompañando a la celda o fila activa, hoy dejo un ejemplo con 2 hojas para mostrar el comportamiento de los Userforms.

Debemos recordar que tanto los objetos insertados desde la ficha Programador/Desarrollador (ActiveX o de Formularios) como así también los insertados desde menú Insertar-Ilustraciones (imágenes, autoformas, iconos) tienen dos propiedades que tendremos en cuenta: TOP, o sea el margen superior y LEFT que será el margen izquierdo.

Pero los Userforms se manejan diferente. Apelamos a la propiedad StartUpPosition que nos permite elegir entre 4 valores: 0 (Manual), 1 (Centrado en la ventana Excel), 2 (Centrado en pantalla) y 3 (predeterminado de Windows).

Para modificar estos valores y asignarle las propiedades Top y Left correspondientes a una celda, recurrimos a programación. En libro adjunto se encuentra en un módulo la macro principal, y en el formulario las que corresponden al evento Initialize del formulario.


En este evento realizaremos los ajustes a los cálculos devueltos por la macro principal. Esta macro coloca el objeto 'sobre' la celda activa. Podemos modificar esto y ajustar esta posición según el ancho de la columna activa.


Descargar el libro de ejemplo desde aquí.

Acceso al VIDEO N° 59 desde aquí.

Otros videos relacionados con este tema: N° 13 y N° 58.




jueves, 19 de mayo de 2022

58 - Objetos flotantes - Uso de ICONOS

 Ya hemos visto en videos anteriores, como en el VIDEO N° 13, la posibilidad de mantener en la hoja controles de modo flotante. Es decir, que se irán moviendo a medida que avanzamos en las filas de una hoja.

Por ejemplo, si tenemos una tabla y deseamos ver el acumulado a medida que registramos filas, tendríamos un control dibujado desde la ficha Programador/Desarrollador, donde mostraremos la suma de los registros hasta ese momento. Esto será de utilidad considerando que las Tablas permiten agregar una fila de Totales pero al final de la misma.


La macro que utilizaremos se colocará en el Editor, en el objeto Hoja donde se encuentre la tabla. El evento a controlar será Worksheet_Change, o sea al cambio en la hoja.

Private Sub Worksheet_Change(ByVal Target As Range)

'controlar que la celda modificada se encuentre en col B a partir de fila 3

If Target.Column <> 2 Or Target.Row < 3 Or Target.Count > 1 Then Exit Sub

' se muestra en el label el total que se va acumulando

totx = Application.WorksheetFunction.Sum(Range("C3:C" & Target.Row))

Label1.Caption = Format(totx, "#,000.00")

'se ubica el control en la línea de la celda

ActiveSheet.Label1.Top = Range("B" & Target.Row).Top

End Sub

Hoy vamos a ver que también podemos utilizar 'OBJETOS' de modo 'flotante'.  Pueden ser imágenes, formas o ICONOS, que es la última novedad en las nuevas versiones Excel.

En el siguiente ejemplo, necesitamos mostrar diferentes iconos, según el valor ingresado en la col F con respecto a un valor de referencia que se encuentra en celda I2.

Primero insertaremos 3 objetos desde menú Insertar, Ilustraciones, Iconos. 

Estos objetos se llamarán 'Gráfico' más un índice y se activará un nuevo menú: Formato de gráfico. Podemos trabajarlos individualmente asignándole tamaño y color. 

También podemos 'Convertir a forma' el objeto seleccionado, activándose en ese caso el menú Formato de formas. Y trabajarlo por partes (modificar o quitar partes, con colores diferentes o no, etc).


Una vez terminada la tarea de formatear los objetos, tomaremos nota de sus nombres para ajustarlos en la siguiente macro. Como también se trata de controlar la modificación de una celda se colocará en el Editor, objeto Hoja donde se esté trabajando.

Aquí tendremos 2 eventos
   - Activate, para volver a colocar los objetos en fila 1 de un rango auxiliar.
   - WorkSheet_Change, o sea al cambio de una celda de la col F a partir de fila 3.

'declaración de una matriz que será utilizada en varios procesos. 

 Dim grafico()    


Private Sub Worksheet_Activate()

'cada vez que se activa la hoja se muestran todos los iconos en fila 1 de un rango auxiliar.

grafico = Array("Graphic 5", "Group 15", "Graphic 4")

For i = 0 To 2

    With ActiveSheet.Shapes.Range(Array(grafico(i)))

        .Visible = True

       'se pueden ubicar todos encima de la celda L1

        '.Top = [L1]: .Left [L1] 

       'o se colocan a 12 columnas más allá del índice i, lo que resultará en col L, M y N

        .Top = Cells(1, i + 12).Top: .Left = Cells(1, i + 12).Left

    End With

Next i

End Sub

 

Private Sub Worksheet_Change(ByVal Target As Range)

'se controla el cambio en la col F a partir de fila 3

If Target.Column <> 6 Or Target.Row < 3 Then Exit Sub

'se guarda en una variable el resultado de comparar el valor ingresado con respecto al valor de referencia.

If Target.Value < [I2] Then x = 0

If Target.Value = [I2] Then x = 1

If Target.Value > [I2] Then x = 2

'llamada a una subrutina que será común a los 3 casos, indicando el nro de gráfico a mostrar y la celda donde ubicarlo.

Call mueveGraf(x, Target.Row)     

End Sub

 

Sub mueveGraf(x, filx)

'recorre la colección de objetos de la hoja mostrando u ocultando según el valor de x

For i = 0 To 2

    If i = x Then

'si se trata del objeto que corresponde, se lo muestra

        With ActiveSheet.Shapes.Range(Array(grafico(i)))

            .Visible = True

            'y se lo coloca haciendo coincidir el tope y margen izquierdo con la celda que ha sido modificada.

            .Top = Cells(filx, 8).Top: .Left = Cells(filx, 8).Left

        End With

    Else

        'si no se trata del objeto correspondiente, se lo oculta

        ActiveSheet.Shapes.Range(Array(grafico(i))).Visible = False

    End If

Next i

End Sub



Descargar libro de ejemplo desde aquí.

Acceso al VIDEO N° 58 desde aquí.



domingo, 8 de mayo de 2022

57 - ¡Fuera Ceros ! Cómo ocultar ceros en hojas Excel

Hay trabajos donde nos quedan mejor los informes o las planillas en general, mostrando las celdas vacías cuando tienen por resultado un cero. Esto puede resolverse de modo parcial o total y para ello veremos 4 escenarios a continuación.

Escenario N° 1: formular correctamente la función SI.ERROR

En la siguiente imagen se observa la función con el último argumento en 0. Esto no será correcto en casos de facturas, presupuestos o algún otro tipo de documento donde, al no encontrarse el producto, el valor a devolver debiera ser vacío en lugar de presentarlo con un precio de 0.


Otro ejemplo sería al mostrar una tabla de stock de productos, donde puede haber productos con stock = 0, pero en caso de no encontrarse algún item debiera mostrarlo vacío.

Por lo tanto, la función SI.ERROR debiera tener su último argumento con comillas (vacío) o con algún texto, pero no con ceros.

Escenario N° 2: quitar ceros solo a una tabla en una hoja donde se pueden encontrar otro tipo de tablas que no deseamos modificar.


En este caso utilizaremos una macro para limpiar solamente el rango de la tabla deseada. Desde el Editor de macros, insertaremos un módulo y allí copiaremos el siguiente código:

Sub quitaCeros()

Dim x As Integer

x = Range("T" & Rows.Count).End(xlUp).Row

Range("T3:AD" & x).Replace What:="0", Replacement:="", LookAt:=xlWhole, SearchOrder:=xlByRows ', _

    MatchCase:=False, SearchFormat:=False, ReplaceFormat:=True

End Sub


Escenario N° 3: quitar ceros a toda la hoja activa. 

Esto se da generalmente en tablas diarias o mensuales, donde muchas celdas mantienen resultados en cero, impidiendo ver con claridad el resto de los valores, tal como se aprecia en la siguiente imagen:


La solución en este caso, es ir al menú Archivo, Opciones Excel, Avanzadas y quitar el tilde a la opción de 'Mostrar un cero......'


Escenario N° 4: como la solución anterior solo modifica el modo de tratar los ceros en la hoja activa, nos obligaría a ir hoja por hoja en caso de hojas mensuales o diarias. 

Entonces, para modificar todas o gran parte de las hojas del libro recurriremos nuevamente a una macro. En el Editor, en un módulo copiaremos el siguiente código, donde podemos agregar instrucciones que omitan los cambios en ciertas hojas:

Sub quitaCerosTotal()

'quita ceros en todo el libro

For Each sh In Sheets

    'omito alguna(s) hoja(s)

    If sh.Name <> "FILTRO" Then

        sh.Select

        ActiveWindow.DisplayZeros = False

    End If

Next sh

End Sub


Acceso al video N° 57 desde aquí.

domingo, 24 de abril de 2022

56 - Obtener parte del contenido de una celda para dar formato.

 Sabemos dar formato a celdas y rangos: negrita, colores, otra fuente, etc.

Pero ¿cómo podemos dar otro formato a solo parte del contenido parcial de una celda?

Utilizando la función INSTR([posición inicial], cadena donde buscar, cadena a buscar, [coincidencia])

Veamos 2 ejemplos:

Ejemplo 1:  Tenemos una factura (nota de débito, albarán o presupuesto, etc). Y deseamos 'hacer notar' de alguna manera las marcas de los productos facturados.

Lo que se necesita es una columna auxiliar con las marcas de los productos que facturamos. Esta columna se crea directamente desde el mismo código, tomando esa información de la tabla de productos.


Luego se recorrerá esa columna auxiliar en todas las filas de la hoja Factura, cambiando la fuente y color de fuente, no a toda la celda sino solamente al texto encontrado.

Sub marcaNegrita()     'hoja Factura

'buscar fin de rango en la hoja de productos.... Ajustar al modelo

Set hox = Sheets("Productos")

x = hox.Range("C" & Rows.Count).End(xlUp).Row

'copiar col C a la col auxiliar E y quitar duplicados ... Ajustar al modelo

With hox

    .Columns("C:C").Copy Destination:=.[E1]

    .Range("$E$1:$E$" & x).RemoveDuplicates Columns:=1, Header:=xlYes

    'se guarda el nuevo fin de rango para la col E

    x = .Range("E" & Rows.Count).End(xlUp).Row

End With

'recorre la col E buscando esos textos en la hoja Factura, col B

'se sabe que la lista de productos facturados va de 10 a 13....Ajustar al modelo

For I = 2 To x

    'se guarda la marca sin posibles espacios

    dato = Trim(hox.Range("E" & I).Text)   

    For y = 10 To 13

        'se la busca en la celda de la factura, a partir de la posición 1 

       ubica = InStr(1, Range("B" & y), dato, 0)    

        'si la función InStr devuelve un valor se toma la cadena a partir de esa ubicación

        If ubica > 0 Then   

            With Range("B" & y).Characters(ubica, Len(dato)).Font     

                'la función Len devuelve la longitud del dato

                .FontStyle = "Negrita"

                .ColorIndex = 3

            End With

        End If

    Next y

Next I

MsgBox "Fin del proceso."

End Sub


Ejemplo 2:  A partir de una plantilla o documento modelo, se busca completar los campos modificables mediante un formato en negrita.


Se necesitará una columna con la lista de campos modificables, una columna con los valores con los que se rellenará la plantilla y una hoja auxiliar para dejar el documento formateado listo para imprimir.


Como el documento se rellena con fórmulas utilizando la función BuscarV, luego desde la macro lo que haremos es copiar el rango del documento, en este caso col A:G a la hoja auxiliar, solo valores y formatos. 
Y en esta hoja resultado, recorriendo la col de datos modificables, se irán buscando esos textos en las cadenas de cada fila, marcándolos en negrita.

Sub enNegrita()   'hoja Contrato

'ultimas filas con datos de la hoja principal, hoja activa

x = Range("K" & Rows.Count).End(xlUp).Row

f = Range("A" & Rows.Count).End(xlUp).Row

'se copia el rango del documento en otra hoja auxiliar, como 'solo valores'

'quitando previamente col utilizadas con anterioridad

Set hox = Sheets("Cto final")     'ajustar el nombre de la hoja auxiliar

hox.Columns("A:G").Delete

Columns("A:G").Copy

    hox.Range("A1").PasteSpecial Paste:=xlValues, Operation:= _

        xlNone, SkipBlanks:=False, Transpose:=False

    hox.Range("A1").PasteSpecial Paste:=xlPasteFormats, Operation:=xlNone, _

        SkipBlanks:=False, Transpose:=False

'recorre la col de textos a formatear, buscandolos en hoja auxiliar

For I = 2 To x

    If Range("K" & I) <> "" Then

        dato = Trim(Range("K" & I).Text)  

        'para mantener los formatos de importes y fechas

        For y = 2 To f

            ubica = InStr(1, hox.Range("A" & y), dato, 0)

            If ubica > 0 Then

                With hox.Range("A" & y).Characters(ubica, Len(dato)).Font

                    .FontStyle = "Negrita"

                    '.ColorIndex = 5

                End With

            End If

        Next y

    End If

Next I

'altos de fila

For I = 2 To f

    alto = Range("A" & I).RowHeight

    hox.Range("A" & I).RowHeight = alto

Next I

hox.Select

[A1].Select

MsgBox "Fin del proceso."

End Sub



Descargar libro de ejemplo desde aquí.

Acceso al VIDEO Nº 56 desde aquí.










domingo, 10 de abril de 2022

55 - Propiedad 'HasFormula' en VBA

 En Excel tenemos la función ESFORMULA para determinar si una celda contiene valores o fórmula, devolviendo VERDADERO en caso de que la tenga.

            =ESFORMULA(E3) 

En VBA, también podemos necesitar esta información para tomar alguna decisión. Y para ello utilizaremos la propiedad 'HasFormula' de la celda. 

En esta entrada veremos 2 casos concretos de cómo evaluar esta situación.

CASO 1: si por alguna razón la hoja no puede ser protegida, pero necesitamos impedir que se modifiquen sus fórmulas. 

Lo que haremos es evaluar la situación al momento de seleccionar una celda y en caso de que contenga fórmula, pasar a la celda siguiente. Esto lo controlamos desde el evento SelectionChange de la hoja.

Private Sub Worksheet_SelectionChange(ByVal Target As Range)

If Target.HasFormula Then Target.Offset(0, 1).Select

       'If Target.Row < 3 Then Target.Offset(1, 0).Select

End Sub


NOTA: La segunda instrucción no tiene que ver con las fórmulas pero nos sirve para impedir que se modifiquen títulos o encabezados de tablas en una hoja sin protección.

CASO 2: cuando necesitamos agregar algún argumento o alguna otra función a un rango de celdas.

Es un caso frecuente cuando olvidamos agregar la función SI.ERROR en cualquier función que presenta nuestra hoja. Y con las siguientes macros que colocaremos en un módulo, lograremos modificar la celda activa (opción 1) o un rango dentro de la hoja (opción 2).


Opción 1: Solo para la celda activa. Recomiendo utilizar un atajo de teclado para ejecutarla.

Sub cambiaFormula_celda()

'Opción 1: solo la celda activa. Para este caso podrías utilizar un atajo de teclado

'atajo de teclado: CTRL f

 With ActiveCell

    If .HasFormula Then

        cadena = Mid(.FormulaR1C1, 2, Len(.FormulaR1C1) - 1)

        'se evalúa si todavía no tiene la función SI.ERROR, en ese caso lo agrega

        If Left(cadena, 7) <> "IFERROR" Then

            .FormulaR1C1 = "=IFERROR(" & cadena & ","""")"

        End If

    End If

End With

End Sub



Opción 2:
 Para un rango seleccionado previamente. También en este caso es posible utilizar un atajo de teclado para ejecutarla. 

Sub cambiaFormula_seleccion()

'atajo de teclado: CTRL h

'Opción 2: recorriendo el rango previamente seleccionado

'tratándose de un rango extenso es conveniente pasar el modo de cálculo

'a manual para que no recalcule en cada celda y evitar así demoras en el proceso

Application.Calculation = xlCalculationManual

'se recorre cada celda de la selección, evaluando si tiene fórmula o no

For Each cd In Selection

    If cd.HasFormula Then

    'se obtiene la fórmula a partir de la posición 2

        cadena = Mid(cd.FormulaR1C1, 2, Len(cd.FormulaR1C1) - 1)

        'si los 7 1ros caracteres no mencionan iferror se arma la nueva fórmula

        If Left(cadena, 7) <> "IFERROR" Then

            'el última argumento es vacío. Puede ser 0 o algún otro valor.

            cd.FormulaR1C1 = "=IFERROR(" & cadena & ","""")"

        End If

    End If

Next cd

'volver el modo de cálculo a automático

Application.Calculation = xlCalculationAutomatic

'opcional: enviar un mensaje de fin

MsgBox "Fin del proceso.", , "Información"

End Sub

NOTA: si el rango es muy extenso como para seleccionarlo previamente, agregar la siguiente instrucción marcada de color (ajustando al rango deseado):

Sub cambiaFormula_seleccion()

'atajo de teclado: CTRL h

'Opción 2: recorriendo el rango previamente seleccionado

'tratándose de un rango extenso es conveniente pasar el modo de cálculo

'a manual para que no recalcule en cada celda y evitar así demoras en el proceso

Application.Calculation = xlCalculationManual

Range("A2:K200").Select

'se recorre cada celda de la selección, evaluando si tiene fórmula o no

For Each cd In Selection

     '................ continúa la macro.

End Sub

           

El libro con los ejemplos puede ser descargado desde aquí.

Acceso al VIDEO Nº 55 desde aquí.











domingo, 3 de abril de 2022

54 - BUSCARV vs INDICE+COINCIDIR.

No siempre la función BUSCARV nos resuelve una búsqueda de cierta información según el criterio empleado. Por ejemplo, si tenemos una tabla de varias columnas y el criterio se encuentra en una columna central como en la imagen.


BUSCARV nos devolverá la información a derecha. Y para obtener la de la izquierda utilizaremos las funciones INDICE + COINCIDIR.

Ejemplos de fórmulas de búsqueda para una tabla como la de la siguiente imagen:

Ubicamos en celda M2 un número de documento:  F-104

Para obtener el nombre   =BUSCARV($M$2;D:F;3;FALSO)
Para obtener el importe  =BUSCARV(M2;D3:H18;5;FALSO)

Para obtener la fecha      =INDICE(C:C;COINCIDIR($M$2;D:D;0))
Para obtener el Id.Reg   =INDICE(B3:B17;COINCIDIR(M2;D3:D17;0))

NOTAS: los signos $ solo serán necesarios si arrastramos la fórmula. La búsqueda puede realizarse en columnas completas (D:F) siempre y cuando no haya otras tablas o datos más allá de este rango.

En el caso de que nuestra hoja tenga Diseño de Tabla, no necesitamos seleccionar columnas o rangos de datos, sino simplemente hacer mención al nombre de la columna.


Para obtener el nombre:   
=BUSCARV(M2;Tabla1[[NRO.DOC.]:[NOMBRE]];3;FALSO)
Para obtener la fecha:     =INDICE(Tabla1[FECHA];COINCIDIR($M$2;Tabla1[NRO.DOC.];0))

NOTA: a pesar de ser Tabla, de todos modos podemos utilizar las fórmulas anteriores. Pero justamente tenemos que aprovechar las ventajas de no tener que seleccionar rangos sino utilizar los títulos de columnas.

Y si de VBA se trata, estas 2 macros nos resuelven el problema de modo sencillo:

Sub busqueda_Gral()

'devolviendo datos a la derecha del código buscado

dato = [M2]

Set busco = Range("D:D").Find(dato, LookAt:=xlWhole)

If Not busco Is Nothing Then   

    [O4] = Range("F" & busco.Row)       'indicando la col a devolver

    [O7] = busco.Offset(0, 4)                    'moviéndonos 4 col a derecha

End If

End Sub

 

Sub busqueda_Tabla()

'devolviendo datos a la izquierda del código buscado, en un diseño de Tabla

dato = [M2]

Set busco = Range("Tabla1[[NRO.DOC.]]").Find(dato, LookAt:=xlWhole)

If Not busco Is Nothing Then

    [P4] = Range("C" & busco.Row)      'indicando la col a devolver

    [P7] = busco.Offset(0, -2)                  'moviéndonos 2 col a la izquierda 

End If

End Sub

 

IMPORTANTE: las fórmulas con las funciones INDICE + COINCIDIR también pueden ser utilizadas para obtener información a la derecha del dato buscado.


Para descargar el libro de ejemplo, entrar aquí.

Para ver video 54 entrar aquí.