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

  1. In eMbedded Visual C++, create a new WCE Dynamic-Link Library project. I named mine Today2002.
  2. 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...

  1. Under the Build menu, select Set Active Platform and make sure Pocket PC 2002 is selected
  2. Under Project click Settings and select Settings For: All Configurations
  3. . 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:

  1. Add a new Resource Script (*.rc) to the project named Today2002.rc.
  2. Insert a new Dialog resource, right click the dialog and select Properties.
  3. 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:
  1. 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.
  2. 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.
  3. Update your newly compiled DLL.
  4. Fix the DLL setting in the registry.
  5. Do the same thing as step #2, and you should see the DLL listed again causing the shell to reload it.
  6. 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.

code NORTH Software Owl Island Help Desk Software and Live Chat Software

Copyright © 2009 EFNet #winprog. All rights reserved.