Ventanas parpadeantes con FlashWindowEx (y II)

Una vez descritas en la primera parte de este artículo las características de la función FlashWindowEx, en esta segunda entrega pasaremos a la parte práctica, con varios ejemplos de uso.


Comencemos a parpadear


El primer ejemplo consistirá en realizar el efecto de parpadeo únicamente sobre el título y borde de la ventana un número determinado de veces, con un cierto intervalo de tiempo entre cada una; para ello utilizaremos el botón btnBasico de la ventana, en cuyo evento Click crearemos una instancia de la estructura FLASHWINFO, a la que pasaremos los datos necesarios para que la función FlashWindowEx se ejecute correctamente, como vemos en el siguiente bloque de código.


using System.Windows.Interop;
//….
private void btnBasico_Click(object sender, RoutedEventArgs e)
{
// crear la estructura con los datos para pasar a la función
FLASHWINFO oFlashwInfo = new FLASHWINFO();
oFlashwInfo.cbSize = Marshal.SizeOf(oFlashwInfo);
oFlashwInfo.hwnd = new WindowInteropHelper(Application.Current.MainWindow).Handle;
oFlashwInfo.dwFlags = (int)FlashStatus.FLASHW_CAPTION;
oFlashwInfo.uCount = 7;
oFlashwInfo.dwTimeout = 500;

// ejecutar la función
FlashWindowEx(ref oFlashwInfo);
}


Lo que acabamos de hacer es obtener el tamaño de la estructura utilizando la clase Marshal, cuya misión consiste en permitirnos interaccionar con código no administrado; en concreto, el método que usaremos será SizeOf, pasándole como parámetro la propia estructura de datos.


El manipulador de la ventana lo obtendremos a través de la propiedad Handle, perteneciente a la clase WindowInteropHelper, contenida en el espacio de nombres System.Windows.Interop -que habremos declarado al comienzo de nuestro código-, pasando al constructor de esta clase una referencia a la ventana actual. Si estamos desarrollando una aplicación Windows Forms, podemos conseguir este manipulador a partir de la clase base del formulario, de la siguiente forma.


oFlashwInfo.hwnd = base.Handle;

El tipo de parpadeo que vamos a usar será sobre el título y borde de la ventana, y dado que hemos creado la enumeración FlashStatus para contener todas estas constantes, haremos uso de la misma, pero teniendo en cuenta que deberemos aplicar una operación de conversión de tipos sobre el valor devuelto por FlashStatus.FLASHW_CAPTION, para asignarlo adecuadamente al miembro dwFlags de la estructura FLASHWINFO.


Después de asignar el número de parpadeos y su intervalo, finalizaremos la operación llamando a la función FlashWindowEx, pasando como parámetro por referencia la estructura FLASHWINFO.


 


Ejecutando el efecto un número indeterminado de veces


En el ejemplo anterior realizamos el parpadeo un número concreto de veces, pero supongamos que queremos iniciar el efecto sin especificar su cantidad, para que sea el usuario quien lo detenga, y adicionalmente, que se visualice tanto en la ventana normal como en su icono de la barra de tareas.


En esta ocasión nos ayudaremos de los botones btnComenzar y btnTerminar de la ventana, pero antes de pasar al código de los mismos, explicaremos un detalle que puede resultar interesante si vamos a llamar a FlashWindowEx desde múltiples puntos del código de la ventana.


Si revisamos el ejemplo anterior, observaremos que la estructura FLASHWINFO la creamos a nivel local del método que hace la llamada a la función de la API de Windows. Sin embargo, podemos optimizar este aspecto de nuestro código utilizando -siempre que sea posible-una única instancia de dicha estructura.


Volviendo al presente ejemplo, lo que haremos será declarar, a nivel de la clase de la ventana, una variable para la estructura, inicializándola con los datos básicos cuando la ventana se haya cargado.


FLASHWINFO moFlashwInfo;
//….
private void Window_Loaded(object sender, RoutedEventArgs e)
{
moFlashwInfo = new FLASHWINFO();
moFlashwInfo.cbSize = Marshal.SizeOf(moFlashwInfo);
moFlashwInfo.hwnd = new WindowInteropHelper(Application.Current.MainWindow).Handle;
}

Si estuviéramos escribiendo la aplicación para Windows Forms, este código podríamos situarlo en el constructor del formulario, pero bajo WPF no podemos obtener el manipulador de ventana en el constructor, ya que todavía no lo tiene asignado, y por ese motivo empleamos el evento Loaded.


A continuación pasaremos al código de los botones que se van a encargar de poner en marcha y parar el parpadeo. Como podemos observar, utilizamos la misma estructura para establecer el efecto necesario en cada ocasión. En lo que respecta al inicio del efecto, combinamos dos valores de la estructura FlashStatus para indicar que el parpadeo se producirá en la ventana y barra de tareas de forma indefinida; para detenerlo, volvemos a asignar el valor adecuado a la misma estructura. En ambos casos, siempre terminamos con la llamada a FlashWindowEx.


private void btnComenzar_Click(object sender, RoutedEventArgs e)
{
moFlashwInfo.dwFlags = (int)FlashStatus.FLASHW_ALL | (int)FlashStatus.FLASHW_TIMER;
FlashWindowEx(ref moFlashwInfo);
}

private void btnTerminar_Click(object sender, RoutedEventArgs e)
{
// establecer parada del parpadeo en la estructura de datos
moFlashwInfo.dwFlags = (int)FlashStatus.FLASHW_STOP;

// detener el parpadeo
FlashWindowEx(ref moFlashwInfo);
}


En la siguiente imagen vemos este ejemplo en ejecución.



 


Parpadear en estado minimizado


Un escenario que se produce con frecuencia consiste en poner en ejecución un proceso con una duración prolongada, lo cual motiva que con toda probabilidad, el usuario minimice la ventana de la aplicación; en esta situación es muy recomendable avisar al usuario cuando el proceso termina, y una de las formas de hacerlo es mediante nuestro querido parpadeo de ventana.


Para intentar emular este contexto de ejecución, lo que haremos será detectar el momento en que el usuario minimiza la ventana, deteniendo la hebra de ejecución de la aplicación durante unos momentos mediante el método Thread.Sleep, para seguidamente, comenzar el efecto de parpadeo con el que avisaremos al usuario de que el proceso ha terminado. Como comportamiento adicional, cuando el usuario restaure la ventana, el parpadeo se detendrá.


Todo lo que necesitaremos en lo que respecta al código a escribir será el evento StateChanged, donde implementaremos la lógica que detectará cuándo la ventana se minimiza, asignando la información a la estructura FLASHWINFO -que reutilizamos del ejemplo anterior al ser visible a nivel de clase-y llamando a la función de la API.


private void Window_StateChanged(object sender, EventArgs e)
{
// iniciar el parpadeo al pasar dos segundos después de minimizar la ventana
if (this.WindowState == WindowState.Minimized)
{
System.Threading.Thread.Sleep(2000);
moFlashwInfo.dwFlags = (int)FlashStatus.FLASHW_TRAY | (int)FlashStatus.FLASHW_TIMERNOFG;
FlashWindowEx(ref moFlashwInfo);
}
}

 


FlashWindow. Los orígenes


Para conseguir el efecto de parpadeo en las ventanas, originalmente se desarrolló la función FlashWindow, la cual solamente realiza un parpadeo cada vez que es llamada. La firma de esta función en la documentación de Windows es la siguiente.


BOOL WINAPI FlashWindow(__in HWND hWnd, __in BOOL bInvert);


El primer parámetro contiene el manipulador de la ventana sobre la que aplicamos el efecto; el segundo un valor lógico que nos permite realizar un parpadeo, o bien cambiar el estado de la ventana a activa o inactiva; por último, el valor de retorno indica el estado de la ventana previo a la llamada de la función.


Para superar esta limitación, posteriormente se creó la función FlashWindowEx -de ahí el sufijo Ex-, que como hemos visto en los pasados ejemplos, aporta funcionalidad y características adicionales, simplificando nuestra labor de programación.


No obstante, dado que FlashWindow sigue estando disponible, vamos a crear un sencillo ejemplo ilustrativo de su uso.


En primer lugar añadiremos un nuevo botón a la ventana empleada como base para las pruebas.


<Window ….>
<my1:Button Name=»btnFlashWindow» Margin=»10″ Width=»110″ Click=»btnFlashWindow_Click»>Usar FlashWindow</my1:Button>
</Window>

El objetivo de este ejemplo consistirá en instanciar y activar un temporizador cuando pulsemos el anterior botón, de forma que en cada evento Elapsed del temporizador hagamos un parpadeo de ventana. Al mismo tiempo, empleando objetos TimeSpan, controlaremos el tiempo transcurrido, de forma que pasados varios segundos detendremos el temporizador, y por consiguiente el parpadeo.


En primer lugar declararemos en el código de la ventana la función FlashWindow y los elementos para este proceso que necesitan tener ámbito a nivel de clase.


using System.Timers;
//….
public partial class Window1 : Window
{
//….
[DllImport(«user32.dll»)]
public static extern bool FlashWindow(IntPtr hWnd, bool bInvert);

IntPtr hwndManipVentana;
Timer tmrTemporizador;
TimeSpan tsComienzo;
//….


Seguidamente, en el evento Loaded de la ventana obtendremos su manipulador -podríamos haber utilizado el miembro hwnd de la estructura FLASHWINFO, pero por simplificar, hemos preferido emplear una variable aparte-, mientras que al pulsar el botón instanciaremos y activaremos el temporizador, obteniendo también la hora de comienzo del proceso. Por último, en el evento Elapsed del temporizador iremos aplicando el parpadeo sobre la ventana, hasta que comprobemos mediante objetos TimeSpan que han transcurrido varios segundos, momento en el que pararemos el temporizador y terminará el efecto de parpadeo.


private void Window_Loaded(object sender, RoutedEventArgs e)
{
//….
hwndManipVentana = new WindowInteropHelper(Application.Current.MainWindow).Handle;
}

private void btnFlashWindow_Click(object sender, RoutedEventArgs e)
{
// instanciar y configurar el temporizador
tmrTemporizador = new Timer();
tmrTemporizador.Interval = 500;
tmrTemporizador.Elapsed += new ElapsedEventHandler(tmrTemporizador_Elapsed);

// obtener la hora de comienzo del proceso
DateTime dtComienzo = DateTime.Now;
tsComienzo = new TimeSpan(dtComienzo.Hour, dtComienzo.Minute, dtComienzo.Second);

// arrancar el temporizador
tmrTemporizador.Start();
}

void tmrTemporizador_Elapsed(object sender, ElapsedEventArgs e)
{
// hacer parpadear la ventana en cada intervalo del temporizador
FlashWindow(hwndManipVentana, true);

// obtener la hora actual y si ha transcurrido
// un determinado tiempo, detener el temporizador
// y con ello el parpadeo
DateTime dtFinal = DateTime.Now;
TimeSpan tsFinal = new TimeSpan(dtFinal.Hour, dtFinal.Minute, dtFinal.Second);
TimeSpan tsDiferencia = tsFinal – tsComienzo;

if (tsDiferencia.Seconds == 10)
{
tmrTemporizador.Stop();
}
}


Y con este ejemplo damos por concluido el artículo, en el que a través del uso de estas funciones de la API de Windows, hemos visto un medio de captar la atención de nuestros usuarios sobre el programa que tenemos en ejecución.


Al igual que hicimos en la primera parte, en los enlaces para C# y VB tenemos disponibles los proyectos con los ejemplos para cada lenguaje.


Esperando que os resulte de utilidad, un saludo para todos.

7 Comentarios

  1. lmblanco

    Hola Enrique

    Muchas gracias, me alegra que te haya gustado 😎

    Un saludo.
    Luismi

  2. anonymous

    Por que sera que ya se programa en Visual Basic .Net ya son mas poco los ejemplos en este lenguaje de programación.
    Aun habemos personas que nos gusta programar en este lenguaje.

  3. lmblanco

    Hola Richard

    Aunque los ejemplos de código en el artículo están en C#, tienes también disponible un enlace para descargar el código fuente en VB.NET, revisa hacia el final del texto del artículo y lo encontrarás.

    Un saludo.
    Luismi

  4. anonymous

    Gracias por el aporte y para lo demas usuarios, sino pueden traducir de C# a VB o viceversa para que quieren aprender de llamadas APIs?

  5. lmblanco

    Hola Luis

    Gracias por tu interés en el artículo. Para otros usuarios que trabajen con distintas herramientas de desarrollo, debería ser dicha herramienta la que les proporcione el acceso a las llamadas a las APIs de Windows.

    Un saludo,
    Luismi

  6. anonymous

    Todoi está muy bien explicado, aparte de aprender a usar la FlashWindowEx, usé por primera vez una aplicacion WPF, se ve muy interesante hoy no dormiré por quedarme cachjarriando estas dos cosas.

    Muchas Gracias

  7. lmblanco

    Hola Guillermo

    Muchas gracias por tu interés en el artículo. Me alegra que te haya gustado 😎

    Un saludo,
    Luismi

Deja un comentario

Tema creado por Anders Norén