jueves, 26 de diciembre de 2013

VBA: Emitiendo sonidos desde VBA en Excel.

Un par de veces en las últimas semanas me han preguntado por la posibilidad de emitir sonidos controlados desde nuestros proyectos de VBA. POr lo que en la entrada del día, explicaré una manera rápida de conseguirlo.

Por supuesto nos podríamos conformar con la instrucción .Beep que emite un sonido sin más al llamarlo en nuestras macros. Por ejemplo:
Sub sonido()
Beep
End Sub


Pero lo que hoy veremos será algo más completo; ya que podremos hacer sonar cualquier sonido que se nos antoje, sin necesidad de ningún reproductor de media.... Lo primero que deberíamos tener es el archivo de sonido (.wav)
Para nuestra macro emplearemos la función API PlaySound que se encuentra en la librería de Windows winmm.dll. Asi que emplearemos la instrucción Declare que se usa en el nivel de módulo para declarar referencias a procedimientos externos que están en una biblioteca de vínculos dinámicos(DLL).

Por tanto en la parte de arriba de nuestro módulo dentro de un proyecto de VBA, antes de cualquier otra sentencia, insertaremos el siguiente código:

Private Declare Function PlaySound Lib "winmm.dll" Alias "PlaySoundA" _
(ByVal lpszName As String, ByVal hModule As Long, ByVal dwFlags As Long) As Long



Los parámetros asociadas a la variable dwFlags son:
Const SND_SYNC = &H0 ' (por defecto) Reproduce el sonido 'sincrónicamente'. Se interrumpe la ejecución de nuestro código hasta la finalización del sonido!!.
Const SND_ASYNC = &H1 ' Reproduce el sonido 'sincrónicamente'. El código continúa sin necesidad de que el sonido finalice.
Const SND_NODEFAULT = &H2 ' En caso no se encuntre el sonido determinado no reproduciría el sonido por defecto, es decir, no sonaría nada...
Const SND_LOOP = &H8 ' Se reproduce el sonido ininterrumpidamente hasta que se ejecute de nuevo la función.
Const SND_NOSTOP = &H10 ' El sonido no se para antes de reproducir el sonido especificado.

Otras constantes:
Const SND_APPLICATION = &H80
Const SND_ALIAS = &H10000
Const SND_ALIAS_ID = &H110000
Const SND_FILENAME As Long = &H20000
Const SND_MEMORY = &H4
Const SND_NOWAIT = &H2000
Const SND_PURGE = &H40
Const SND_RESOURCE = &H40004

Es posible ver algo más de documentación en la página de Microsoft (ver)


Podemos crear entonces la siguiente macro, dentro de un módulo, que reproduzca el sonido elegido. En el ejemplo he optado por un sonido estándar de Windows (el famoso tada!!), que podemos encontrar en la ruta C:\Windows\Media\

Public Sub SonidoControlado()
    'definimos dos constantes para localizar la ruta y el nombre del archivo
    Const RutaSonido As String = "C:\Windows\Media\"    'la ruta del fichero
    Const ArchivoSonido As String = "tada.wav"          ' ojo, porque el fichero de sonido debe tener extensión wave
    Dim Sonido As String
    'construimos la ruta completa del archivo de sonido
    Sonido = RutaSonido & ArchivoSonido
    
    'llamamos a la función sndPlaySound32
    Call PlaySound(Sonido, &H1, &H1 Or &H20000)
End Sub



Por supuesto, igualmente podríamos haber asociado nuestra macro a un evento del libro o de la hoja, por ejemplo, cada vez que selecionemos una celda (evento _Worksheet_SelectionChange):

Private Declare Function PlaySound Lib "winmm.dll" _
    Alias "PlaySoundA" (ByVal lpszName As String, ByVal hModule As Long, ByVal dwFlags As Long) As Long
''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
 'definimos dos constantes para localizar la ruta y el nombre del archivo
    Const RutaSonido As String = "C:\Windows\Media\"    'la ruta del fichero
    Const ArchivoSonido As String = "tada.wav"          ' ojo, porque el fichero de sonido debe tener extensión wave
    Dim Sonido As String
    'construimos la ruta completa del archivo de sonido
    Sonido = RutaSonido & ArchivoSonido
    
    'llamamos a la función sndPlaySound32
    Call PlaySound(Sonido, &H1, &H1 Or &H20000)
End Sub

lunes, 23 de diciembre de 2013

VBA: personalizando el pié de página o Footer mediante programación en Excel.

Probablemente te hayas preguntado alguna vez si era posible incorporar el valor de una celda como parte del Pié de página (o del Encabezado)... Si lo has intentado alguna vez te habrás percatado que tal cosa no es posible desde la Configuración de página.
Explicaré hoy un sencillo procedimiento Sub (macro de Excel) que incorpora en el Footer (pié de página) el valor de una celda.

Para ello emplearemos el objeto .PageSetup y su propiedad .CenterFooter (podríamos haber optado por .LeftFooter o .RightFooter.. es decir, por cualquiera de las tres secciones del pié de página.. o sus variantes para el Encabezado: .CenterHeader, LeftHeader o RightHeader).

Igualmente controlaremos la comunicación con la impresora con la propiedad .PrintCommunication.


Añadiremos nuestro código asociándolo a un evento _BeforePrint del libro (ThisWorkbook); para ello accederemos a la ventana de código del explorador y buscaremos ThisWorkbook, donde insertaremos el siguiente código VBA:

Private Sub Workbook_BeforePrint(Cancel As Boolean)
Dim strFecha As String
'ejecutamos la macro Limpiar que elimina anteriores diseños del pié de página
Call Limpiar

'construimos el texto que aparecerá en el pié de página
strFecha = "Oferta válida hasta el: " & Format(Range("C1").Value + 30, "dd/mm/yyyy")

'controlamos la comunicación con la impresora
Application.PrintCommunication = False

'y par la hoja activa configuramos la sección central del pié de página
ActiveSheet.PageSetup.CenterFooter = strFecha
Application.PrintCommunication = True

End Sub



En un módulo a parte creamos la macro Limpiar:

Sub Limpiar()
Application.PrintCommunication = False
ActiveSheet.PageSetup.CenterFooter = " "
Application.PrintCommunication = True
End Sub



El resultado es el esperado, al ordenar la impresión de la hoja, actuará el evento _BeforePrint y ejecutará nuestro procedimiento, incorporando al pié de página el texto elegido. En el ejemplo, sumará 30 días a la fecha que aparezca en C1, dándole un formato de fecha.
Ojo, ya que los cambio se producirán justo antes de la impresión!!! (quizá no se visualicen en la vista preliminar).

VBA: personalizando el pié de página o Footer mediante programación en Excel.

jueves, 19 de diciembre de 2013

Reparto equitativo con Excel.

En respuesta a un lector, plantearé una forma cómoda de realizar un reparto entre un número determinado previamente:

...en mi trabajo llegan diariamente cierto número de sobres que se reparten entre cierto número de trabajadores.
Por ejemplo 100 sobres para 10 ejecutivos, a veces más, a veces menos. La idea es que de la lista en Excel de lo que llegó, que está en la Columna A, excel pueda repartir de forma automática mediante una fórmula en la columna B a quien le toca proporcionalmente, por ejemplo si hay 5 ejecutivos y 20 sobres que Excel pueda asignarlo así:
Número de operaciones por ejecutivo = 4

Columna A Columna B (fórmula)
506010 Ejecutivo A Aquí Excel debería de saber que 4 es lo que corresponde al Ejecutivo A
506011 Ejecutivo A
506012 Ejecutivo A
506013 Ejecutivo A
506014 Ejecutivo B Aquí Excel debe saber que debe dar 4 a ejecutivo B.
506015 Ejecutivo B
506016 Ejecutivo B
506017 Ejecutivo B
506018 Ejecutivo C
... ...
506029 Ejecutivo D

Lo que he logrado es que Excel calcule cuantos sobres le tocan a cada ejecutivo, pero no he logrado que haga que la columna B de el resultado adecuado. Los nombres de los ejecutivos salen de otra lista donde apunto la gente disponible.



Tenemos el siguiente modelo, con algunos cálculos sencillos ya realizados:

Reparto equitativo con Excel.


En E3 tenemos un conteo directo sobre el número de sobres a repartir:
=CONTARA(A3:A26)
En E4 el númeo de personas entre los que se debe realizar la asignación/reparto:
=CONTARA(E9:E20)
Ambos cálculos se han realziado con la función CONTARA, que devuelve el número de celdas no vacías, esto es, con algún dato.
En E5 con la fórmula
=ENTERO(MULTIPLO.SUPERIOR(E3;E4)/E4)
conseguimos un reparto al alza entre el número de personas, es decir, nos aseguramos que nunca faltará una persona para asignarle un sobre...


Una vez visto el planteamiento, añadimos la sencilla fórmula que realizará el reparto, tomando los cálculos anteriores. En B3:B26 añadiremos la fórmula:
=ENTERO((FILA()-3)/$E$5)+1
con lo que conseguimos intervalos de cinco en cinco, como vemos en la imagen:

Reparto equitativo con Excel.



Para trabajar con nombres en lugar de números, aprovechándonos de la ordenación de las personas, podríamos aplicar la siguiente función:
=INDICE($E$9:$E$20;ENTERO((FILA()-3)/$E$5)+1)
para todo nuestro rango...

martes, 17 de diciembre de 2013

Calendario laboral en Excel.

Por estas fechas siempre tenemos una necesidad (humana diría yo) de conocer el futuro... desde luego no será Excel quien nos de respuesta a tan antigua pregunta...
Pero lo que si podemos conseguir es conocer y disponer en Excel de un calendario laboral vistoso.. y si además conocemos de antemano las Festividades del año, incluso quedarán marcadas en rojo.


Aquí en España las festividades oficiales Nacionales y de las Comunidades autónomas aparecen más o menos por el mes de Noviembre (ver BOE núm 278 20-Nov-2013 Sec. I. Pág. 92699-92701). Pero servirá para cualquier parte del mundo.. bastará que conozcas los festivos oficiales de tu zona o país...
Puestos en antecedentes, pasemos a lo que nos interesa, cómo construir en Excel ese calendario laboral.


Diré que todo funcionará empleando fórmulas matriciales incorporadas en Nombres definidos, lo que facilita la comprensión; además utilizaremos los Formatos condicionales para marcar nuestras festividades. Y dejando atrás macros u otros procesos ya explicados en alguna entrada anterior (ver)

La primera parte del trabajo consiste en preformatear las celdas donde irán nuestros meses y días. Ver imagen siguiente:

Calendario laboral en Excel.



Comenzaremos mostrando los Nombres definidos que nos servirán para construir nuestro Calendario:
calendario =díasysemanas + PrimerDia - DIASEM(PrimerDia;2)
díasysemanas =COLUMNA(DESREF(INDIRECTO("$A$1");0;0;1;42))
Festivos =TblFestivos[Festivos del año]
PrimerDia =FECHA(CalendarioPerpetuo!$A$3;MES(CalendarioPerpetuo!$B7);1)

Calendario laboral en Excel.



Como vemos tenemos una Tabla llamada 'TblFestivos' que utilizaremos para los formatos condicionales.
Tenemos un nombre 'PrimerDia' que nos devuelve el primer día de cada mes, según la fila del rango B7:B18.
El nombre definido 'díasysemana' devuelve un rango virtual con valores de 1 al 42. El 42 no es un número al azar, es el resultado de 6 x 7, es decir seís semanas por siete días por semana. Con esto controlamos el máximo ancho posible, puesto que no hay posibilidad alguna de que un mes tenga más de seis semanas...
Por último con el nombre 'calendario', combinando ambos nombres definidos anteriores, tendremos la secuencia de los días del mes. Si contemplamos sólo la suma de
=díasysemanas + PrimerDia
para corregir y afianzar el comienzo correcto en el día de la semana adecuado, incorporamos la función
DIASEM(PrimerDia;2)
que nos dice el día de la semana en que comienza el primer día de cada mes...


Estamos en disposición de ejecutar nuestra matricial 'calendario' sobre nuestra plantilla, para ello seleccionaremos fila a fila los rangos correspondientes a cada mes, es decir, comenzaremos seleccionando, para enero, el rango D7:AS7, en D7 escribimos 'calendario' y ejecutamos matricialmente (presionando Ctrl+Mayusc+Enter).
Repetiremos la operación doce veces, una por mes.. para febrero, seleccionamos D8:AS8 y en D8 escribimos 'calendario' y ejecutamos matricialmente, etc. etc.


El resultado final sería (ya aplicado el formato condicional):



Para marcar los festivos (los que aparezcan en la tabla de Festividades) y los domingos del año en rojo; así como para que sólo aparezcan los días de cada mes en la fila correspondiente, aplicaríamos los siguientes formatos condicionales:

jueves, 12 de diciembre de 2013

Contando secuencias en un rango de Excel.

Hace un par de días un usuario del blog me contactó preguntándome por la manera de construir una secuencia 'especial' sobre un rango de celdas:

Lo que yo requiero es contar la secuencia de las columnas pero de una forma simplificada
La columna A:
3A, 1I, 2A, 1G, 1E, 1B, 1A, 1E, 4A.
… y asi por cada columna, la idea es como un resumen, pero no solo contando sino conservando el orden


Partiremos de la siguiente serie de datos (nos centraremos en la columna A):

Contando secuencias en un rango de Excel.


El asunto está claro, necesitamos construir, de alguna manera, el conteo de elementos continuos en el rango a evaluar. Es decir,a cada salto por diferencias entre el elemento anterior y siguiente, mostraremos el número de repeticiones habidas. Y lo haremos sin emplear macros, mediante fórmulas.

Para hacerlo sencillo de explicar construiremos dos columnas auxiliares que nos apoyarán en la obtención de lo buscado.
En nuestra Aux1 dispondremos, para nuestro ejemplo, la siguiente fórmula en la celda F2:
=SI(FILAS($A$1:A1)=1;1;SI(A2=A1;F1+1;1))
con ella conseguimos que para el primer valor del rango aparezca un 1:
SI(FILAS($A$1:A1)=1;1;...
y para el resto de filas aplica lo importante
...;SI(A2=A1;F1+1;1)
es decir, cuando haya un cambio de elemento, asignamos de nuevo el valor 1, y en caso que sea el mismo elemento acumulamos +1 al valor anterior.
Vemos el resultado en la imagen:

Contando secuencias en un rango de Excel.



En este primer paso hemos conseguido tener numerada las series continuas que aparezcan en nuestro rango (columna A), preparando el terreno para nuestra segunda Axuliar, donde en G2 insertaremos la fórmula:
=SI(A2=A3;"";A2)
y arrastrando hacia abajo:

martes, 10 de diciembre de 2013

Detener la creación automática de hipervínculos en Excel.

Quizá no sea tu caso, pero para mí resulta un pequeño inconveniente cada vez que escribo una dirección de correo electrónico o una dirección web (URL) en mi hoja de trabajo se convierta en enlace/hipervínculo a ese email o web.
Hoy veremos cuál es la forma de deshabilitar esta funcionalidad.


Como siempre para estos casos accederemos a las Opciones de Excel > menú Revisión > sección Opciones de Autocorrección > botón Opciones de Autocorrección...:

Detener la creación automática de hipervínculos en Excel.


Tras presionar el botón, en la ventana diálogo abierta, buscaremos la pestaña 'Autoformato mientras escribe' y desmarcaremos la opción Rutas de Red e Internet por hipervínculos:

Detener la creación automática de hipervínculos en Excel.



Con esta acción, es decir, tras desmarcar la opción comentada, ya podemos probar a escribir en nuestras celdas cualquier tipo de enlace a un correo electrónico o una URL, que el resultado será el esperado... NO se convertirá en hipervínculo.

Esta primera manera de configurar esta opción es proactiva, esto es, antes de escribir sobre nuestras celdas. Otra manera es configurarlo una vez escribamos sobre una celda.
Tras escribir un email o web en la hoja de cálculo se convertirá automáticamente en hipervínculo, pero si dejamos el cursor del ratón sobre la celda, en la parte inferior izquierda de la celda aparecerá una pequeña marca que despliega algunas opciones:

Detener la creación automática de hipervínculos en Excel.


Si lo presionamos se desplegarán tres posibilidades:
1- Deshacer hipervínculo
2- Detener la creación automática de hipervínculos
3- Controlar Opciones de autocorrección...

El primero de ellos simplemente 'desvincula' la celda en cuestión, pero mantiene la funcionalidad activa. La segunda es la que nos interesa, ya que es esta opción Detener la creación automática de hipervínculos la que deshabilita la funcionalidad.

Detener la creación automática de hipervínculos en Excel.

jueves, 5 de diciembre de 2013

Ordenar rango de celdas de texto con una matricial.

Hace ya bastante tiempo conté la manera de ordenar valores numéricos en un rango de celdas (ver). Hoy vamos a aplicar una técnica similar para ordenar un conjunto de celdas con texto.
En particular aprenderemos la particularidad de la función CONTAR.SI respecto a esta ordenación.

Nuestro objetivo será ordenar en sentido ascendente, esto es de A a Z, este conjunto de celdas:

Ordenar rango de celdas de texto con una matricial.



Para esto es fundamental comprobar los valores resultantes de aplicar CONTAR.SI sobre un rango de celdas con texto, veamos la imagen siguiente donde he colocado las cinco vocales...

Ordenar rango de celdas de texto con una matricial.


Observamos que la función:
=CONTAR.SI($A$2:$A$6;">="&A2)
con el operador mayor o igual que nos devuelve el número de elementos ordenados posteriores a la celda condicionada...
De igual forma
=CONTAR.SI($A$2:$A$6;"<="&A2)
nos dice el número de elementos en el rango a evaluar anteriores a la celda condicionada...

Dicho de otro modo el operador mayor o igual a me da una ordenación alfabética en sentido descendente (de Z a A), y el operador menor o igual a en sentido ascendente (de A a Z).
Así que ya tenemos la primera parte de nuestra fórmula matricial.


Si volvemos sobre nuestro listado original a ordenar en sentido ascendente, y para trabajar de una manera más sencilla, le asignaremos un nombre definido 'listado':
listado =Hoja1!$A$2:$A$20


A continuación seleccionaremos el rango contiguo en B2:B20 y en la celda activa B2 escribiremos nuestra matricial:
=INDICE(listado;COINCIDIR(K.ESIMO.MENOR(CONTAR.SI(listado;"<"&listado);FILA()-1);CONTAR.SI(listado;"<"&listado);0))
tras lo cual presionaremos Ctrl+Mayusc+Enter.

El resultado será nuestro 'listado' ordenado de A a Z:

martes, 3 de diciembre de 2013

Contando celdas: la combinación FILAS x COLUMNAS.

Hoy explicaré una forma de contar celdas sin macros, aplicado a un problema concreto planteado por un lector:

Requiero hacer una macro que busque un nombre (Hoja 1)en la columna A por ejemplo (Carlos) y que al mismo tiempo en la columna B tome el valor que tiene al lado, es decir:

Columna A Columna B
Carlos 8
Roberto 10
Carlos 4

y que sume el valor del numero las veces que se repita el nombre de Carlos.

Lo interesante es que la suma del valor se distribuya en unos como una matriz en una Hoja 2 en un rango de 5 filas a lo largo de todas las columnas es decir si tengo una suma de 12

Columna A B C D E F
1 1 1 1
2 1 1 1
3 1 1
4 1 1
5 1 1



Veremos la manera de emplear las funciones estándar de hoja de cálculo a nuestro alcance para conseguir nuestro objetivo (distribuir una cantidad por los elementos de una matriz) sin necesidad de implementar una macro.
Para ello veamos de qué origen de datos partimos:

Contando celdas: la combinación FILAS x COLUMNAS.


Hemos insertado en la celda D3 la función:
=SUMAR.SI(A1:A11;D2;B1:B11)
con el fin de tener el primer resultado solicitado, la suma de los valores de la columna B que coinciden con el valor 'carlos'.


El siguiente paso para conseguir nuestra matriz de cinco filas con 'unos' distribuidos será trabajar una 'matriz auxiliar'. Asi que no situaremos en la celda H2 y escribiremos:
=FILAS(H$2:H2)*COLUMNAS(H$2:H2)+MAX(G$2:G$6)
también podríamos haber optado por esta algo más sencilla:
=FILAS(H$2:H2)+MAX(G$2:G$6)
luego copiaremos cinco filas hacia abajo y tantas columnas a la derecha como necesitemos... esto resultará una numeración de arriba hacia abajo y de izquierda hacia derecha consecutiva:

Contando celdas: la combinación FILAS x COLUMNAS.


Fijémonos en que no haya datos en la columna G.
El funcionamiento de esa operación =FILAS(H$2:H2)*COLUMNAS(H$2:H2) devuelve una numeración de filas por cada columna (1,2,3,4 y 5 para nuestro ejemplo); y a continuación sumamos el valor máximo de la columna anterior con +MAX(G$2:G$6); asegurándonos entonces ese secuencia correlativa con saltos de 5 en 5 por cada columna.


El siguiente y último paso consite en emplear nuestros valores calculados en la matriz auxilar y aplicarlos con el fin de obtener la distribución pedida.
Como ya conocemos cuál es el valor a distribuir, en la celda D3, bastará aplicar un condicional del tipo y escribir en H9:
=SI(H2<=$D$3;1;"")
arrastrando al resto de elementos de la matriz final:

Contando celdas: la combinación FILAS x COLUMNAS.



Así en dos pasos tenemos lo que se solicitaba, y sin macros.

En el ejemplo he expuesto dos maneras de conseguir el mismo resultado, la primera consistía en multiplicar la función FILAS por la función COLUMNAS (aunque realmente para el ejemplo no era necesario); pero me parece interesante recordar cómo podemos contar celdas de un rango o matriz con funciones de Excel; y es que sería tan sencillo como calcular el producto del número total de filas por el número de columnas de dicha matriz:
=FILAS(H2:M6)*COLUMNAS(H2:M6)
por ejemplo, la matriz H2:M6 tiene 30 celdas (5 filas x 6 columnas).