The State and Notifications Broker API Part 2

6/4/2007

Jim Wilson, JW Hedgehog, Inc.

January 2007

Summary

This month's column continues Jim’s discussion, in Part 1 of this column, of the State and Notifications Broker. The focus this month is on notification specialization. This month's column includes details about how to create conditional notifications, how to efficiently handle notifications for rapidly changing state values, and how to create notifications that your program can handle even when that program isn't running. The discussion, and all code examples, cover both native (C\C++) and managed (.NET Compact Framework) code. (27 printed pages)

Applies To

Microsoft® .NET Compact Framework

Microsoft® .Windows Mobile 5.0

Microsoft® .Visual Studio 2005

Introduction

Specializing Notifications

Conditional Notifications

Batch Notifications

Persistent Notifications

Creating Persistent Notifications in Native Code

Creating Persistent Notifications in Managed Code

Persistent Notification Architecture

Persistent Notification and Application Installation

Conclusion

Introduction

This month continues our discussion of the features that are offered with Windows Mobile 5.0, Visual Studio 2005, and .NET Compact Framework 2.0. As you may recall, in Part 1 of this column we started looking at the architecture of the State and Notifications Broker, and its role as the central repository for all System State and User State information.

At the end of that column, we talked about the notification features of the State and Notifications Broker, and how to register your application to be notified when a specific state value changes. The focus of this month's column is notification specialization. We’ll cover some of the more effective and powerful uses of the notification system such as notification filtering, efficient notification handling for rapidly changing state values, and automatic application start up.

Note

If you'd like a refresher on State and Notifications Broker basics, you may want to review The State and Notifications Broker API Part 1.

However, before we get into the State and Notifications Broker, I want to mention a change I've decided to make in the State and Notifications Broker series. As you know, I feel strongly that the State and Notifications Broker is a core part of the Windows Mobile 5.0 platform, and is something that many applications can use to more tightly interact with the Windows Mobile 5.0 platform. Given the State and Notifications Broker's importance, I've decided to expand our discussion about it from two columns to three. Efficient use of the State and Notifications Broker requires a solid understanding of the different ways that you can specialize and optimize notification, so this month we'll focus on just those issues. We’ll complete our coverage of the State and Notifications Broker in part 3 of this column.

Note

The State and Notifications Broker is part of the Windows Mobile platform and is equally available to both native and managed applications; therefore, just like in Part 1 of this column, our discussion and demonstrations this time will include both native and managed examples.

Specializing Notifications

You'll recall from Part 1 of this column that the State and Notifications Broker provides a central repository for system, device, user and application state information. Using the State and Notifications Broker API, applications can register to have the State and Notifications Broker send the application notification of changes to a particular state value. By default, the State and Notifications Broker sends a notification about every change to the state value; however, that many notifications may not be appropriate for all scenarios. In a scenario where an application may not need to receive notification about every state value change, the State and Notifications Broker enables an application to specify additional information to limit the number of notifications that it receives.

An application can specialize State and Notifications Broker notifications by making the notifications conditional or batch. Regarding conditional notifications, the State and Notifications Broker notifies the application about changes to a state value only when the new state value meets criteria that the application specifies. Batch notifications are used when a state value may go through a period of rapid change; during this period, the State and Notifications Broker waits for the state value to remain stable for some period of time before notifying the application about changes to the state value.

Conditional Notifications

Conditional Notification in Native Code

Conditional Notification in Managed Code

In some instances, an application is only interested in a state value when the state value meets certain criteria, such as when the state value contains a specific string, or when the state value crosses some threshold. Let's consider a simple C/C++ example.

In this example, we set up a notification to monitor the names of callers making incoming calls. In situations where the name of the caller making the incoming call contains the string "Mrs. Wilson," the application displays the text "Mommy's Calling" on the application's status bar.

Using only those State and Notifications Broker API features that we covered in Part 1 of this column, the following code example is an acceptable implementation of the conditional notification just described.

First, the application creates a notification that results in the State and Notifications Broker sending the custom message WM_REGISTRYNOTIFY to the application window anytime that the PhoneIncomingCallerName state value changes.

// Global Notification Handle
HREGNOTIFY g_ hPhoneIncomingCallerName = NULL;

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
  // ...
  // ...

  g_hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
    NULL, NULL, hInstance, NULL);
  ShowWindow(g_hWnd, nCmdShow);
  UpdateWindow(g_hWnd);

  // Register for changes in the incoming caller name
  HRESULT hr = RegistryNotifyWindow(SN_PHONEINCOMINGCALLERNAME_ROOT,
    SN_PHONEINCOMINGCALLERNAME_PATH, SN_PHONEINCOMINGCALLERNAME_VALUE,
    g_hWnd, WM_REGISTRYNOTIFY, 0, NULL, &g_hPhoneIncomingCallerName);

  return TRUE;
}

To process the notification, the application WndProc function includes the code to handle the custom message WM_REGISTRYNOTIFY. Each time the application receives this custom message, the application checks the current PhoneIncomingCallerName state value. When the PhoneIncomingCallerName state value contains the string "Mrs. Wilson," the application calls the application function UpdateAppStatusBar to display "Mommy's Calling" on the application status bar.

Note

The code that checks the current state value uses the _tcsstr function; therefore, the string only need contain the desired value. The string can have additional content as well.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  int wmId, wmEvent;

  switch (message) 
  {
    case WM_REGISTRYNOTIFY:
    {
      TCHAR callerName[256];
      RegistryGetString(SN_PHONEINCOMINGCALLERNAME_ROOT,
        SN_PHONEINCOMINGCALLERNAME_PATH, 
        SN_PHONEINCOMINGCALLERNAME_VALUE, callerName,
        sizeof(callerName)/sizeof(callerName[0]));

    if (NULL != _tcsstr(callerName, _T("Mrs. Wilson"))
      UpdateAppStatusBar(_T("Mommy's Calling"));

    break;
    }
    case WM_COMMAND:
      // ..
    case WM_PAINT:
      // ..
    case WM_CREATE:
      // ..
    case WM_DESTROY:
      // ..
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }

  return 0;
}

As you can see, implementing the desired behavior is easy. However, this implementation can also be very inefficient. For example: provided that I have a healthy relationship with my mother, I should receive more calls from the combination of coworkers, clients, friends and other family members than I receive from my mother. But if I use this implementation, the State and Notifications Broker will send far more notifications than necessary as it notifies the application of all incoming callers, rather than just the one caller that I'm interested in. Although the State and Notifications Broker is well engineered, sending and handling messages consumes resources such as CPU cycles, memory, and battery power. Therefore, a notification should only be sent if it contains a state value that is of interest.

A much better solution is for the State and Notifications Broker to only send notification of a change in the PhoneIncomingCallerName state value when the new value is something that the application will actually process. Fortunately, the State and Notifications Broker API makes this very easy through conditional notifications.

Conditional Notification in Native Code

When working in C\C++ you register for conditional notification by passing the NOTIFICATIONCONDITION structure to the RegistryNotifyWindow function. The NOTIFICATIONCONDITION structure enables you to specify both the target value against which you would like to compare the state value, and what kind of comparison that you would like to perform. You specify the kind of comparison that you would like to perform by using the REG_COMPARISONTYPE enumeration. Table 1 shows the list of REG_COMPARISONTYPE enumeration values.

Table 1. REG_COMPARISONTYPE enumeration members

Enumeration Description

REG_CT_EQUAL

The registry value is equal to the target value.

REG_CT_NOT_EQUAL

The registry value is not equal to the target value.

REG_CT_GREATER

The registry value is greater than the target value.

REG_CT_GREATER_OR_EQUAL

The registry value is greater than or is equal to the target value.

REG_CT_LESS

The registry value is less than the target value.

REG_CT_LESS_OR_EQUAL

The registry value is less than or is equal to the target value.

REG_CT_CONTAINS

The registry value contains the target value.

REG_CT_STARTS_WITH

The registry value starts with the target value.

REG_CT_ENDS_WITH

The registry value ends with the target value.

Note

All comparison enumeration values can be used with string state values and with DWORD state values, with the exception of REG_CT_CONTAINS, REG_CT_STARTS_WITH and REG_CT_ENDS_WITH, all of which can only be used with string state values. The State and Notifications Broker performs string comparisons by using the CompareString function. When performing greater-than and less-than comparisons, the CompareString function uses the sort order of the device's currently set default locale. String comparisons are all case insensitive.

Using the NOTIFICATIONCONDITION structure, you can instruct the State and Notifications Broker to only notify the application when the new PhoneIncomingCallerName state value contains the string "Mrs. Wilson".

The following code example modifies the call to the RegistryNotifyWindow function, which was shown in the first code example in the “Conditional Notifications” section, to use the NOTIFICATIONCONDITION structure to create the appropriate conditional notification.

                NOTIFICATIONCONDITION condition;
condition.ctComparisonType = REG_CT_CONTAINS;
condition.TargetValue.psz = _T("Mrs. Wilson");

HRESULT hResult = RegistryNotifyWindow(SN_PHONEINCOMINGCALLERNAME_ROOT, 
  SN_PHONEINCOMINGCALLERNAME_PATH, SN_PHONEINCOMINGCALLERNAME_VALUE, 
  g_hWnd, WM_REGISTRYNOTIFY, 0, &condition,
  &_hPhoneIncomingCallerName);

One thing to note about the NOTIFICATIONCONDITION structure is the TargetValue member, which is defined as a union. The union has two members, psz and dw, which correspond to the two State and Notifications Broker API notification data types, string and DWORD. Just as you would expect, the above example uses the psz member because the PhoneIncomingCallerName state value is a string.

With the State and Notifications Broker now sending notifications only when the incoming caller name contains the desired value of "Mrs. Wilson", the notification message handler code in the application WndProc function no longer needs to perform the condition check.

The following code example modifies the message handler code that was contained in the WndProc code example that was shown earlier to now handle the conditional notification.

    case WM_REGISTRYNOTIFY:
    {
      TCHAR callerName[256];
      RegistryGetString(SN_PHONEINCOMINGCALLERNAME_ROOT,
        SN_PHONEINCOMINGCALLERNAME_PATH, 
        SN_PHONEINCOMINGCALLERNAME_VALUE, callerName,
        sizeof(callerName)/sizeof(callerName[0]));
    // No longer required to check contents of state value
    UpdateAppStatusBar(_T("Mommy's Calling"));

    break;
    }

Although the conditional notification only slightly reduces the amount of code that is used to handle the notification, the conditional notification's effect is more far reaching than that. In the case of the unconditional notification, the State and Notifications Broker sends a notification on every incoming call. For someone who receives lots of phone calls, the State and Notifications Broker may send tens or even hundreds of notifications each day, with most of them being ignored by the application.

By using a conditional notification, the State and Notifications Broker sends a notification only when the state value meets the criteria that you specify. As a result, only a few notifications are sent, and each one is pertinent to the application. This reduction in notifications improves the performance of both the application and the device. In some situations, the use of conditional notifications can significantly increase the battery life as compared to sending a notification on every value change especially on frequently changing values.

As you'll recall from Part 1 of this column, not all state values can be used directly. Some DWORD state values require the use of bitmasks because the DWORD value that is stored in the registry may contain several distinct state values. In such situations, the state value that is of interest occupies only a portion of the bits of the DWORD value that is stored in the registry. To find the state value, you must use the bitwise-AND operator to apply the bitmask to the registry value. All DWORD state values have a bitmask that is defined in snapi.h. For those state values that are represented by the entire registry value, the bitmask is defined as -1 (0xFFFFFFFF) – all bits on.

Note   If you would like a refresher on the role of bitmasks in the State and Notifications Broker API, check out the section "Working with DWORD Values and Bitmasks in Native Code" in Part 1 of this column.

For conditional notifications of DWORD state values to work correctly, you must always assign the state value's bitmask to the dwMask member of the NOTIFICATIONCONDITION structure. This is true even when the state value occupies the entire DWORD state value in the registry because the State and Notifications Broker always performs the conditional test in two parts: first, the State and Notifications Broker determines the actual state value by performing a bitwise-AND operation between the DWORD value that is stored in the registry and the bitmask that is contained in the dwMask member. The condition is then tested by using the state value as determined by using the bitwise-AND operator.

The following example shows how to register for a notification when the device's main battery reaches a critical state.

NOTIFICATIONCONDITION condition;
condition.ctComparisonType = REG_CT_EQUAL;
condition.dwMask = SN_POWERBATTERYSTATE_BITMASK;
condition.TargetValue.dw = BATTERYSTATE_CRITICAL;

HRESULT hResult = RegistryNotifyWindow(SN_POWERBATTERYSTATE_ROOT, 
  SN_POWERBATTERYSTATE_PATH, SN_POWERBATTERYSTATE_VALUE, 
  g_hWnd, WM_BATTERYSTATECRITICAL, 0, &condition, 
  &g_hBatteryStateCritical);

Note

The constant value BATTERYSTATE_CRITICAL is borrowed from the bitmask example code in Part 1 of this column. BATTERYSTATE_CRITICAL is defined as the value 8 which is the documented state value for a critical battery state.

Conditional Notification in Managed Code

As you'll recall from Part 1 of this column, notification handling when using the managed State and Notifications Broker API is an extension of the .NET delegate/event model. Your application need only create an instance of the SystemState class, pass the appropriate SystemProperty enumeration for the state value of interest and then associate a delegate with the SystemState instance's Change event. The application function associated with the Change event is then called each time the state value associated with the SystemState instance changes.

Note

If you would like a refresher on the managed State and Notifications Broker API's notification handling see the section “Receiving Notifications in Managed Code” in Part 1 of this column.

All of the information about raising state change notifications is part of the SystemState instance. To make the change events raised by the SystemState instance conditional, set the ComparisonType property and the ComparisonValue property of the SystemState instance.

The ComparisonType property accepts the StatusComparisonType enumeration, the values of which correspond to the values of the C\C++ enumeration REG_COMPARISONTYPE. Table 2 lists and describes the StatusComparisonType enumeration values.

Table 2. StatusComparisonType enumeration members

Enumeration Description

Equal

The registry value is equal to the target value.

NotEqual

The registry value is not equal to the target value.

Greater

The registry value is greater than the target value.

GreaterOrEqual

The registry value is greater than or equal to the target value.

Less

The registry value is less than the target value.

LessOrEqual

The registry value is less than or equal to the target value.

Contains

The registry value contains the target value.

StartsWith

The registry value starts with the target value.

EndsWith

The registry value ends with the target value.

AnyChange

No comparison is performed, the notification is sent with every change to the registry value. This is the default value of the SystemState.ComparisonType property.

Just like in the earlier native example code, the following code example also demonstrates how to use managed code to create a conditional notification that enables the State and Notifications Broker to only notify the application of a change in the PhoneIncomingCallerName state value when the incoming caller name contains "Mrs. Wilson".

SystemState _incomingCallerName;

public Form1()
{
  InitializeComponent();

  _incomingCallerName = 
    new SystemState(SystemProperty.PhoneIncomingCallerName);
  _incomingCallerName.ComparisonType = StatusComparisonType.Contains;
  _incomingCallerName.ComparisonValue = "Mrs. Wilson";
  _incomingCallerName.Changed += 
    new ChangeEventHandler(IncomingCallerName_Changed);
}

void IncomingCallerName_Changed(object sender, ChangeEventArgs args)
{
  messageStatusBar.Text = "Mommy's Calling";
}

One thing to remember when creating conditional notifications with the SystemState class is that you must set the ComparisonType property and the ComparisonValue properties before you attach a delegate to the Changed event. If you try to set either of these properties after you have attached a delegate to the Changed event, they will throw a StateException exception.

As you know, the managed State and Notifications Broker API ultimately uses the native API to interact with the State and Notifications Broker. This doesn't mean that every action that is performed on a managed class such as the SystemState class results in a call to the native API; rather, the managed API creates an abstraction and calls the native API at the appropriate times. One such situation where the SystemState class calls the native API is when the delegate is attached to the Changed event.

Before you attach a delegate to the SystemState.Changed event, the SystemState instance is simply a managed object; it makes no calls to the native State and Notifications Broker API. But when you attach a delegate to the Changed event, the SystemState class creates the actual notification by calling the native RegistryNotifyWindow function. Once the call is made to the RegistryNotifyWindow function, the SystemState instance is directly associated with the newly created notification. Just as in the case of the native API, once you create a notification, you cannot change or add a condition to the notification; instead, you must create a new notification which, in the managed API, requires a new instance of the SystemState class.

As you'll recall from Part 1 of this column, the SystemState class hides the fact that the use of a bitmask is required to access some values. This same encapsulation is true when performing conditional notifications; therefore, bitmasks require no special handling.

Note

The SystemState class exposes a BitMask property but it is not used. This BitMask property is an artifact of the StateBase class, which is the base class of the SystemState class. Any attempt to set or to access the SystemState.Bitmask property will throw a NotSupportedException exception.

With the bitmask details encapsulated in the SystemState class, the managed implementation of the critical battery state notification, shown earlier in C\C++, is basically the same as the managed implementation of the PhoneIncomingCallerName state value above. The following code example shows you how to set up a battery state notification so that it’s only sent when the battery reaches a critical state.

SystemState _batteryState;

void SetupCriticalBatteryStateNotification()
{
  _batteryState = new SystemState(SystemProperty.PowerBatteryState);
  _batteryState.ComparisonType = StatusComparisonType.Equal;
  _batteryState.ComparisonValue = BatteryState.Critical;
  _batteryState.Changed += 
    new ChangeEventHandler(BatteryStateCritical);
}

void BatteryStateCritical(object sender, ChangeEventArgs args)
{
  // Turn off unneeded features
}

Batch Notifications

Batch Notification in Native Code

Batch Notification in Managed Code

In situations where a state value may change frequently, you can reduce the number of notifications that are sent to your application by creating a batch notification. Batch notifications are useful in situations where a state value may experience bursts of rapid change. In such situations, attempting to handle each message may not be useful as the state value may have already changed again before you finish processing the prior state value. Even if your application can process the notifications as fast as the state values are changing, it still may not be useful to expend CPU and battery resources on a state value that will soon change.

A batch notification is one that waits for a state value to stabilize before being sent. Obviously, the amount of time that a state value must remain unchanged in order to be considered stable differs from one application to the next; therefore, when you create a batch notification, you pass in an idle time value that is expressed in milliseconds. The State and Notifications Broker keeps track of how much idle time passes since the value last changed. If the state value remains stable for the specified idle time, the State and Notifications Broker notifies your application.

The idle time value works well in situations where you know that the state value will stabilize with reasonable frequency. However, there are also situations where the state value may continue to rapidly change for long periods of time. For these situations, batch notifications accept a maximum delay, which is also expressed in milliseconds. A maximum delay insures that your application still receives regular notification of changes in the state value even if the state value continually changes without pause. If your application needs to be notified only when the state value stabilizes, pass the constant INFINITE (0xFFFFFFFFL) for the maximum delay value.

Batch notification and conditional notification are not mutually exclusive. You can create a conditional notification to conditionally filter a notification and make that notification a batch notification that requires that the state value remain stable for a specified period of time.

Batch Notification in Native Code

Unlike a conditional notification in which the conditional criteria are provided to the notification at the time that you create the notification, a batch notification operates on an existing notification. You create a notification just as you normally would, by using the RegistryNotifyWindow structure, with or without a condition associated. Then, you make that notification a batch notification by passing the notification handle to the RegistryBatchNotification function.

Again unlike conditional notifications, in which the condition that is associated with a notification cannot be changed, you can change the idle time and the maximum delay time for batch notifications whenever you want. This enables you to modify the notification timing based on any number of factors, including user input, battery power, other activity on the device, and so on.

The following code example demonstrates how to create a batch notification for the Microsoft® Windows Media® Player Mobile track title state value.

HREGNOTIFY g_hTrackTitle;
const DWORD idleTime = 3000;
const DWORD maxDelay = 10000;
// Create initial notification
HRESULT hResult = RegistryNotifyWindow(SN_MEDIAPLAYERTRACKTITLE_ROOT, 
  SN_MEDIAPLAYERTRACKTITLE_PATH, SN_MEDIAPLAYERTRACKTITLE_VALUE, 
  g_hWnd, WM_TRACKTITLECHANGE, 0, NULL, 
  &g_hTrackTitle); 
// Now make it a batch notification
if (SUCCEEDED(hResult))
  RegistryBatchNotification(g_hTrackTitle, idleTime, maxDelay);

As you can see, the notification is created normally, and is then made a batch notification that requires that the track title remain constant for three seconds, in order for the State and Notifications Broker to notify the application about the track title change. In the event that the track title keeps changing for a long period of time without being stable for at least three seconds, a notification is still sent every 10 seconds.

Batch Notification in Managed Code

The managed State and Notifications Broker API does not provide a way to create batch notifications. I found this a little disappointing at first. Managed applications handle notifications by calling a delegate, which has a higher overhead as compared to a native application, which simply handles window messages. Given the cost of calling a delegate, it's important that managed applications avoid processing notifications unnecessarily, especially in situations where state values are rapidly changing.

But there is no need to worry about the fact that the managed API does not explicitly support batch notifications—where there's a will, there's a way. After spending some time spelunking through the Microsoft.WindowsMobile.Status assembly, I found that with a little help from reflection and platform invoke, it is relatively easy to modify notifications that are created using the SystemState class to be batch notifications.

As we just discussed in the “Batch Notification in Native Code” section, the RegistryBatchNotification function needs the notification handle of an existing notification. Because the managed State and Notifications Broker API ultimately uses the native API, the SystemState class must store the handle that is returned by the SystemState class' call to the RegistryNotifyWindow function. Using reflection, you can access the stored notification handle but accessing the handle is a bit indirect.

The SystemState class is primarily a simplified wrapper over another class, called RegistryState, that is also in the Microsoft.WindowsMobile.Status namespace. We'll talk about the RegistryState class in the Part 3 of this paper. For now, though, all we need to understand is that the SystemState class contains an instance of the RegistryState class, and that the RegistryState class actually does the work of calling the native State and Notifications Broker API.

Using tools like the MSIL Disassembler (Ildasm.exe) or the .NET Reflector, we can look in the Microsoft.WindowsMobile.Status assembly and see that the SystemState class stores the RegistryState instance in a private field that is named registryState. We can then look at the RegistryState class and see that the RegistryState class stores, in a private field that is named notifyHandle, the notification handle that is returned by the native RegistryNotifyWindow function. We can now write a simple function that uses reflection to retrieve the notification handle for a SystemState instance. This function is shown in the following code example.

IntPtr GetSystemStateHandle(SystemState notification)
{
  // Get type information for SystemState and for RegistryState
  Type ssType = typeof(SystemState);
  Type regStateType = typeof(RegistryState);

  // Get field information for SystemState.registryState
  FieldInfo registryStateField =
    ssType.GetField("registryState",
    BindingFlags.Instance | BindingFlags.NonPublic);

  // Get field information for RegistryState.notifyHandle
  FieldInfo handleField = regStateType.GetField("notifyHandle",
    BindingFlags.Instance | BindingFlags.NonPublic);

  // Get the RegistryState instance from the SystemState instance
  object regStateInstance = registryStateField.GetValue(notification);
  // Get the notification handle from the RegistryState instance
  object handleInstance = handleField.GetValue(regStateInstance);

  // Return the notification handle
  return (IntPtr)handleInstance;

}

Using basic reflection features, the GetSystemStateHandle function that is shown in the above code example first gets the Type information for both the SystemState class and for the RegistryState class. The function then retrieves the RegistryState instance, which is named registryState, from the SystemState instance. Next, the GetSystemStateHandle function retrieves the notification handle, which is named notifyHandle, from the RegistryState instance.

The following example code demonstrates how to use the GetSystemStateHandle function and the native RegistryBatchNotification function to create a new function that makes the notification that is associated with the SystemState instance a batch notification.

[DllImport("ossvcs.dll", EntryPoint = "#343" SetLastError = true)]
public static extern int RegistryBatchNotification(IntPtr hNotify, 
   int dwIdle, int dwMax);

void SetupBatchNotification(SystemState notification, int idleTimeout, int maxTimeout)
{
  int hResult;
  
  IntPtr handle = GetSystemStateHandle(notification);
  hResult = RegistryBatchNotification(handle, idleTimeout, maxTimeout);
  if (0 != hResult)
  {
    throw new ApplicationException(
      "RegistryBatchNotification Win32 Error = " + 
      Marshal.GetLastWin32Error().ToString());
  }
}

As you can see, making the notification that is associated with a SystemState instance a batch notification is now pretty simple. You simply retrieve the native notification handle by using the GetSystemStateHandle function, and then call the native RegistryBatchNotification function.

You can now easily make any SystemState notification a batch notification, as shown in the following code example.

public partial class Form1 : Form
{
  SystemState _trackTitle;
  const int batchIdleTimeout = 3000;
  const int maxIdleTimeout = 10000;

  public Form1()
  {
    InitializeComponent();
  
    _trackTitle = 
       new SystemState(SystemProperty.MediaPlayerTrackTitle);
    _trackTitle.Changed += new ChangeEventHandler(TrackTitle_Changed);

    SetupBatchNotification(_trackTitle, batchIdleTimeout,
       maxIdleTimeout);
  }

  //...
}

As you can see in this code example, you simply create the SystemState instance as normal, and attach a delegate to the Changed event. Next, you make the SystemState instance a batch notification by calling the SetupBatchNotification function, passing the SystemState instance and the timeout values.

This code works well, but you must exercise caution.

  • You must call SetupBatchNotification after you attach a delegate to the Changed event. As mentioned earlier in this article, the notification handle does not exist until you attach a delegate to the Changed event.
  • Add proper error handling and recovery code to the GetSystemStateHandle function and the SetupBatchNotification function before you use them.
  • As with all reflection code, the reflection code in the GetSystemStateHandle function which extracts the notification handle from the SystemState instance has a relatively high overhead. Therefore, you should avoid calling the GetSystemStateHandle function multiple times for the same SystemState instance. Instead of retrieving the notification handle multiple times, cache it.

Persistent Notifications

Up until now, we've made the assumption that notification handling is something that begins and ends with the execution of a particular program. This type of notification is known as a transient notification. Although transient notifications are often appropriate, you may want notification handling to outlive a given program, or to begin when the device is turned on and continue indefinitely.

Fortunately, the State and Notifications Broker provides a reliable solution to this problem via what are called persistent notifications. With a persistent notification, the State and Notifications Broker guarantees that the program that is associated with the notification is running and receives the notification anytime there is a change in the state value.

When there is a change in the state value, the State and Notifications Broker checks to see that the associated program is running. When the associated program is running, the State and Notifications Broker sends the notification message to the program's registered window, just as it does for transient notifications. When the program associated with the persistent notification is not running, the State and Notifications Broker automatically launches the program, then sends the notification message to the program's registered window once the program starts.

In addition to assuring that the program that is associated with a persistent notification is running and notified when the state value changes, the State and Notifications Broker also assures that persistent notifications are truly persistent, and that they remain until they are explicitly removed. A persistent notification remains even after you soft-reset the device or the device's battery charge is exhausted.

All persistent notifications are globally available to any program on the device and each notification is identified by a unique name known as an application identifier (often called an applicationId, or appId). One way to insure the uniqueness of each appId on the device is to use a globally unique identifier (GUID) which by definition is unique.

As you know, a GUID is a 32-bit hexadecimal value that is formatted like the following example:

{708CA6CE-5365-4511-9496-653B692DF2F9}

Although implicitly unique, GUIDs are not always a good choice for persistent notifications’ appId because GUIDs are difficult for people to read and remember. To make persistent notifications a little easier for people to identify, the State and Notifications Broker API documentation recommends that you use a string value that is made up of the application name and the State and Notifications Broker value name separated by a period, for example ExampleApp.MediaPlayerTrackTitle.

This appId value is definitely easier to read, identify and remember than a GUID. My only concern with using this appId value is that the inclusion of only the program name and value names doesn't insure that the appId value is unique; there is a possibility, however small, that another organization might create a program with the same name as your program, and that both programs are interested in the same state value thereby creating an appId collision. To avoid the possibility of an appId collision, add the name of your company to the beginning of the appId value. By making this change, the original appId value of ExampleApp.MediaPlayerTrackTitle is now CompanyA.ExampleApp.MediaPlayerTrackTitle, and has little chance of conflicting with another appId.

Note

If you're an enterprise developer or a corporate developer creating software for use within the company where you work, the company name may not be enough to insure uniqueness if multiple departments within your company also create software for internal use. In this case, you might want to also add the department name to the appId value, giving you an appId value that would look like the following: CompanyA.MyDepartment.ExampleApp.MediaPlayerTrackTitle.

When launching a program, the State and Notifications Broker passes two command-line arguments to the program: /notify, followed by the notification appId. These command-line arguments provide the information that is necessary for the program to differentiate between a user starting the program normally, and the State and Notifications Broker launching the program as part of a notification. For programs that are associated with multiple persistent notifications, these command-line arguments also allow the program to immediately determine which notification has caused the State and Notifications Broker to launch the program.

Creating Persistent Notifications in Native Code

To set up a persistent notification in native code, use the RegistryNotifyApp function. This function is very similar to the RegistryNotifyWindow function; the difference is that the RegistryNotifyApp function accepts the path of the executable file to launch and the class and title (frequently known as the name) of the notification target window, both of which are necessary to locate the appropriate window. The window class and target values that you pass to the RegistryNotifyApp function are the same window class and title values that you pass to the CreateWindow function to create the actual window instance. With the program's executable file path, window class, and window title, the RegistryNotifyApp function provides the State and Notifications Broker with all of the information that is necessary to launch the program, locate its target window, and send notification messages to the target window.

The following code example demonstrates creating a persistent notification with RegistryNotifyApp.

const DWORD WM_TRACKTITLECHANGE = WM_APP + 1;

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
  // ...
  // ...
  // Get window class name and title from application string table
  LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 
  LoadString(hInstance, IDC_EXAMPLEAPP, szWindowClass,
    MAX_LOADSTRING);
  // ...
  // ...
  // Create the Window specifying window class name and title 
  g_hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
    NULL, NULL, hInstance, NULL);
  ShowWindow(g_hWnd, nCmdShow);
  UpdateWindow(g_hWnd);

  const TCHAR *pszAppName = 
    _T("\"\\Program Files\\ExampleApp\\ExampleApp.exe\"");
  const TCHAR *pszAppId =
    _T("CompanyA.ExampleApp.MediaPlayerTrackTitle");

hResult = RegistryNotifyApp(SN_MEDIAPLAYERTRACKTITLE_ROOT,
  SN_MEDIAPLAYERTRACKTITLE_PATH, SN_MEDIAPLAYERTRACKTITLE_VALUE,
  pszAppId, pszAppName, szWindowClass, szTitle,
  WM_TRACKTITLECHANGE, 0, NULL);

  return TRUE;
}

As you can see, this code example creates a persistent notification for changes to the Windows Media Player Track Title state value. You can create the notification anywhere in the application, but in general you should call the RegistryNotifyApp function shortly after the window is created so that the application can begin receiving notifications as soon as possible. In the above code example, the registration is performed in the InitInstance function immediately after the window is created and displayed.

Like the RegistryNotifyWindow function, the call to the RegistryNotifyApp function starts with the registry root, path and state value to monitor for changes. The next four parameters to the RegistryNotifyApp function identify the appId, the path of the program executable file, and the class and title of the window to receive the notification messages. As you may know, in native projects that are generated by Microsoft Visual Studio 2005, the window class name and title are stored in the application string table, which is the reason for the two calls to the LoadString function in the above example code. By default, the window class name is identical to the name of the Visual Studio 2005 project, except with all of the characters capitalized (for example, EXAMPLEAPP). By default, the window title is identical to the Visual Studio 2005 project name exactly as it appears in the Visual Studio 2005 Solution Explorer (for example, ExampleApp).

Note

The string that contains the executable file path for the application follows the same formatting rules as an executable file path that is entered on the Windows command line: if the executable file path contains spaces, the executable file path must be in quotation marks. For an example, see the pszAppName constant value in the above code example. If you wish to pass arguments to the application, include the arguments in the executable file path string just as you would on the Windows command line: the arguments must appear after the file name, and there must be at least one space both after the end of the executable file name and between each argument, as shown in the following example executable file path string: T(""\Program Files\ExampleApp\ExampleApp.exe" arg1 arg2") Also, note that the RegistryNotifyApp function does not verify that the executable path is valid; therefore the RegistryNotifyApp function will return with a success code even if the executable file path doesn't exist or contains illegal characters. If the executable file path is missing or invalid, the State and Notifications Broker silently fails, never starting the application, and never sending a notification.

The remaining three parameters are the window message to send to the program's target window; a flag to specify the behavior of the RegistryNotifyApp function; and, a reference to the NOTIFICATIONCONDITION structure.

The only valid values that you can pass to the flag parameter are zero and RNAF_NONAMEONCMDLINE. Zero indicates that no special behavior is requested. RNAF_NONAMEONCMDLINE indicates that the State and Notifications Broker should not include /notify appId on the command line when launching the program. Just as in the RegistryNotifyWindow function, passing a reference to a NOTIFICATIONCONDITION structure creates a conditional notification.

In the above code example, the message is an application-defined message, WM_TRACKTITLECHANGE. Zero is passed as the flag value, indicating that no special behavior is requested. A NULL is passed as the last argument, indicating that the notification is non-conditional.

Creating Persistent Notifications in Managed Code

As you've seen throughout our discussion of the State and Notifications Broker API, the managed implementation tends to hide many of the details that exist in the native implementation. This fact remains true for persistent notifications.

Creating a persistent notification in managed code is very much like creating a transient notification. First, create the SystemState instance and pass the appropriate SystemProperty enumeration, just as you would if you were creating a transient notification. To make the notification persistent, simply call the EnableApplicationLauncher method passing the persistent notification appId as shown in the following example code.

SystemState _trackTitle;

private void Form1_Load(object sender, EventArgs e)
{
  const string appId = "CompanyA.ExampleApp.MediaPlayerTrackTitle";
  _trackTitle = new SystemState(SystemProperty.MediaPlayerTrackTitle);
  _trackTitle.EnableApplicationLauncher(appId);

  _trackTitle.Changed += new ChangeEventHandler(TrackTitle_Changed);
}

The above code example creates a persistent notification, with an appId of CompanyA.ExampleApp.MediaPlayerTrackTitle, that notifies the current application anytime the Windows Media Player Mobile track title changes. If the track title changes when the program isn't running, the State and Notifications Broker automatically starts the program.

The EnableApplicationLauncher method provides three overloads. The overload that is used in the above code example creates a persistent notification, and stores the executable file path of the current program with the persistent notification.

The other two EnableApplicationLauncher overloads allow you to specify the executable file path of the program to store with the persistent notification. One of these versions also accepts a string value that contains the list of command-line arguments to pass when the State and Notifications Broker launches the program.

The following example code demonstrates how to use the version of the EnableApplicationLauncher function that accepts the appId value and an executable file path.

SystemState _trackTitle;

private void EnableMediaPlayerTrackTitleLogging()
{
  const string appId = "CompanyA.MediaPlayerLog.MediaPlayerTrackTitle";
  const string applicationToNotify =
    @"\Program Files\MediaPlayerLog\MediaPlayerLog.exe";

  _trackTitle = new SystemState(SystemProperty.MediaPlayerTrackTitle);
  _trackTitle.EnableApplicationLauncher(appId, applicationToNotify);

}

The above code example creates a persistent notification, with an appId value of CompanyA.MediaPlayerLog.MediaPlayerTrackTitle, that will automatically launch the MediaPlayerLog.exe program anytime the Windows Media Player Mobile track title changes.

Look at this code example carefully; note that the code contains several differences from earlier examples. First, unlike the native persistent notification code example (see the note in the Creating Persistent Notifications in Native Code section), the executable file path is not enclosed in quotes (other than those required by the C# compiler). The executable file path in this code example does not require quotes because the EnableApplicationLauncher method automatically adds any necessary quotes before storing the file path with the persistent notification.

The other notable difference is that unlike all of the earlier managed examples, the application in this code example doesn't associate a delegate with the SystemState instance (in this case,_trackTitle). Instead, the program creates a persistent notification that notifies a program other than itself of state value changes. With no notification associated with the calling program, there is no reason for the program to create a delegate. The calling program's responsibility ends with creating the notification. The responsibility to handle the notification is with the program associated with the notification which in this case is MediaPlayerLog.exe).

Persistent Notification Architecture

Storing a Persistent Notification

Sending and Receiving Notifications

Persistent notifications require a little more housekeeping than transient notifications, due to persistent notifications being persistently stored on the device and the State and Notifications Broker sometimes being required to launch an application in order to send the persistent notification. The following two sections discuss those necessities.

Storing a Persistent Notification

Conceptually, the State and Notifications Broker's responsibilities are fairly simple. For both persistent notifications and transient notifications, the State and Notifications Broker keeps a list of programs that have registered interest in a state value; when a state value changes, the State and Notifications Broker notifies the appropriate programs.

In order to reliably meet these responsibilities, the list of persistent notifications is stored in the device's registry, where it won't be destroyed if the device is reset or if the battery exhausts its charge. The registry, like all of the Windows Mobile 5.0 file system, is stored in non-volatile memory on the device, and therefore does not need power to maintain the memory contents.

The State and Notifications Broker stores all of the persistent notifications in the registry under the HKEY_LOCAL_MACHINE\System\Notifications registry key. For each persistent notification, the State and Notifications Broker creates a key that has the same name as the notification appId value. The State and Notifications Broker stores the remaining information for the persistent notification as named values on the newly created key. For each persistent notification, the registry structure is basically a registry representation of the parameters that one passes to the RegistryNotifyApp function.

As an example of a registry key, figure 1 shows the registry entries that are created by the following code example.

                  const TCHAR *pszAppName = 
    _T("\"\\Program Files\\ExampleApp\\ExampleApp.exe\"");
  const TCHAR *pszNotificationName =
    _T("CompanyA.ExampleApp.MediaPlayerTrackTitle");

hResult = RegistryNotifyApp(SN_MEDIAPLAYERTRACKTITLE_ROOT,
  SN_MEDIAPLAYERTRACKTITLE_PATH, SN_MEDIAPLAYERTRACKTITLE_VALUE,
  pszNotificationName, pszAppName, szWindowClass, szTitle,
  WM_TRACKTITLECHANGE, 0, NULL);

Figure 1. Registry entry for a persistent notification

As you can see, the appId value and corresponding values are all stored unchanged in the registry. Table 3 shows the relationship between each registry value and the corresponding parameter.

Table 3. The relationship between notification values in the registry and the parameters passed to the RegistryNotifyApp function

Registry Value Program Value

HKEY: 21474483649

SN_MEDIAPLAYERTRACKTITLE_ROOT

  • In this case, HKEY_CURRENT_USER, which has a value of 0x80000001 (21474483649 decimal)

Key: \System\State\MediaPlayer

SN_MEDIAPLAYERTRACKTITLE_PATH

Value Name: Title

SN_MEDIAPLAYERTRACKTITLE_VALUE

Application: \Program Files\ExampleApp\ExampleApp.exe

pszAppName program constant

Class Name: EXAMPLEAPP

szWindowClass program variable

Window Name: ExampleApp

szTitle program variable

Message: 32769

WM_TRACKTITLECHANGE

  • The program constant that is set to WM_APP +1, which results in the value 0x8001 (32769 decimal).

Flags: 0

The value that is passed to the dwFlags parameter, which in this case is zero.

  • The only valid non-zero value for this parameter is RNAF_NONAMEONCMDLINE (0x00000001).

With all of the information related to persistent notifications stored in the registry, the State and Notifications Broker simply reads the list anytime that the device is restarted.

Sending and Receiving Notifications

When a state value that is associated with a persistent notification changes, the State and Notifications Broker must determine whether to send a notification message to the application target window, or launch the application and then send the notification message. The decision depends upon whether the program that is associated with the notification is running at the time that the state value changes. To determine if the program is running, the State and Notifications Broker attempts to find a window with the class and the title registered in the call to the RegistryNotifyApp function. If the State and Notifications Broker locates the window, it sends the specified notification message to the window and takes no further action.

If the State and Notifications Broker is not able to find the window with the specified class and title, the State and Notifications Broker assumes that the application is not running, and therefore launches the registered executable. When starting the application, the State and Notifications Broker checks the persistent notification's flag value; and as long as the persistent notification flag's value is not RNAF_NONAMEONCMDLINE, the State and Notifications Broker passes the program the two command-line arguments: /notify and the appId. Once the application starts running, the State and Notifications Broker again attempts to locate the window that has the appropriate class and title. The State and Notifications Broker continues trying once each second until it either finds the window or 10 seconds elapse. If the State and Notifications Broker finds the window before 10 seconds elapse, the State and Notifications Broker sends the notification message to the window; otherwise, the State and Notifications Broker takes no further action to notify the application.

Note

You should not write your applications to depend on the frequency and duration that the State and Notifications Broker uses to locate the application window. Future updates and/or versions of the Windows Mobile platform may use different time values or may even change the algorithm altogether.

Native programs do not normally need any special handling for a notification that requires the State and Notifications Broker to start the program. In native programs, notification messages are normally handled in the main program window's WndProc function with the same logic used to handle both persistent notifications and transient notifications. The main program window is created very early in the program's execution and is therefore in place to handle the notification message that is sent by the State and Notifications Broker before the State and Notifications Broker’s search for the window times out.

Managed programs, however, do need to provide special handling for persistent notifications. This special handling is necessary because of two issues: managed programs tend to require a longer startup time than native programs and managed programs must create SystemState instances to handle persistent notifications differently then when handling transient notifications.

The longer startup time required by managed applications is because most of the standard program startup behavior is handled by the .NET Compact Framework runtime; therefore, your managed program doesn't have an opportunity to create the SystemState instance for a particular notification, and attach a delegate to the SystemState.Changed event, until very late in the managed program's startup process. To ensure that your managed application receives the notification event, create the SystemState instance, and attach a delegate to the SystemState.Changed event as early in your application's startup process as you can. In most programs this will be either the application's main form constructor or in the Program.Main function.

Note

On some desktop computers, the Device Emulator exhibits notably slower performance than a real Windows Mobile device. This is especially true if you are running the Device Emulator within a Virtual PC image. If you find that the Device Emulator is taking more than 10 seconds to start your managed application, the application may not have an opportunity to handle the initial persistent notification that is sent by the State and Notifications Broker after launching the program. The only solutions to the Device Emulator's slow performance are either to use a desktop computer that provides better Virtual PC or Device Emulator performance, or to use a real device. Currently Virtual PC does not support USB connections therefore using a real device is only an option if you’re working directly on your desktop computer outside of a virtual environment or you are using a virtual machine product that provides USB support.

Handling a persistent notification in a managed program is very much like handling a transient notification except that you pass the persistent notification’s appId to the SystemState class constructor rather than pass a member of the SystemProperty enumeration. The SystemState instance uses the passed appId to read the persistent notification's information from the registry. After you construct the SystemState instance, you attach a delegate to the SystemState.Changed event just as you do for transient notifications.

Unlike native applications which, when creating a persistent notification, give you a choice of whether to have the State and Notifications Broker send the appId on the command line when starting the application, a persistent notification that is created by a managed application always creates the persistent notification so that the State and Notifications Broker does not send the appId on the command line. Instead, the SystemState class provides the static IsApplicationLauncherEnabled function which tests to see if the persistent notification that is identified by a particular appId is already defined.

Most commonly, a managed program uses the IsApplicationLauncherEnabled function to determine whether the application should create a new persistent notification or just start handling an existing persistent notification. This behavior is demonstrated in the following code example.

SystemState _mediaPlayerTitle;
const string appId = "CompanyA.ExampleApp.MediaPlayerTrackTitle";

public Form1()
{
  InitializeComponent();

  if (SystemState.IsApplicationLauncherEnabled(appId))
  {
    // Handle existing persistent notification
    _mediaPlayerTitle = new SystemState(appId);
  }
  else
  {
    // Create a new persistent notification
    _mediaPlayerTitle = 
        new SystemState(SystemProperty.MediaPlayerTrackTitle);
    _mediaPlayerTitle.EnableApplicationLauncher(appId);
  }

  // Attach delegate
  _mediaPlayerTitle.Changed += 
      new ChangeEventHandler(MediaPlayerTitle_Changed);
}

The program shown in the above code example starts by declaring a string constant named appId to hold the persistent notification's appId value, CompanyA.ExampleApp.MediaPlayerTrackTitle. The program then uses the IsApplicationLauncherEnabled function to check whether the persistent notification with the appId has already been created. If the IsApplicationLauncherEnabled function returns true, the program creates a new SystemState instance passing the appId. In this case, the program creates the SystemState instance to handle the existing persistent notification. If the IsApplicationLauncherEnabled function returns false, the program creates a new SystemState instance passing the SystemProperty enumeration value that indicates that the notification is associated with the Windows Media Player Mobile track title state value. The program then creates a new persistent notification with the appropriate appId by calling the EnableApplicationLauncher function on the SystemState instance just created. In both cases, once the SystemState instance is created the Changed event fires each time the Windows Media Player Mobile track title changes.

So far in this section, we've discussed the basics of persistent notifications and program startup, there are also some other more involved situations that are useful to understand but we'll save those for Part 3 of this paper.

Persistent Notification and Application Installation

As we've already discussed, persistent notifications are stored in the registry. Being stored in the registry, we can view, create and modify these entries by using standard tools such as the Remote Registry Editor (RegEdit) or .cab files. With such a direct relationship between the registry values and the parameters passed to the RegistryNotifyApp function, you can see how easy it is to create a persistent notification by simply adding the appropriate registry values. In most situations, bypassing the RegistryNotifyApp function and modifying the registry directly is not desirable; the one exception is during program installation.

If you are creating a CAB file to install a program that you wish to have associated with a persistent notification, one way to create the persistent notification is programmatically. To do this, you create a C\C++ setup DLL, and associate the setup DLL file with the CAB file. Within the setup DLL, you must handle one of the install events, and within that event call the RegistryNotifyApp function to create the persistent notification. You also must handle one of the uninstall events, and within that event call the StopNotification function to remove the notification.

A much easier way to create the persistent notification as part of a CAB file installation is to take advantage of a CAB file's ability to add registry entries. Figure 2 shows the registry view of a Smart Device CAB project to install the ExampleApp program, and to create a persistent notification with the same settings as the persistent notification that was created by using the RegistryNotifyApp function in the code example in the “Storing a Persistent Notification” section.

Figure 2. Persistent notification registry entries in a Visual Studio 2005 Smart Device CAB Project

When this Smart Device CAB Project is built, the information that is required to create the set of registry entries that are shown back in figure 1 is included in the CAB file. These registry entries are created as part of the CAB file's regular installation behavior, and are automatically removed if the CAB file is uninstalled from the device.

Note

The value for the HKEY registry value that is shown in figure 2 is -2147483647, whereas the value shown in figure 1 for the same HKEY registry value is 2147483649. This apparent discrepancy is due to the different ways that Visual Studio 2005 and the RegEdit utility display registry DWORD values. Visual Studio 2005 displays DWORD values as signed integers, whereas the RegEdit utility displays DWORD values as unsigned integers. Both of these values are expressed as 0x80000000 in hexadecimal, which is the correct value for HKEY_CURRENT_USER. When entering an HKEY value into the Registry view of a Visual Studio 2005 Smart Device CAB Project, you need to enter the value as either a hexadecimal value or as a signed integer value. If you enter the value as an unsigned integer, (which is how it appears in the RegEdit utility), Visual Studio 2005 will report that the value is out of range. Table 4 shows the four valid HKEY names and their corresponding numeric representations.

Table 4. Registry root values and the corresponding numeric representations

Name Hexadecimal Unsigned Decimal (RegEdit Format) Signed Decimal (VS2005 Format)

HKEY_CLASSES_ROOT

0x80000000

2147483648

-2147483648

HKEY_CURRENT_USER

0x80000001

2147483649

-2147483647

HEKY_LOCAL_MACHINE

0x80000002

2147483650

-2147483646

HKEY_USERS

0x80000003

2147483651

-2147483645

If you create CAB files by manually creating the *.inf files or if you use some mechanism other then Visual Studio 2005 to create CAB files, the following example *.inf file shows the CAB directives to create the same registry entries as the Smart Device CAB project shown in figure 2.

[Version]
Signature="$Windows NT$"
Provider="CompanyA"
CESignature="$Windows CE$"

[CEStrings]
AppName="ExampleAppInstall"
InstallDir=%CE1%\%AppName%

...
...

[RegKeys]
"HKLM","System\Notifications\CompanyA.ExampleApp.MediaPlayerTrackTitle","Trust","0x00010001","1"
"HKLM","System\Notifications\CompanyA.ExampleApp.MediaPlayerTrackTitle","Flags","0x00010001","1"
"HKLM","System\Notifications\CompanyA.ExampleApp.MediaPlayerTrackTitle","Class Name","0x00000000","EXAMPLEAPP"
"HKLM","System\Notifications\CompanyA.ExampleApp.MediaPlayerTrackTitle","Window Name","0x00000000","ExampleApp"
"HKLM","System\Notifications\CompanyA.ExampleApp.MediaPlayerTrackTitle","Application","0x00000000","\Program Files\ExampleApp\ExampleApp.exe"
"HKLM","System\Notifications\CompanyA.ExampleApp.MediaPlayerTrackTitle","Key","0x00000000","\System\State\MediaPlayer"
"HKLM","System\Notifications\CompanyA.ExampleApp.MediaPlayerTrackTitle","Message","0x00010001","0x8001"
"HKLM","System\Notifications\CompanyA.ExampleApp.MediaPlayerTrackTitle","Value Name","0x00000000","Title"
"HKLM","System\Notifications\CompanyA.ExampleApp.MediaPlayerTrackTitle","HKEY","0x00010001","0x80000000"

Conclusion

Wow!! The State and Notifications Broker is such an incredibly powerful part of the Windows Mobile 5.0 platform. As we've discovered in this month's column, the State and Notifications Broker is very customizable. You can filter and batch notifications, and can even persist notifications with the device, so that your program doesn't even need to be running in order to be notified of changes to a state value.

What's amazing is that we still haven't covered all of the State and Notifications Broker's features. When I started writing Part 1 of this State and Notifications Broker discussion, I thought I would need three parts to cover all of the features, but then talked myself into believing that I could cover all of the features in only two parts. Obviously, I was right the first time! In part 3, we'll dig into more detail about how persistent notifications behave, we'll look at the multithreading capabilities of the State and Notifications Broker, and we'll also cover how to use the State and Notifications Broker to expose your own state values.

That's it for this month. If you have any questions about what I've covered, or if you have ideas for things that you would like to see me cover, please contact me through my blog.