måndag 12 oktober 2009

CeSetUserNotificationEx, DateTime.Now and GetLocalTime

It's been known for a while now that DateTime.Now has a few time zone related quirks in it. To overcome these issues I've come to use the native function GetLocalTime as a replacement. GetLocalTime returns SYSTEMTIME and converting it to DateTime is as easy as 1-2-3. Add the following code to the SYSTEMTIME class described earlier.
            public DateTime GetAsDateTime()
  {
      return new DateTime(wYear, wMonth, wDay, wHour, wMinute, wSecond);
  }
Declaration and usage of GetLocalTime is done like this:

        [DllImport("coredll.dll")]
public static extern void GetLocalTime(SYSTEMTIME sysTime);

SYSTEMTIME sysTime = new SYSTEMTIME(DateTime.Now);
GetLocalTime(sysTime);
DateTime dtLocalTime = sysTime.GetAsDateTime();

Now that's that..

What I really wanted to talk about tonight was the rest of the Notification API's. Or some of them at least.
All the API's that we have looked at so far has been marked as obsolete by Microsoft. They are still supported but are so only for back compatibility.
Instead of using CeRunAppAtTime and CeSetUserNotification you are now encouraged to use CeSetUserNotificationEx. The main difference between these two are the use of the CE_NOTIFICATION_TRIGGER struct in the latter. This struct basically holds information on what triggers the notification and what application to run. It's declaration is as follows.

    [StructLayout(LayoutKind.Sequential)]
public class CE_NOTIFICATION_TRIGGER
{
 public UInt32 Size = 0;
 [MarshalAs(UnmanagedType.U4)]
 public CeNotificationType Type;
 [MarshalAs(UnmanagedType.U4)]
 public CeNotificationEvent Event;
 [MarshalAs(UnmanagedType.LPWStr)]
 public string pApplication;
 [MarshalAs(UnmanagedType.LPWStr)]
 public string pArgs;
 public SYSTEMTIME StartTime;
 public SYSTEMTIME EndTime;
 public CE_NOTIFICATION_TRIGGER()
 {
 }
}
The struct is dependent of a couple of ENUMs that specifies the type and event parameter. CeNotificationEvent has already been discussed but CeNotificationType is new. It's values are..

    public enum CeNotificationType
{
  CNT_EVENT = 1           //@flag CNT_EVENT  | System event notification
  ,
  CNT_TIME              //@flag CNT_TIME   | Time-based notification
      ,
  CNT_PERIOD            //@flag CNT_PERIOD | Time-based notification is active for
      // time period between stStart and stEnd
      ,
  CNT_CLASSICTIME         //@flag CNT_CLASSICTIME | equivalent to using (obsolete)
  // CeSetUserNotification function - standard command line is
  // supplied. lpszArguments must be NULL
}

Now what it does is that it tells the API what type of trigger it is. The names speak for themselfs so I won't dig any further in that.

As you can see we use SYSTEMTIME but since the API expects the SYSTEMTIME structs to be a part of the CE_NOTIFICATION_TRIGGER struct then we must change our implementation of SYSTEMTIME from beeing a Class to become a Struct. This means that our older code will break unless you add the "ref" keyword in your API declarations and API calls. As follows..

        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeRunAppAtTime")]
   public extern static bool RunAppAtTime(string AppName, ref SYSTEMTIME SysTime);

   RunAppAtTime(@"\windows\bubblebreaker.exe", ref sysTime);
As with CeSetUserNotification, CeSetUserNotificationEx alse makes use of the CE_USER_NOTIFICATION struct. Just remember tough that if Type is CNT_EVENT you must pass a NULL-parameter instead of CE_USER_NOTIFICATION! If you don't; CeSetUserNotificationEx will fail!

The actual declaration of CeSetUserNotificationEx are like this:

        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeSetUserNotificationEx")]
    public static extern uint SetUserNotificationEx([MarshalAs(UnmanagedType.U4)]uint hNotification, CE_NOTIFICATION_TRIGGER Trigger, CE_USER_NOTIFICATION Notification);
The following code will create a time based trigger and connect that trigger to a notification that will fire up \windows\fexplore.exe.
Note that you need to compile this sample with the /unsafe switch. You could always hard code the size of CE_NOTIFICATION_TRIGGER in which case /unsafe won't be necessary. If hard coded; the value is 52.
Also don't forget to pass NULL instead of ceun if you change the trigger type to CNT_EVENT.

            CE_NOTIFICATION_TRIGGER cnt = new CE_NOTIFICATION_TRIGGER();
         CE_USER_NOTIFICATION ceun = new CE_USER_NOTIFICATION();
         cnt.Type =CeNotificationType.CNT_TIME;
         cnt.StartTime = stStart;
         cnt.EndTime = stEnd;
         cnt.pApplication = @"\windows\fexplore.exe";
         cnt.pArgs = "";
         cnt.Size = (UInt32)Marshal.SizeOf(cnt); // Needs to compile with /unsafe

         ceun.ActionFlags = PUN_FLAGS.PUN_LED;
         ceun.sDialogText = "Dialogtext";
         ceun.sDialogTitle = "Title";
         ceun.nMaxSound = 0;

         hNotificationEx = SetUserNotificationEx(0, cnt, ceun);
         if (hNotificationEx > 0)
             MessageBox.Show("Successfully created a notification. Handle: " + hNotificationEx.ToString());
         else
             MessageBox.Show("Failed to create a notification");

Reviewing it, I don't think I forgot something important.. Tomorrow we'll look at how to remove a previously created notification and later this week we'll take a peek at how to do a system wide enumeration of all existing notifications.

Inga kommentarer:

Skicka en kommentar