The Today Screen is usually the first thing a user sees when they power on their device. The number of new email messages, upcoming appointments, and current tasks are all optionally presented from this primary interface. Your applications can take advantage of this same functionality by creating a custom Today Item, allowing you to present useful, important, or even entertaining information to the user.
The Today API for Pocket PC 2002 contains a couple of advantages from the original Pocket PC API. Most noticeably, users are now able to select a background image or Theme, and the Today Items are drawn overtop of this image transparently. Themes also allow the user to select the color of the text they want their Today Items to use... you could choose to let the user to select a color specifically for your item through your Custom Options dialog box, however the default should still be the color associated with the Theme.
In eMbedded Visual C++, create a new WCE Dynamic-Link Library project. I named mine Today2002.
When asked what kind of DLL you would like to create, select An empty Windows CE DLL project.
. Next, on the Link tab in the General category, add aygshell.lib at the end of the list of libraries under Object/library modules.
The library aygshell.lib is used for initializing our Custom Options dialog box later on.
Now we're ready to create the files required for the project.
Add a new C++ Source File to the project and you're ready to write the code for the DLL. It consists of only 4 simple functions, InitializeCustomItem() which is called when the Pocket PC 2002 shell wants you to create your Today Item window, CustomItemOptionsDlgProc() which handles the messages for the Options dialog box we will create shortly, WndProc() which handles the messages for our Today Item window, and the standard DllMain() which is called when the DLL is loaded and unloaded.
// that you may have allocated, such as bitmaps.
Now for a little discussion about how this code will run when your Today Item is loaded.
After DllMain() has been called to indicate the DLL being loaded, this function will be called by the shell to request that we create our custom Today Item window. We check to see if our item is enabled (since this function will be called even if it is not) and if so, we create a window from the class we have previously registered. It's important to make this window a child of the shell, whose handle is provided as the hParent parameter. The position and sizes you specify here are not important, since the shell will automatically move it into place depending on the order that is set in the Today settings dialog. The height of the window is up to you, but it is the shell that ultimately sets the size, so you need to tell the shell how high you want your window to be... this is accomplished by handling the WM_TODAYCUSTOM_QUERYREFRESHCACHE message in WndProc().
You are passed a pointer to a TODAYLISTITEM structure which contains information about your item (the information specified in the registry) as well as some members that you can use to associate flags or additional memory for use later on. This is the same structure that will be passed to the Today Custom messages in WndProc().
The options dialog is loaded by the shell, which looks for a dialog resource with an ID of 500. The shell will then use our CustomItemOptionsDlgProc() as the dialog procedure parameter when it calls DialogBox(). Other than this strange way of doing things, it is otherwise the same as if you had loaded the dialog yourself.
The heart of the beast. WM_PAINT is of course where we draw whatever we want our Today Item to display. Notice how we send the TODAYM_GETCOLOR message to the shell (the parent of our window) to retrieve the correct text color setting. Also, we set the text drawing mode to TRANSPARENT, otherwise we would have an unsightly white rectangle behind any text we drew.
The transparent effect that allows the Theme or background image to show through all of the Today Items is handled by WM_ERASEBKGND. We send the TODAYM_DRAWWATERMARK message to the shell, and the shell draws the appropriate section of the image onto our window. This happens prior to any WM_PAINT message, so that by the time we end up in WM_PAINT, the background has been nicely drawn for us, and we can go about adding text and icons.
WM_LBUTTONUP simply responds to a stylus tap by popping up an informative message box... one would hope that in general you would do something more useful at this point!
The WM_TODAYCUSTOM_QUERYREFRESHCACHE message is sent to your window by the shell periodically to ask you if anything has changed that might require your window to be redrawn. If you return TRUE, the shell makes sure that your window is properly positioned and causes it to be redrawn by triggering WM_ERASEBKGND and WM_PAINT.
It is very important that you update the ptli->cyp member of the TODAYLISTITEM with the height that you currently desire for your window. If you do not do so, your window may not be visible at all except as a thin line, or it may have a size that does not match it's contents.
WM_TODAYCUSTOM_CLEARCACHE is sent prior to the DLL being unloaded. This gives you an opportunity to unload, free or close up any resources that you may have allocated or associated with the TODAYLISTITEM structure which is passed as a pointer in the wParam parameter. This is essentially the uninitialization counterpart to InitializeCustomItem().
I have seen examples of Today Item code that does not include a DllMain(), however I don't recommend this. The primary problem is registering and unregistering your Today Item's window class.
If you do not unregister your window class when your dll is unloaded, and your dll is subsequently reloaded, when it tries to register the window class again it will fail since the original (and now incorrect) registration is still hanging around. This may not happen often but it does happen, and when it does the person using your program will need to reset their Pocket PC to fix it.
I register the window class in DllMain() as well. You could do this in InitializeCustomItem() instead, but I chose not to for two reasons. First of all it makes the code easier to understand, DllMain() takes care of the registration and unregistration in one place. Secondly, it allows your program to prevent itself from being loaded if registration fails. This is a good thing! If any initialization fails, in this case the call to RegisterClass(), simply return FALSE and the dll will be unloaded immediately.... this is helpful because it allows you to tweak your code and upload another copy of the dll without having to go through all the trouble of disabling it and getting the shell to unload your dll so that you can overwrite it, this will come up again later when I talk about debugging.
The name of the dialog is not important, however it is critical that the numeric ID of the resource be 500. Oddly enough, instead of us loading our own dialog box, the Pocket PC 2002 shell will load it automatically based on it's resource ID, and call our CustomItemOptionsDlgProc() to handle messages for it. If the resource ID doesn't match, the shell won't find it and our dialog won't load.
Add a new Resource Script (*.rc) to the project named Today2002.rc.
Insert a new Dialog resource, right click the dialog and select Properties.
On the General tab enter a name for the dialog in the box labelled ID, followed by the value you want for the resource ID, like so: IDD_TODAY_CUSTOM=500. Do not use quotes (""). The =500 will be removed from view, but it will set the value properly. You can verify this either by looking in the file resource.h that is generated automatically when you save your resource script, or by selecting Resource Symbols under the View menu.
At this point, it's up to you to decide what you want to put on the dialog, any options relevant to your particular needs.
To get the shell to load your DLL at all, we need to add an entry to the registry on the device. While you are developing this is most likely accomplished with the Remote Registry Editor under the Tools menu.
Using the Remote Registry Editor, change the value of the DLL entry that points to your DLL to an invalid filename. I usually just append a ~ since it's easily removed to get the original filename back.
On your device, tap Start, Settings, Today, Items and you should see your DLL is no longer listed, then click OK. Unfortunately this isn't just for your benefit, the DLL will not be unloaded untill you force it to try and reload by performing this step.
Update your newly compiled DLL.
Fix the DLL setting in the registry.
Do the same thing as step #2, and you should see the DLL listed again causing the shell to reload it.
This method works both on the Pocket PC 2002 Emulator and on my iPAQ. If for some reason Step #2 doesn't cause the DLL to be unloaded for you, you will need to reset your device instead.
If you are having difficulty determining just what code is getting executed during the whole process of loading, unloading and refreshing, you may wish to have your program write a log file of it's activities, so that you will have a better sense of what gets called at what time, or if certain things are getting called at all.