Main
Resources
|
Using the Today API for Pocket PC 2002
by theForger (Brook Miles)
How to add your own custom items to the Today screen on the Pocket PC 2002
What You Need
- eMbedded Visual C++ 3.0
- Pocket PC 2002 SDK
- Download Today2002 example project
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.
What's New in 2002
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.
Creating The DLL Project
- 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, to make sure everything goes smoothly, we need to tweak a couple of project settings...
- Under the Build menu, select Set Active Platform and make sure Pocket PC 2002 is selected
- Under Project click Settings and select Settings For: All Configurations
. 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.
Today2002.def
Today2002.dll need only export one function, InitializeCustomItem() and it may optionally export a second,
CustomItemOptionsDlgProc() . Both functions are exported by ordinal. Add a new Text File to the project named
Today2002.def and enter the following:
EXPORTS
InitializeCustomItem @ 240 NONAME
CustomItemOptionsDlgProc @ 241 NONAME
Today2002.cpp
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.
#include <windows.h> // Standard Header
#include <todaycmn.h> // Today API
#include <aygshell.h> // Shell Functions
const TCHAR g_szWndClass[] = _T("myToday2002ItemWnd");
const TCHAR g_szDisplayString[] =
_T("This is my Pocket PC 2002 Today Item application test!");
HINSTANCE g_hInstance = NULL;
HWND APIENTRY InitializeCustomItem(TODAYLISTITEM *ptli, HWND hParent)
{
HWND hwnd = NULL;
if(ptli->fEnabled)
{
hwnd = CreateWindow(g_szWndClass, _T(""), WS_VISIBLE | WS_CHILD,
0, 0, GetSystemMetrics(SM_CXSCREEN), 0,
hParent, NULL, g_hInstance, NULL);
}
return hwnd;
}
BOOL APIENTRY CustomItemOptionsDlgProc(HWND hwnd, UINT msg, UINT wParam,
LONG lParam)
{
switch(msg)
{
case WM_INITDIALOG:
{
SHINITDLGINFO shidi;
shidi.dwMask = SHIDIM_FLAGS;
shidi.hDlg = hwnd;
shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIZEDLGFULLSCREEN;
SHInitDialog(&shidi);
}
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK:
EndDialog(hwnd, LOWORD(wParam));
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
COLORREF crText = SendMessage(GetParent(hwnd), TODAYM_GETCOLOR,
TODAYCOLOR_TEXT, 0);
SetTextColor(hdc, crText);
RECT rcClient;
GetClientRect(hwnd, &rcClient);
SetBkMode(hdc, TRANSPARENT);
DrawText(hdc, g_szDisplayString, -1, &rcClient, DT_WORDBREAK);
EndPaint(hwnd, &ps);
}
break;
case WM_ERASEBKGND:
{
TODAYDRAWWATERMARKINFO dwi;
dwi.hwnd = hwnd;
dwi.hdc = (HDC)wParam;
GetClientRect(hwnd, &dwi.rc);
SendMessage(GetParent(hwnd), TODAYM_DRAWWATERMARK, 0,
(LPARAM)&dwi);
}
return TRUE;
case WM_LBUTTONUP:
MessageBox(hwnd, _T("You clicked my Today Item!"),
_T("Today 2002"), MB_OK | MB_ICONINFORMATION);
break;
case WM_TODAYCUSTOM_QUERYREFRESHCACHE:
{
TODAYLISTITEM *ptli = (TODAYLISTITEM*)wParam;
BOOL bUpdated = FALSE;
RECT rcText = {0, 0, GetSystemMetrics(SM_CXSCREEN), 0};
HDC hdc = GetDC(hwnd);
DrawText(hdc, g_szDisplayString, -1, &rcText,
DT_WORDBREAK | DT_CALCRECT);
ReleaseDC(hwnd, hdc);
if(ptli->cyp != (ULONG)rcText.bottom)
{
ptli->cyp = rcText.bottom;
bUpdated = TRUE;
}
return bUpdated;
}
break;
case WM_TODAYCUSTOM_CLEARCACHE:
// Here you can free up any additional resources
// that you may have allocated, such as bitmaps.
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
BOOL WINAPI DllMain(HANDLE hInstance, DWORD dwReason, LPVOID pvUnused)
{
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
{
g_hInstance = (HMODULE)hInstance;
WNDCLASS wc;
ZeroMemory(&wc, sizeof(wc));
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.hInstance = g_hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = g_szWndClass;
return (RegisterClass(&wc) != NULL);
}
break;
case DLL_PROCESS_DETACH:
{
UnregisterClass(g_szWndClass, g_hInstance);
}
break;
}
return FALSE;
}
Source Code Notes
Now for a little discussion about how this code will run when your Today Item is loaded.
InitializeCustomItem()
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() .
CustomItemOptionsDlgProc()
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.
WndProc()
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() .
DllMain()
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.
Today2002.rc
This file contains the Options dialog for Today2002. Because of the rather verbose code generated by the resource editor, I won't list the entire
resource file here. The lines of interest are:
// This line from resource.h
#define IDD_TODAY_CUSTOM 500
// These from Today2002.rc
IDD_TODAY_CUSTOM DIALOG DISCARDABLE 0, 0, 160, 120
STYLE DS_SETFOREGROUND | WS_POPUP | WS_VISIBLE
FONT 8, "Tahoma"
BEGIN
CTEXT "This is where you can put any controls you need...",
IDC_STATIC,7,7,146,106,WS_BORDER
END
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.
These are the steps I followed to create the dialog for Today2002.dll :
- 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.
Registry Settings
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.
[HKEY_LOCAL_MACHINE\Software\Microsoft\Today\Items\"Today 2002"]
"DLL"="\Windows\Today2002.DLL" ; DLL path and filename
"Type"=dword:4 ; Item type, 4 indicates Custom
"Enabled"=dword:0 ; 1 to display the item, 0 to hide it
"Options"=dword:1 ; 1 if you have an Options dialog, 0 if not
Enabling Your Item
On your device, tap Start, Settings, Today, Items and you should see your DLL listed
along with the default items. To enable, check the box next to it and hit OK. Assuming all went well, you should see
it displayed on the Today Screen as shown in this screenshot:
Today2002 Screenshot
Debugging
It's somewhat more difficult to debug this kind of DLL than it is a regular application. The shell will attempt to
load your DLL even if it's disabled in the Today Settings. In order to update the DLL after recompiling, follow these
steps:
- 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.
- Test it out!
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.
|