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"