ByeByeBrain – Juego de Tower defence de Plain Concepts

Hola a todos, estamos orgullosos de presentar desde Plain Concepts Game Studio, ByeByeBrain. Un juego de tower defence para Windows Phone 7. El juego ha sido desarrollado en XNA usando 3D para el gameplay, con 5 tipos de torretas y 4 tipos de zombines incluyendo un mini juego para los ataques especiales. El juego combina el clásico de defensa de torres con mini juegos y una cámara interactiva durante el juego. Tambien cuenta con integración de Facebook y Twitter para publicar tu puntuación.

Podeis ver un video en Vimeo http://vimeo.com/14723559 y una versión en HD con Silverlight en: http://games.plainconcepts.com/.

También unos fondos de pantalla.

cleaner1920x1200colonel1920x1200fuzz1920x1200nerd1920x1200spanker1920x1200

Algunas cosas interesantes sobre el juego:

· Tiene un sistema de propiedades basado en el de DependencyProperty como WPF y Silverlihgt

· Tiene un sistema de animacions como BeginAnimation y SingleAnimation.

· Approx. 20Mb de fichero XAP.

· 61139 lineas de codigo (Visual Studio 2010 metric system)

· El juego ha sido desarrollado en tres localizaciones diferentes durante los 2 meses de desarrollo

· Uno de los miembros es adicto a los donnetes

Luis Guerrero.

Primer dispositivo de Windows Phone 7 en Plain Concepts

Hola a todos!

Ya tenemos teléfono de Windows Phone 7 en Plain Concepts, hoy mismo lo hemos recibido de Fedex. Es de la marca LG y parece que a todos los que estamos desarrollando para WP7 tenemos el mismo modelo.

El teléfono arranca es unos escasos 10 segundos desde que le das al teléfono hasta que aparece la lista de Tiles disponibles o la pantalla para introducir el pin, es impresionante la velocidad. Por lo demás muy chulo todo, el correo se configura súper rápido, la cuenta de Facebook y todo lo demás.

Para sincronizarlo con el PC el software de Zune, una versión que tenemos la gente que estamos en el TAP de WP7 y además puedes activarlo para ejecutar tus aplicaciones en él. Hay algunas aplicaciones en el marketplace de ejemplo y puedes depurar directamente en el dispositivo.

Os adjunto algunas fotos!.

_DSC0841_DSC0842_DSC0843_DSC0844_DSC0845_DSC0846_DSC0847

Por cierto también me gustaría presentaros, http://games.plainconcepts.com/ el nuevo departamento de desarrollo de video juegos de Plain Concepts.

Saludos.

Luis Guerrero.

Como acceder al teclado nativo en una aplicación XNA de Windows Phone 7

Una de las mejores de desarrollas aplicaciones en XNA es que tienes acceso al dibujado de bajo nivel para dibujar sprites y mayas 3d, pero en ocasiones necesitas objetos de alto nivel que proporcionen una funcionalidad específica, es el caso del teclado.

Si estamos desarrollando una aplicación de Windows Phone 7 en XNA y queremos empezar una nueva partida y queremos obtener el nombre del usuario tenemos que crear nuestro propio teclado. Esto puede ser un poco complicado hacerlo, es por eso que Microsoft ya incluye una API que nos permite invocar una Task dentro del teléfono para pedir datos al usuario.

image

Guide.BeginShowKeyboardInput(
       PlayerIndex.One,
       "You Win",
       "Insert your name",
       "",
       new AsyncCallback(OnEndShowKeyboardInput),
       null);

Con este código lo que estamos haciendo en invocar asíncronamente a la tarea del teclado, hay que tener en cuenta que la aplicación se desactivará en esta ejecución y el código que tengas en el evento se ejecutará. Una vez que el usuario termina de escribir su nombre y hace tap en aceptar volvemos a la aplicación.

image

private void OnEndShowKeyboardInput(IAsyncResult result)
{
   name = Guide.EndShowKeyboardInput(result);
}

Aquí tenemos el string que el usuario ha escrito.

Os podéis descargar una demo desde aquí.

Luis Guerrero.

Taskbar de Windows 7 desde WPF

Ahora que se acerca el lanzamiento de Windows 7 tenemos que intentar que nuestras aplicaciones se integren de la mejor manera con Windows 7, y esto pasa por hacer que nuestras aplicaciones se lleven bien con la nueva Taskbar.

Como comentaba en mi artículo anterior http://www.luisguerrero.net/post/2009/04/05/Windows-7-Taskbar.aspx se pueden trabajar con varias características de la Taskbar, nosotros vamos a ver cómo crear una tarea personalizada y como añadir un pequeño botón a la pre visualización de la ventana como la del Windows Media Player. Todo esto lo vamos a hacer para nuestra aplicación de WPF, así que como programadores de .net no tenemos una API directamente para hacer esto así que tenemos que hacer P/Invoke desde .NET. Pero estamos de suerte porque Microsoft ha creado varias librerías para que los programadores de .NET usemos las nuevas características del SO directamente desde nuestro lenguaje favorito de .NET.

Para ello tenemos que descargar dos librerías

Nosotros vamos a trabajar con la librería de la Taskbar, pero está referencia al proyecto de Vista Bridge Library. Dentro del proyecto de la Taskbar encontramos una clase llamada Windows7Taskbar que nos va a permitir hacer una serie de cosas básicas con la taskbar como:

  • Habilitar o deshabilitar la pre visualización de miniatura de nuestra aplicación
    • public static void EnableCustomWindowPreview(IntPtr hwnd)
    • public static void DisableCustomWindowPreview(IntPtr hwnd)
  • ·Establecer el appId de nuestra aplicación para que así se agrupen las ventanas en la Taskbar:
    • public static void SetCurrentProcessAppId(string appId)
  • Establecer el appId para una de las ventanas de nuestra aplicación
    • public static void SetWindowAppId(IntPtr hwnd, string appId)

También vamos a poder trabajar con los JumpList y con los ThumbButton.

JumpList

Para trabajar con los JumpList tenemos una clase dentro del proyecto de Windows 7 Taskbar llamada JumpListManager, aquí podemos ver qué métodos, propiedades y eventos tiene.

Para crear una instancia de esta clase tenemos dos constructores en el que se nos pide o el HANDLE de la ventana o el AppId de la ventana.

image

Obtener el Handle de una ventana en Windows Forms es muy sencillo solo tenemos que acceder a la propiedad Handle de la clase Form y ya lo tenemos, pero ¿cómo se accede al manejador de una ventana de WPF?, si intentamos buscar esa propiedad en la clase System.Windows.Window de WPF observamos que no tenemos esa propiedad, es más, que ningún control de WPF tiene una propiedad Handle.

Esto es así porque como ya sabemos WPF es una tecnología nueva en la que no se utiliza nada de lo anterior para trabajar, y de hecho no es como Windows Forms que es un wrapper muy grande de la API de Win32, aquí en WPF se ha empezado desde cero. Pues bien tiene que haber alguna manera de por lo menos, obtener el Handle de la ventana porque de lo que estamos seguros es de que por mucho que el contenido de una ventana sea WPF o WF al final se llamada a la función CreateWindowEx que en alguno de los casos nos devolverá el Handle de la ventana.

Para obtener el Handle de la ventana en WPF tenemos que ir hasta el namespace System.Windows.Interop para encontrarnos con la clase WindowInteropHelper que una vez creada una instancia, pasándole por argumento la ventana (System.Windows.Window) podemos obtener el Handler (IntPtr) de la ventana y incluso su propietario Owner (o establecerlo). Una vez superado esta pequeña traba podemos continuar con nuestra integración de la Taskbar.

El siguiente problema que se nos plantea es cuando es el mejor momento para crear la instancia de esta clase, porque si lo hacemos antes de que el Handle de la ventana se haya construido, la llamada a la función fallará. Tenemos que elegir el mejor momento para hacerlo, ese momento puede ser cuando se lanza el evento Loaded de la ventana porque es cuando ya se ha construido el árbol visual, se ha hecho measure, arrange y se va a mostrar la ventana al usuario. Pero también el mejor momento puede ser cuando la Taskbar nos avista de que se va a proceder a colocar el icono de la aplicación en la propia Taskbar, este es el momento justo para hacerlo.

Ese momento del que estoy hablando es cuando la aplicación recibe el mensaje “TaskbarButtonCreated” que previamente ha sido registrado llamado a RegisterWindowMessage(L” TaskbarButtonCreated”). Teniendo en cuenta que ya hemos llamado a esa función como podemos escuchar los mensajes de la API de Win32 desde una aplicación WPF. En Windows Forms es muy sencillo porque tenemos que sobrescribir la función virtual WndProc en la que se nos pasa por referencia el Mensaje que se está procesado en ese momento, pero de la misma manera si vamos a WPF vemos que no tenemos esa función disponible para sobrescribir.

La cosa es un poco más complicada porque si alguno recuerda de otros post anteriores, en WPF tenemos un objeto llamado Dispatcher (System.Windows.Threading) que se encarga de procesar los mensajes de la API de Win32, pero de alguna manera queremos que después de que este Dispatcher procese los mensajes lo hagamos nosotros, básicamente lo que queremos hacer en un WindowHook, pero un poco más elegante. No tenemos que llamar a SetWindowHookEx sino que tenemos que explorar de nuevo System.Windows.Interop para encontrar la clave.

La clase que estamos buscando ahora es: HwndSource, que según la documentación presenta el contenido de WPF en una ventana de Win32, que es justamente lo que estamos buscando, pero el caso es que esta clase ya deberá de esta creada porque ya tenemos nuestra ventana creada, así que tenemos que obtener el HwndSource para nuestra ventana actual.

h = new WindowInteropHelper(Application.Current.MainWindow);

HwndSource source = HwndSource.FromHwnd(h.Handle);

Esta clase tiene un método llamado AddHook que acepta por parámetro un delegado que será la función que se llamará cada vez que haya un mensaje disponible para procesarse. Esta es la signatura de la función a ejecutar:

image

public IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)

Lo último que nos queda es filtrar todos los mensajes que nos van a llegar por el mensaje que nos enviara la Taskbar diciendo que ya está lista. Podemos ver el código completo de todo esto aquí.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Windows7.DesktopIntegration;
using System.Windows;
using System.Windows.Interop;
using System.Diagnostics;
using System.Drawing;


namespace MainClient
{
    public class Taskbar
    {
        private static Taskbar instance = new Taskbar();
        public static Taskbar Instance
        {
            get
            {
                return instance;
            }
        }
        private IntPtr idWindow;
        private JumpListManager manager;
        private ThumbButtonManager thumbButtonManager;
        private WindowInteropHelper h;
        public Taskbar()
        {
            HwndSourceHook hook = new HwndSourceHook(HwndSourceHook);
            h = new WindowInteropHelper(Application.Current.MainWindow);
            HwndSource source = HwndSource.FromHwnd(h.Handle);
            source.AddHook(hook);
        }

        public IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == Windows7Taskbar.TaskbarButtonCreatedMessage)
            {
                manager = new JumpListManager(h.Handle);
                manager.UserRemovedItems += new EventHandler<UserRemovedItemsEventArgs>(OnUserRemoveItems);
                manager.Refresh();
                if (manager.CustomDestinationCategories.Count() == 0)
                {
                    // create list
                    manager.AddUserTask(new ShellLink()
                    {
                        Title = "Enviar mensaje",
                        Path = Process.GetCurrentProcess().MainModule.FileName,
                        Arguments = " /send"
                    });
                }

                manager.Refresh();

                thumbButtonManager = new ThumbButtonManager(h.Handle);
                ThumbButton button = thumbButtonManager.CreateThumbButton(101, SystemIcons.Information, "No molestar!!");
                button.Clicked += new EventHandler(OnChangeNotificationAlerts);
                thumbButtonManager.AddThumbButtons(button);
            }
            if (thumbButtonManager != null)
            {
                System.Windows.Forms.Message m = new System.Windows.Forms.Message();
                m.HWnd = hwnd;
                m.LParam = lParam;
                m.Msg = msg;
                m.WParam = wParam;
                thumbButtonManager.DispatchMessage(ref m);
            }
            return IntPtr.Zero;
        }

        void OnChangeNotificationAlerts(object sender, EventArgs e)
        {
            Services.Instace.IsNotificationEnabled = !Services.Instace.IsNotificationEnabled;
            if (!Services.Instace.IsNotificationEnabled)
            {
                Windows7Taskbar.SetTaskbarOverlayIcon(h.Handle, SystemIcons.Error, "Sin notificaciones");
            }
            else
            {
                Windows7Taskbar.SetTaskbarOverlayIcon(h.Handle, null, null);
            }
        }


        void OnUserRemoveItems(object sender, UserRemovedItemsEventArgs e)
        {

        }
    }
}

Una vez que ya tenemos la instancia del JumpListManager ya podemos agregar tareas a la Taskbar y asociarles una acción. En el código de arriba podemos ver que se añade una acción que es enviar mensaje que, básicamente, ejecuta la aplicación actual pero con el parámetro /send.

Otra posibilidad que hay con la Taskbar es poder insertar botones dentro de la previsualización de la ventana, como hace el Windows Media Player. A través de la clase ThumbButtonManager podemos agregar botones a la Taskbar y realizar una acción cuando se haga clic. En el ejemplo de arriba podemos ver como añadimos un botón configuramos el Icono a mostrar y nos suscribimos al evento Click.

Para terminar el ejemplo tenemos que “alimentar” al ThumbButtonManager con los mensajes que recibimos de Win32, para que pueda realizar sus tareas, es por eso que tenemos que llamar al método DispatchMessage para que pueda comprobar que se ha hecho clic en un botón.

Windows 7: Taskbar

Una de las características más novedosas de Windows 7 es la nueva Taskbar. Es una evolución del concepto de Taskbar que teníamos en las versiones previas de Windows pero que ahora se ha renovado. Veremos cuáles son esas características y cómo podemos aprovecharlas para nuestras aplicaciones.

Aquí podemos ver una captura de pantalla de las nueva Taskbar que viene con Windows 7:

image

Podemos observar que ahora la barra de acceso rápido (Quick Launch) ha desaparecido, y ahora en su lugar la barra de tareas es como una especie de mezcla entre ambos, pues como podemos observar tenernos en la Taskbar programas ejecutándose y programas que no se están ejecutando, además también podemos ver como algunos de los elementos tiene varias ventanas y están se agrupan (en el ejemplo podemos ver a Microsoft PowerPoint).

Con respecto a la agrupación ¿Cómo Windows 7 sabe que una ventana corresponde a un mismo programa, proceso o las tiene que agrupar?. Ahora las aplicaciones tiene un Application Id, que representa el identificador de la ventana para que la Taskbar sepa si tiene que agrupar o no, eso permite que dos ventanas del mismo proceso aparezcan virtualmente como dos aplicaciones diferentes y justo al revés, es decir, que dos ventanas de procesos diferentes aparezcan agrupadas. Esta propiedad es un string y está limitado a 128 caracteres.

Los programas actuales no están programados utilizando las características de Windows 7, así que como sabe el sistema como agrupar las ventanas, en principio utiliza en nombre del proceso.

Podemos tener varios tipos de procesos especiales en los que se tiene que tener en cuenta el nombre de la Taskbar:

· Varios ejecutable, la misma aplicación

· El mismo ejecutable (host), varias aplicaciones.

Ejemplos de estos, tenemos por ejemplo, el proceso de host de Visual Studio (vshost.exe) que es el host de una aplicación que depuramos dentro de VS con lo que en la Taskbar aparecerá el programa como vshost y no como el nombre de nuestro ejecutable. El otro ejemplo lo tenemos en Internet Explorer que ahora dentro del mismo proceso, tenemos diferentes ventanas y queremos que aparezcan como procesos diferentes dentro de la Taskbar.

Esta es la función que tenemos que llamar para poder establecer el nombre de todas las ventanas:

#include <windows.h>
#pragma comment (lib, "shell32.lib")
SetCurrentProcessExplicitAppUserModelId(L"Microsoft.Samples.AppId1");

Y si queremos específicamente establecer el Application Id para una ventana en concreto tenemos que hacer esto:

PROPVARIANT pv;
InitPropVariantFromString(L"Microsoft.Samples.AppId2", &pv);
IPropertyStore *pps;
HRESULT hr = SHGetPropertyStoreForWindow(hWnd, IID_PPV_ARGS(&pps));
pps->SetValue(PKEY_AppUserModel_ID, pv);

JumpList

Una de las nuevas características de la Taskbar es la inclusión de las JumpList en los menus contextuales de las aplicaciones que tenemos en la barra.

image

Aquí podemos ver una captura de esa característica, que nos permite ver una lista de los elementos recientes que hemos usado con ese programa. Ahora simplemente para abrir uno de esos ficheros tenemos que hacer clic sobre el icono y automáticamente se abrirá el fichero correspondiente.

Esta lista es generada a partir de varios orígenes, una de ellas es las lista de ficheros abiertos desde el explorador de Windows, lo que vienen a ser los últimos ficheros abiertos. Además, si dentro de nuestra aplicación usamos el Common File Dialog para abrir ficheros automáticamente el fichero abierto se añadirá a la lista de nuestro usuario. Además de eso disponemos de una API que nos permite acceder a los elementos de la lista y configurarlo a nuestra manera.

Además de lo que son archivos recientemente usados por nuestra aplicación también podemos insertar tareas dentro de los JumpList. Las tareas son comandos que desde la TaskBar se pueden invocar para nuestra aplicación y el usuario se puede comunicar con nuestra aplicación de una manera normalizada.

Aquí podemos ver un diagrama de las diferentes opciones de los JumpList.

image

En el que podemos observar varios grupos. Abajo del todo tenemos las tareas de la TaskBar, Cerrar Ventana, Despinear esta aplicación de la taskbar y lanzar de nuevo otra instancia de la aplicación. Encima tenemos, tareas definidas por el desarrollador de la aplicación. Un ejemplo de estas tareas pueden ser las tareas del Windows Media Player que incluye una tarea de reproducir toda la música mezclada y otra que es resumir la última lista reproducida. Esta última acción no siempre está disponible porque tenemos que haber reproducido una lista previamente para que nos aparezca esta tarea.

Además de eso también podemos tener otra serie de acciones disponibles para el usuario, que representan acciones previamente hechas en la aplicación. Siguiendo el ejemplo del Windows Media Player, podemos ver que tenemos una serie de acciones como reproducir el resultado de la búsqueda de música “The Pianist”, reproducir toda la música, reproducir todo el vídeo, reproducir la lista “RockBand”, reproducir el artista “The Alan Parsons Project” y ColdPlay y Pink Floyd. Toda esta lista son las acciones pineadas (Pinned) es decir acciones que el usuario (yo, en este caso) quiere tener disponibles para su aplicación.

image

Si ponemos como ejemplo otra aplicación como por ejemplo una aplicación para administrar la facturación de una empresa, podemos tener disponibles las últimas facturas a las que el usuario ha accedido en la aplicación, permitiendo así al usuario poder acceder y pinear las facturas más importantes de la aplicación.

Esta última parte es muy importante pues permite que las aplicaciones se integren perfectamente con la nueva Taskbar explotando toda la potencia de la nueva Experiencia de Usuario de Windows 7.

En el próximo artículo veremos cómo podemos acceder a toda esta información desde código, tanto nativo como administrado.

Windows 7: Coalescing timers

Bajo este título tan raro se encuentra escondida una de las características más interesantes de Windows 7 para desarrollar aplicaciones energéticamente eficientes.

Conforme la potencia de cálculo de los procesadores aumenta, también aumenta su consumo energético, y por supuesto el software juega un papel importante en este consumo. Así que lo que veremos en este post es algunas de las características más interesantes de Windows 7 para hacer aplicaciones energéticamente eficientes.

Los procesadores actuales incluyen funcionalidad que les permite establecerse en un estado de Power Ilde, en el que el procesador entra en un estado de bajo consumo de energía hasta que es interrumpido por alguna razón. La velocidad de los procesadores actuales hace que la medida de su tiempo comparada con la nuestra sea completamente abismal, ya que para nosotros un simple segundo es más que la eternidad para un procesador. Así que teniendo en cuenta este escenario y el consumo de CPU de la mayoría de los programas y del kernel en sí, el escenario más común a optimizar por la mayoría de las aplicaciones es el Idle. Es decir tenemos que hacer aplicaciones que respeten al máximo el estado de bajo consumo energético del procesador haciendo así que el procesador no esté constantemente saliendo y entrando de ese modo de bajo consumo energético, esto es así porque las constantes interrupciones hacen que se consuma más energía en esos cambios de estado que la energía que se ahora por estar el procesador en el estado de bajo consumo energético.

Sabiendo esto como podemos, los desarrolladores, hacer aplicaciones que no “molesten” tanto al procesador. Pongo molesten al procesador porque no se trata de no hacer funcionalidad por ahorrar energía, más quisiéramos, sino de intentar aprovechar al máximo las características de consumo de energía de Windows 7. Aquí tenemos algunas opciones.

· Evitar el uso de poolling, usar eventos, es decir en vez de están constantemente consultando una API para saber si un valor se ha establecido o se ha cambiado usar un evento de Windows para que se nos notifique ese cambio.

· No hacer poolling para conocer el estado energético del sistema, usar RegisterPowerSettingNotification

HPOWERNOTIFY WINAPI RegisterPowerSettingNotification(  
  __in  HANDLE hRecipient, 
  __in  LPCGUID PowerSettingGuid, 
  __in  DWORD Flags
);

· En el caso de que no se pueda usar un evento nunca hacer polling más a menudo que un segundo.

Para el caso de que tengamos que hacer poolling constantemente Windows 7 incluye una API nueva para hacer temporizadores agrupados (coalescing timers). Veamos que son estos temporizadores agrupados.

clip_image004

clip_image006

En las dos gráficas podemos ver fechas de dos colores verdes y naranjas. Las fechas verdes que son constantes en el tiempo representan los eventos periódicos del temporizador del sistema, del kernel. Las flechas naranjas representan los eventos de temporizador personalizados, que se producen en el sistema, es decir temporizadores que los programadores programan. Lo que podemos ver entre estas dos graficas en que en la gráfica de abajo, que representa a Windows 7, podemos ver como los temporizadores naranjas se han alineado con los temporizadores verdes, es decir ahora los eventos se retrasan para agruparse cuando el sistema se levanta del estado de Ilde. Esto permite que entre evento y evento de color verder (kernel), el sistema se encuentre en completo idle y así que procesador pueda descansar durante ese periodo de tiempo, y solo cuando el procesador necesita ser “levantado” para ejecutar el evento del sistema (verde) aprovechamos y ejecutamos los eventos personalizados (naranjas) que tengamos pendientes.

Como podéis ver es una solución muy ingeniosa para optimizar nuestro escenario más habitual que como dijimos antes es el Idle del pc. Esto no significa que tengamos que cambiar todos nuestros temporizadores a temporizadores agrupados (coalescing timers), sino que si estamos haciendo alguna tarea en segundo plano, como monitorizar una web, o un dispositivo, podemos usar estos temporizadores para hacer nuestra aplicación más óptima con el consumo energético. En cualquier otro caso podemos seguir usando los temporizadores normales.

Este tipo de temporizadores es muy util para usarse en servicios de sistema que necesitan monitorizar algo en segundo plano.

Para crear un temporizador tenemos que llamar a la función CreateWaitableTimerEx

HANDLE WINAPI CreateWaitableTimerEx(  
    __in_opt  LPSECURITY_ATTRIBUTES lpTimerAttributes,  
    __in_opt  LPCTSTR lpTimerName,  
    __in      DWORD dwFlags, 
    __in      DWORD dwDesiredAccess
);

Una vez llamado tenemos que llamar a la función SetWaitableTimerEx para inicializar el temporizador.

BOOL SetWaitableTimerEx(  
    __in  HANDLE hTimer, 
    __in  const LARGE_INTEGER *lpDueTime, 
    __in  LONG lPeriod,  
    __in  PTIMERAPCROUTINE pfnCompletionRoutine, 
    __in  LPVOID lpArgToCompletionRoutine,  
    __in  PREASON_CONTEXT WakeContext,  
    __in  ULONG TolerableDelay
);

Quiero resaltar en esta función SetWaitableTimerEx, que el último parámetro ULONG TolerableDelay es el tiempo máximo en el que el temporizador puede ser retrasado para poder ser agrupado, así podemos indicar un tiempo por el cual el temporizador puede esperar sin problema, pasado este tiempo el sistema ejecutará el temporizador como un temporizador normal.

O podemos abrir un temporizador existente llamando a OpenWaitableTimer

HANDLE WINAPI OpenWaitableTimer(  
    __in  DWORD dwDesiredAccess,  
    __in  BOOL bInheritHandle,  
    __in  LPCTSTR lpTimerName
);

Para los programadores administrador (.NET) incluyo código de una clase que implementa esta funcionalidad:

public sealed class CoalescingTimer : IDisposable
    {
      private IntPtr _timer;
    
      public CoalescingTimer(string name)
      {
          _timer = UnsafeNativeMethods.CreateWaitableTimerEx(IntPtr.Zero, name, 0, TIMER_MODIFY_STATE);
      }
    
      public event EventHandler Tick;
    
      public void Set(Int64 dueTime, int period, string reason, uint tolerableDelay)
      {
          UnsafeNativeMethods.POWER_REQUEST_CONTEXT reasonContext = new UnsafeNativeMethods.POWER_REQUEST_CONTEXT();
          reasonContext.Version = UnsafeNativeMethods.POWER_REQUEST_CONTEXT_VERSION;
          reasonContext.Flags = UnsafeNativeMethods.POWER_REQUEST_CONTEXT_SIMPLE_STRING;
          reasonContext.SimpleReasonString = reason;
          _registeredDelegate = new CoalescingTimerProc(OnTimer);
          bool success = UnsafeNativeMethods.SetWaitableTimerEx(_timer, ref dueTime, period, _registeredDelegate, IntPtr.Zero, ref reasonContext, tolerableDelay);
      }
    
      private CoalescingTimerProc _registeredDelegate;
    
      private void OnTimer(IntPtr argument, uint timerLowValue, uint timerHighValue)
      {
          if (Tick != null)
              Tick(this, EventArgs.Empty);
      }
    
      const uint TIMER_MODIFY_STATE = 0x0002;
    
      public void Dispose()
      {
          UnsafeNativeMethods.CloseHandle(_timer);
      }
    }
    internal delegate void CoalescingTimerProc(
      IntPtr argument,
      uint timerLowValue,
      uint timerHighValue);
    
    internal sealed class UnsafeNativeMethods
    {
      [DllImport("kernel32.dll")]
      public static extern IntPtr CreateWaitableTimerEx(
          IntPtr lpSecurityAttributes,
          string timerName,
          uint flags,
          uint desiredAccess);
    
      [DllImport("kernel32.dll")]
      public static extern bool SetWaitableTimerEx(
          IntPtr hTimer,
          ref Int64 dueTime,
          int period,
          CoalescingTimerProc completionRoutine,
          IntPtr argument,
          ref UnsafeNativeMethods.POWER_REQUEST_CONTEXT reasonContext,
          uint tolerableDelay);
    
      [DllImport("kernel32.dll")]
      public static extern bool SetWaitableTimerEx(
          IntPtr hTimer,
          ref Int64 dueTime,
          int period,
          CoalescingTimerProc completionRoutine,
          IntPtr argument,
          ref UnsafeNativeMethods.POWER_REQUEST_CONTEXT_DETAILED reasonContext,
          uint tolerableDelay);
    
      [DllImport("kernel32.dll")]
      [return: MarshalAs(UnmanagedType.Bool)]
      public static extern bool CloseHandle(IntPtr handle);
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
      public struct POWER_REQUEST_CONTEXT
      {
          public UInt32 Version;
          public UInt32 Flags;
          [MarshalAs(UnmanagedType.LPWStr)]
          public string SimpleReasonString;
      }
  }

Luis.