Creating Custom Today Screen Items

What is the Today Plug-In?

You obviously know what the Today screen is. You see it every time with the Microsoft logo (or whatever picture you've put beneath). And, you definitely won't be surprised to know that the Today custom component is just a DLL. The only additional fact you should know about it is that such a DLL should export two predefined functions with the given ordinal number:

; Contents of .DEF file
LIBRARY CustomItem

EXPORTS
InitializeCustomItem        @240    NONAME
CustomItemOptionsDlgProc    @241    NONAME

Being more precise, only the first function is a must. The second one is required if you want to provide an Options dialog for your component. Before you will dig in into code samples, let me highlight a couple of trivial things regarding Today components.

First, your DLL will be loaded at startup by the shell and won't be released until you turn off or reset the device. Unchecking the component in Today's settings panel does not help you at all. Thus, it's pretty hard to debug it. To release the problematic DLL, you should manually change the Registry settings and then reset the PDA.

The next thing to be considered is a place on the screen covered by your component. Due to lack of screen size, you should produce a relatively small-dimensioned GUI. Moreover, if there are too many Today items simultaneously presented on the screen, Windows CE adds a scroll bar, so the actual screen width will be smaller that the standard 240 pixels.

Installing Your Custom Today Item

Your component should be registered to let the OS know about its existence. Literally, it means to make some changes in the Tegistry. You can do it either by preparing a .cab file or developing your own application to update the CE registry. Below is the .inf-file section to command the installer to make the Registry changes:

[Registry.All]
; DWORD, Custom Items must always have Type = 4
HKLM,Software\Microsoft\Today\Items\CustomItem,Type,0x00010001,4
; DWORD
HKLM,Software\Microsoft\Today\Items\CustomItem,Enabled,0x00010001,1
; DWORD, If you're providing "Options" dialog, set this value to 1
HKLM,Software\Microsoft\Today\Items\CustomItem,Options,0x00010001,0
; STRING, This value keeps path to your DLL
HKLM,Software\Microsoft\Today\Items\CustomItem,DLL,0x00000000,
     "CustomItem.dll"
; DWORD, This value allows getting notifications by your component,
         set it to 2
HKLM,Software\Microsoft\Today\Items\CustomItem,Selectability,
     0x00010001,2

The following table gives you a short explanations of the Registry values:

Value Name and Type Description
DWORD: Type Custom Items must have Type = 4
DWORD: Enabled 0 or 1; 1 causes Today panel to show your component; nevertheless, the user can control it via the Today applet in the Control Panel
DWORD: Options if equals 1, the "Options" button in the Today applet will be enabled
SZ: DLL Pull path to your component
DWORD : Selectability New feature in Win Mobile 2003 SE; allows receiving additional notifications
Values are used as follows:
  • 0 or does not exist—component cannot be selected at all
  • 1—selections are manages automatically. In other words, your component will receive messages like WM_LBUTTONXXX
  • 2—the Today screen will send notification messages to your component when the user sets/releases focus or presses navigation keys

In case you'll want to develop your own registration program, following are a few tips of doing it more easily:

To Install

  • Place the component DLL in the desired folder
  • Add appropriate values to the Registry (see above)
  • Send a broadcast message to cause the OS to accept changes:
    SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0xF2, 0)

To Uninstall

  • Remove the key you created during Install (HKLM\SOFTWARE\Microsoft\Today\Items\YOURCOMPONENT)
  • Send a broadcast message to cause the OS to accept changes:
    SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0xF2, 0);
  • Wait to let the OS release your component DLL
  • Delete the DLL

Initializing a Custom Item

As you have told learned, your DLL will export two functions: InitializeCustomItem and optionally CustomItemOptionsDlgProc. In most cases, you'll want to draw one or more icons or images. An important thing to be noted is that you should load all required resources at attaching the DLL and then release them when you exit:

HICON            g_hIcon;
HINSTANCE        g_hInst;
HWND             g_hWnd;
...
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,
                       LPVOID lpReserved)
{
   switch (ul_reason_for_call)
   {
   case DLL_PROCESS_ATTACH:

      // The DLL is being loaded for the first time by a given process.
      // Perform per-process initialization here. If the initialization
      // is successful, return TRUE; if unsuccessful, return FALSE.
      g_hInst = (HINSTANCE)hModule;

      //load the icon
      g_hIcon = (HICON)LoadImage(g_hInst,MAKEINTRESOURCE(IDI_DISPLAYICON),
                                 IMAGE_ICON,16,16,LR_DEFAULTCOLOR );

      //intialize the application class, and set the global window handle
      UnregisterClass((LPCTSTR)LoadString
                      (g_hInst, IDS_CUSTOM_ITEM_APPNAME,0,0), g_hInst);

      // Proceed component initialization
        InitilizeComponent();

      g_hWnd = 0;

      break;

   case DLL_PROCESS_DETACH:
      // The DLL is being unloaded by a given process. Do any
      // per-process clean up here, such as undoing what was done in
      // DLL_PROCESS_ATTACH. The return value is ignored.

      DestroyIcon(g_hIcon);

      UnregisterClass((LPCTSTR)LoadString
                      (g_hInst, IDS_TODAY_STORAGE_APPNAME,0,0), g_hInst);
      g_hInst = NULL;
      break;
   }

   return TRUE;
}

InitializeComponent simply creates and registers a window class for your custom Today item. The shell will call the InitializeCustomItem function when it attempts to create it initially. This function is responsible for all initialization procedures, windows creation, and so forth. Here, you actually initialize all your internal stuff. Below some sample implementation is presented:

HWND InitializeCustomItem(TODAYLISTITEM *ptli, HWND hwndParent)
{
   LPCTSTR appName = (LPCTSTR)LoadString
                     (g_hInst,IDS_TODAY_STORAGE_APPNAME,0,0);

   //create a new window
   g_hWnd = CreateWindow(appName,appName,WS_VISIBLE | WS_CHILD,
      CW_USEDEFAULT,CW_USEDEFAULT,240,0,hwndParent, NULL, g_hInst, NULL) ;

   // create the storage space progress bar
   g_hBatteryProgressBar =
      CreateWindow(PROGRESS_CLASS, TEXT("Battery Progress Bar"),
      WS_CHILD | PBS_SMOOTH, CW_USEDEFAULT,CW_USEDEFAULT,
      CW_USEDEFAULT, CW_USEDEFAULT,g_hWnd, NULL, g_hInst, NULL);
   SendMessage(g_hBatteryProgressBar,PBM_SETSTEP,1,NULL);

   // create the program memory progress bar
   g_hProgramProgressBar =
      CreateWindow(PROGRESS_CLASS, TEXT("Program Progress Bar"),
      WS_CHILD | PBS_SMOOTH, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,
      CW_USEDEFAULT,g_hWnd, NULL, g_hInst, NULL);
   SendMessage(g_hProgramProgressBar,PBM_SETSTEP,1,NULL);

   // create the storage space progress bar
   g_hStorageProgressBar =
      CreateWindow(TRACKBAR_CLASS, TEXT("Storage Track Bar"),
      WS_CHILD | TBS_AUTOTICKS | TBS_BOTTOM,
      CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
      g_hWnd, NULL, g_hInst, NULL);
   SendMessage(g_hStorageProgressBar, TBM_SETRANGE, 1,
               MAKELPARAM(0,100));

   // attach our winproc to the newly created window
   SetWindowLong(g_hWnd, GWL_WNDPROC, (LONG) WndProc);

   //display the window
   ShowWindow (g_hWnd, SW_SHOWNORMAL);
   UpdateWindow (g_hWnd) ;

   return g_hWnd;
}

In your sample, you will implement an informational panel to show different, useful system info:

You can see similar implementation; for example, on a HP iPaq 2410. For simplicity, you won't deal with the options dialog. So, coming back to the above snippet, InitializeCustomItem makes a 'black job' of component initialization. Pay attention that it doesn't paint anything. All forthcoming messages will be handled in WndProc.

Creating Custom Today Screen Items

Processing Window Messages

The window procedure of your custom Today item will handle all Windows messages. Most of them just pass to the default handler. You will be interested in processing the following messages:

  • WM_TODAYCUSTOM_QUERYREFRESHCACHE
  • WM_TODAYCUSTOM_CLEARCACHE
  • WM_TODAYCUSTOM_RECEIVEDSELECTION
  • WM_TODAYCUSTOM_LOSTSELECTION
  • WM_TODAYCUSTOM_USERNAVIGATION
  • WM_TODAYCUSTOM_ACTION
  • TODAYM_TOOKSELECTION
  • WM_LBUTTONUP
  • WM_PAINT
  • WM_ERASEBKGND
  • WM_DESTROY

Start from the WM_TODAYCUSTOM_QUERYREFRESHCACHE message. It will be sent to your component to determine whether your data has been updated. So, that's often a good place for requerying your data. Your sample component simply obtains new data values there. Remember that you should return TRUE if the component needs to be updated, or FALSE otherwise.

WM_TODAYCUSTOM_CLEARCACHE is sent to the component's windows when it is about to be unloaded. Here, you can release all resources that your component might use during its lifetime.

Now, here's an overview of newly introduced notification messages, which are controlled by the Selectability value in the Registry. Below, you'll find short explanations on each message. I'd like just remind you that Selectability should be equal to 2 to use and advantage most of these messages.

Message Description
WM_TODAYCUSTOM_RECEIVEDSELECTION wParam = nVirtKey, lParam is not used; return FALSE if you want to move selection to the next Today item; the component also will get WM_ERASEBKGND and WM_PAINT messages, regardless of the Selectability value
WM_TODAYCUSTOM_LOSTSELECTION Sent when a custom item loses selection; neither wParam nor lParam are used
TODAYM_TOOKSELECTION A Today item may request selection by sending this message to its parent window; in response, the component will get WM_TODAYCUSTOM_RECEIVEDSELECTION notification with wParam = 0 if the request was successful
WM_TODAYCUSTOM_USERNAVIGATION Received by the Today item when it was selected upon each press of navigation buttons; for example, VK_UP, VK_DOWN, and so forth; your app can use it internally to navigate inside a component's screen between different controls of the plug-in; WinProc should return TRUE if message was processed; otherwise, the selection will be passed to the next component
WM_TODAYCUSTOM_ACTION The same as the previous message, except that WM_TODAYCUSTOM_ACTION is sent when the user presses the 'Action' button (VK_RETURN); the return value is ignored; if Selectability equals 1, the OS will generate a pair of WM_LBUTTONDOWN/WM_LBUTTONUP messages at coordinates (1,1)

In the sample project, which is based on the Pocket PC 2003 SDK sample MemWatcher, you will use some of the notifications above. Even though your test project will not implement all the nice-to-have features, you'll get the general route of coding.

Processing 'Mouse' Events

When the user clicks on your custom component, WinProc will receive WM_LBUTTONXXX messages. You can process them and conduct specific actions in response to clicks. The sample project simply recognizes to which control area the click belongs, and responds by calling different Control Panel applets:

case WM_LBUTTONUP:
   {
      RECT rcBattery;
      SetRect(&rcBattery,20,4,89,30);
      RECT rcMem;
      SetRect(&rcMem,90,4,150,30);
      RECT rcStorage;
      SetRect(&rcStorage,160,4,220,30);
      POINT point;
      point.x = LOWORD(lParam);
      point.y = HIWORD(lParam);

      PROCESS_INFORMATION pi;
      if (PtInRect(&rcBattery, point))
         ::CreateProcess(_T("\\Windows\\ctlpnl.exe"),
                         _T("cplmain.cpl,3"),
            NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi);
      else if (PtInRect(&rcMem, point))
         ::CreateProcess(_T("\\Windows\\ctlpnl.exe"),
                         _T("cplmain.cpl,4"),
            NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi);
      else if (PtInRect(&rcStorage, point))
         ::CreateProcess(_T("\\Windows\\ctlpnl.exe"),
                         _T("cplmain.cpl,4,1"),
            NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi);
      else
         MessageBox(NULL, TEXT("Detected Screen Tap!"),
                   TEXT("Custom Today Item:"), MB_OK);
   }
   break;

Similar behavior also is implemented for navigation buttons.

Conclusion

I hope this article has convinced you that creating custom Today items is kind of fun. You're set up well, having the plug-in's WinProc at hand to process all Windows messages. Besides, the Today API on Windows Mobile 2003 SE provides you with some additions that give you new features. With a relatively small effort, you're ready to create cool and powerful mobile applications.

About the Author

Alex Gusev started to play with mainframes t the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.



Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Is your sales and operations planning helping or hurting your bottom line? Here are 5 useful tips from the experts at Quintiq to guide you to a better S&OP strategy.

  • Intelligent N+X Redundancy, Placement Affinities, & Future Proofing in the Virtualized Data Center Virtualization brought about the ability to simplify business continuity management in IT. Workload portability and data replication capabilities mean that physical infrastructure failures no longer need impact application services, and they can rapidly be recovered even in the event of complete site failure. However, Enterprises and Service Providers face new challenges ensuring they have enough compute …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds