domingo, enero 27, 2008

Programación de eventos en Excel – Segunda Nota

En la primera nota sobre programación de eventos en Excel vimos la importancia y el poder de esta herramienta. En esta nota daremos algunos ejemplos, poniendo el énfasis al aspecto práctico. Pero empecemos por señalar algunas características importantes de los eventos.
Existe una jerarquía de eventos:


  • Eventos de la aplicación (Application events)


  • Eventos del cuaderno (Workbook events)


  • Eventos de la hoja (Worksheets events)


  • En el tope de la jerarquía están los eventos de la aplicación, luego los del cuaderno y finalmente los de la hoja. Cada objeto contiene sus propios eventos y los de de los objetos que se encuentran por debajo de él en la jerarquía. Por ejemplo el cuaderno tiene un evento Worksheet_Change que responde a cada cambio en una celda de la hoja. El cuaderno, a su vez, tiene un evento Workbook_SheetChange que responde a un cambio en una celda de cualquiera de las hojas del cuaderno. Ante un cambio en una celda, los eventos de los tres niveles entran en acción.

    Ciertas acciones disparan más un evento, pero en cierto orden preestablecido. Por ejemplo, agregar una nueva hoja a un cuaderno (la acción) dispara una serie de eventos exactamente en este orden:

  • Workbook_SheetDeactivate

  • Workbook_SheetActivate

  • Workbook_NewSheet


  • Un tercer punto, y el más importante del punto de vista práctico, es cómo evitar generar "loops" infinitos al programar eventos. Por ejemplo, consideremos este código

    Private Sub Worksheet_Change(ByVal Target As Range)
    Target.Value = Target.Value + 1
    End Sub

    Ante un cambio en la hoja, el valor de la celda activa (Target) aumenta en 1. Esta acción en sí misma constituye un cambio, lo que produce un evento Change que vuelva a agregar 1 al valor de la celda. Y así sucesivamente hasta el infinito (o hasta que pulsemos Ctrl+Break).
    Si queremos ejecutar este código sin entrar en un "loop" infinito, debemos usar la propiedad EnableEvents de la Aplicación. En nuestro ejemplo:

    Private Sub Worksheet_Change(ByVal Target As Range)
    Application.EnableEvents = False
    Target.Value = Target.Value + 1
    Application.EnableEvents = True
    End Sub

    Primero llevamos el valor de la propiedad a False, ejecutamos la (o las) línea y volvemos a poner el valor de EnableEvents a True. Esto es importante, ya que el valor de la propiedad no vuelve al valor de defecto (True) una vez concluido el código.

    Ya hemos visto algunos ejemplos de eventos en la nota anterior y también en la nota sobre el numerador automático de facturas.

    Ejemplos de eventos de cuaderno (Workbook events):

    Seleccionar la celda A1 al activar una hoja

    Private Sub Workbook_SheetActivate(ByVal Sh As Object)
       [A1].Select
    End Sub

    Dado que usamos un evento a nivel de cuaderno, al seleccionar cualquier hoja, la celda A1 será la celda activa (excepto que seleccionemos una hoja de gráfico, en cuyo caso ocurrirá un error).

    Al agregar una nueva hoja al cuaderno, le fecha y hora aparecen en la celda A1

    Private Sub Workbook_NewSheet(ByVal Sh As Object)

    If TypeName(Sh) = "Worksheet" Then
       [A1] = "La hoja fue agregada el " & Now
    End If

    End Sub

    Ejemplos de eventos de hoja

    En la nota anterior mostramos un ejemplo de Worksheet_Change. Otro ejemplo interesante de este evento es el siguiente que pone un fondo de color a la fila y la columna de la celda activa

    Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    Cells.Interior.ColorIndex = xlNone
     With Target
       .EntireRow.Interior.ColorIndex = 35
       .EntireColumn.Interior.ColorIndex = 35
     End With
    End Sub

    Otros ejemplos pueden encontrarse en notas anteriores en este blog, como Limitar el área de trabajo en Excel o en cómo crear un menú en Excel.



    Technorati Tags:

    sábado, enero 26, 2008

    Programación de Eventos en Excel - Primera Nota

    En mi nota sobre cómo crear un numerador automático para factura en Excel mostraba el uso de eventos en esta tarea. Ya en notas anteriores había mostrado o mencionado el uso de esta herramienta y también prometido que escribiría una nota sobre el tema. Aquí está la nota.

    Debemos distinguir entre eventos y programación de eventos. Evento es, como su nombre lo indica, algo que ocurre en Excel. Programar un evento significa que hemos escrito una rutina que será ejecutada cuando ocurra el evento.

    Excel monitorea constantemente lo que ocurre en la aplicación. Cuando existe un código asociado a un evento, este es disparado al darse las condiciones del evento.

    Por ejemplo, en la nota mencionada más arriba, hacíamos uso del evento BeforePrint. A este evento le habíamos asociado una macro que colocaba un número consecutivo cuando el usuario decidía imprimir la factura.
    En ese ejemplo el evento es iniciado por el usuario. Eventos pueden ser iniciados también por macros.

    Existen eventos para casi todos los objetos de Excel. Podemos clasificar los eventos de la siguiente manera:

    • Eventos del cuaderno (Workbook events): responden a acciones en un cuaderno en particular. Por ejemplo Workbook_Open, que ocurre cuando abrimos un cuaderno.


    • Eventos de las hojas (Worksheet events): responden a acciones en una hoja en particular. Por ejemplo Worksheet_Calculate, que ocurre cuando la hoja es recalculada.


    • Eventos de gráficos (Charts events): responden a acciones en un gráfico en particular.


    • Eventos de la aplicación (Application events): responden a acciones a nivel de la aplicación (es decir, Excel). Un ejemplo es WorkbookBeforeClose, que ocurre cuando iniciamos el proceso de cerrar un cuaderno.


    • Eventos de formularios/controles (Userform events): por ejemplo, el botón de la barra de formulario tiene un evento Button_Click.



    Existen distintas formas de investigar qué eventos existen para cada objeto. Por ejemplo, al poner un botón de la barra de formularios en una hoja de Excel se abre el diálogo Asignar macro




    Si apretamos Aceptar, Excel abrirá el editor de Vb con una rutina Botón2_AlHacerClic() sin líneas.



    Si escribimos esta macro



    cada vez que apretemos el botón ("al hacer clic"), el valor de la celda C1 se incrementa en 1.

    En este caso la macro está ubicada en un módulo corriente. Más adelante veremos que no siempre es así.

    Hay varias formas de investigar qué eventos existen para cada objeto. Por ejemplo, podemos usar el Examinador de Objetos (en el editor de Vb, apretar F2). En la ventana Clases elegimos el objeto que queremos investigar (Worksheet en nuestro caso) y en la ventana Miembros de Worksheet nos deslizamos hasta que vemos nombres con un rayo amarillo a la izquierda



    Esto nos permite ver los eventos disponibles para la hoja.

    Otra forma es seleccionar el objeto en la ventana Explorador de proyectos con un doble clic. Si hacemos esto con la Hoja1, se abre un módulo especial asociado con esta hoja. Abrimos la lista de la ventanilla donde aparece General



    y elegimos Worksheet. Excel pone automáticamente una Sub Worksheet_SelectionChange(ByVal Target As Range) sin líneas. Ahora podemos abrir la lista de la ventanilla de los eventos y elegir alguno de ellos



    Señalemos que los nombres y los parámetros de los eventos están predeterminados y no deben ser cambiados.

    Veamos ahora algunas técnicas útiles. Queremos que cada vez que el usuario abre un determinado cuaderno aparezca un mensaje que le recuerde realizar cierta tarea. Obviamente, este evento pertenece al libro (Workbook). En el editor de Vb, apretamos el icono ThisWorkbook y elegimos el evento Open. Escribimos una rutina como ésta



    Guardamos el cuaderno. Al abrirlo, veremos el mensaje



    Varios de los eventos de la hoja tienen una variable llamada Target. De acuerdo al evento, esta variable está definida como Rango, como en el evento Change


    Sub Worksheet_Change(ByVal Target As Range)

    o como enlace (Hyperlink)

    Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)

    e inclusive como tabla dinámica

    Sub Worksheet_PivotTableUpdate(ByVal Target As PivotTable).

    En el caso de rangos, Target se refiere a cualquier celda de la hoja. Por eso, cuando trabajamos con alguno de estos eventos, surge la necesidad de definir cuál es el rango relevante.
    Supongamos que en una hoja tenemos un rango llamado Ventas, B2:B13. Queremos que cada vez que el usuario ingrese las ventas de un mes, aparezca un mensaje que le muestre el total acumulado. Esto significa que el evento debe reaccionar cuando cambian los datos en el rango.
    En este caso usamos el evento Change, al que le asociamos este código

    Private Sub Worksheet_Change(ByVal Target As Range)
      Dim rngVentas As Range, vntTotal As Double

      Set rngVentas = Range("Ventas")
      vntTotal = WorksheetFunction.Sum("Ventas")

        If Union(Target, rngVentas).Address = rngVentas.Address Then
          MsgBox "Tottal acumulado de ventas " & vntTotal
        End If

    End Sub

    Cada vez que ingresamos un dato en el rango B2:B13, recibiremos un mensaje como este



    La clave en esta rutina es el uso de la función Union. Esta función evalúa si la dirección de Target (la celda activa donde introducimos el dato) coincide con alguna de las direcciones del rango Ventas. En caso afirmativo, se dispara el evento que calcula el total y muestra el mensaje.
    En la línea Set rngVentas = Range("B2:B13") es más conveniente usar la sintaxis Set rngVentas = Range("ventas"), usando el nombre definido "Ventas".

    En la próxima nota tocaremos otros aspectos de los eventos, como jerarquía de eventos, cómo evitar "loops" y agregaremos algunos ejemplos más.


    Technorati Tags:

    sábado, enero 19, 2008

    Distribuir datos en hojas Excel con macros.

    En la nota anterior sobre cómo distribuir datos de una hoja a otras hojas de un cuaderno Excel, vimos como hacerlo con fórmulas.

    En esa nota señalaba que el método con fórmulas es útil sólo si nuestro cuaderno contiene pocos datos. Uno de mis lectores intentó usar la solución con fórmulas pero, en sus porpias palabras "no he podido concluir mi base de datos porque la cantidad de data es mucha y con el peso de las formulas todas las maquinas se cuelgan".

    Este mismo lector me pedía que le enseñara la forma de hacerlo con macros. En esta nota mostraré una solución posible con macros al mismo caso que mostrábamos en la nota anterior.
    Debo aclarar que esta solución no es óptima desde el punto de vista de programación, sino que está orientada al usuario promedio cuyo objetivo es crear herramientas para el trabajo diario y no desarrollo de implementaciones en Excel.

    El ejemplo se basa en el mismo archivo de la nota anterior. El archivo con las macros puede descargarse ">distribuir_a_hojas_macroaquí.

    Empezamos por agregar el encabezamiento "Transferido".





    Esta columna nos servirá para chequear si la línea de la hoja Datos ha sido transferida o no.

    En esta solución usaremos dos macros. Una de tipo evento y otra que hará el trabajo de copiar los datos a la hoja correspondiente.
    La idea es que, una vez agregados los datos en la hoja "Datos", haciendo doble-clic en la celda correspondiente de la columna E, los datos sean transferidos a la hoja adecuada. Además queremos evitar que por error los datos sean transferidos más de una vez. Para eso usaremos el mecanismo de poner, automáticamente, la palabra "SI" en la columna Transferido, después de haber copiado los datos. Una vez que aparece la palabra SI en la columna E de la línea, la macro no permitirá volver a copiarlos.

    Para disparar la macro que copie los datos, usaremos el evento BeforeDoubleClick de la hoja "Datos". Empezamos por abrir el editor de Vba y haciendo doble-clic en el icono de la hoja Datos, abrimos el módulo de Vb para el evento



    En el módulo del evento ponemos este código


    Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)

    Dim rngAnotar As Range, Resp, Fila

    Set rngAnotar = [E:E]

    If IsEmpty(Target) = False Then
    MsgBox "La línea ya fue transferida"
    Exit Sub
    End If

    If Union(Target, rngAnotar).Address = rngAnotar.Address Then
    Resp = MsgBox(prompt:="Transferir datos?", Buttons:=vbYesNo, _
    Title:="Transferir Datos")
    If Resp = vbNo Then
    Cancel = True
    Exit Sub
    End If
    Fila = Target.Row
    Call dist_hojas
    Target.Value = "SI"
    End If
    End Sub


    Después de declara las variables y definir el rango E, empezamos por comprobar si la línea a sido transferida en el pasado. Si la palabra SI aparece en la celda correspondiente de la línea que queremos copiar, aparece un mensaje informándonos que ya hemos transferido los datos y la macro se interrumpe.
    El resto del código produce un mensaje preguntándonos si queremos copiar los datos. Sólo si apretamos NO, la macro se interrumpe y no habrá ningún cambio en el cuaderno.



    Si aceptamos, los datos serán copiados y en la celda de la columna E aparecerá la palabra SI



    Si intentamos volver a copiar los datos de la línea, aparece este mensaje



    El trabajo de copiar a la hoja correspondiente lo hace esta macro, que ponemos en un módulo corriente (Módulo1)


    Sub dist_hojas()
    Dim LastRow As Long, Hoja As String
    Dim rngCopiar As Range

    Hoja = "Ruta " & ActiveCell.Offset(0, -1).Value 'definir nombre de hoja

    Set rngCopiar = Range(Cells(ActiveCell.Row, 1), Cells(ActiveCell.Row, 3))

    Application.ScreenUpdating = False

    With Sheets(Hoja)
    .Activate
    LastRow = WorksheetFunction.CountA([A:A]) + 1
    End With

    rngCopiar.Copy Sheets(Hoja).Cells(LastRow, 1)
    Sheets("Datos").Select

    Application.ScreenUpdating = True

    End Sub


    Después de definir las variable, la primer línea del código "arma" el nombre de la hoja uniendo a la palabra "Ruta" el número que aparece en la columna D, con el operador "&".
    La línea siguiente determina cuál es el rango a copiar en "Datos".
    El paso siguiente es determinar cuál es la primer fila libre en la hoja en la que vamos a pegar los datos.
    Una vez hecho esto, volvemos a "Datos", copiamos el rango adecuado y lo pegamos en la hoja correspondiente.

    En este modelo no hemos puesto ningún método automático para borrar lpineas que han sido copiadas y cancelar la señal SI en la hoja Datos. Esto se deberá hacer manualmente.




    Technorati Tags: