I seem to have been flying/presenting solidly for the last week or so - thankfully my Pocket PC Phone Edition has been able to keep sync'd with e-mail in Taiwan and in Hong Kong - the one issue I have with the Pocket PC is that I have absolutely no idea how much power is left in the device, I could click Start | Settings | System | Power - I'd prefer to just glance at the today screen to get this information, it would be extremely useful to have a custom Today Item that shows me the current power and also whether I'm charging the unit or not... We arrived at the Intercontinental hotel in Seoul late yesterday afternoon - after an excellent Korean meal in the hotel I decided to crack on with some work - I have slides to update ready for tomorrow's Windows Embedded Essentials (WEE) event, I have a spreadsheet to complete for my new manager (welcome on board Melanie!!) and an article to finish for MSDN - so I decided to write the custom today item instead... <G>

Here's the registry keys needed to have the power meter working...

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Today\Items\"PowerMeter"]
"DLL"="
\\Windows\\MyToday.dll"
"Flags"=dword:00000000
"Options"=dword:00000000
"Enabled"=dword:00000001
"Order"=dword:0000000e
"Type"=dword:00000004

It would be useful to be able to double click (tap) on a registry file and have it auto merge with the devices registry, since some of the marketing folks travelling with me might have some issues installing a Today item I also decided to write a simple Win32 application to copy the “MyToday.dll” file to the \Windows folder from an SD storage card, and to also setup the required registry information... here's the listing...

// PowerReg.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include <winreg.h>

int WINAPI WinMain( HINSTANCE hInstance,
     HINSTANCE hPrevInstance,
     LPTSTR    lpCmdLine,
     int       nCmdShow)
{
HKEY hKey;
DWORD dwDisposition;
DWORD dwFlags=0;
DWORD dwOptions=0;
DWORD dwEnabled=1;
DWORD dwOrder=14;
DWORD dwType=4;
TCHAR *tcReg=L"
\\Windows\\MyToday.dll";

DWORD dwSize=sizeof(DWORD);

long lRet=RegCreateKeyEx(HKEY_LOCAL_MACHINE,
       L"Software\\Microsoft\\Today\\Items\\\"PowerMeter\"",
       0,
       NULL,
       0,
       0,
       NULL,
       &hKey,
       &dwDisposition);

if (lRet == ERROR_SUCCESS) { // Create Subkey goo here...
 CopyFile(L"
\\Storage Card\\MyToday.dll",L"\\Windows\\MyToday.dll",FALSE);
 RegSetValueEx(hKey,L"DLL",0,REG_SZ,(LPBYTE)tcReg,sizeof(TCHAR)*lstrlen(tcReg));
 RegSetValueEx(hKey,L"Flags",0,REG_DWORD,(LPBYTE)&dwFlags,dwSize);
 RegSetValueEx(hKey,L"Options",0,REG_DWORD,(LPBYTE)&dwOptions,dwSize);
 RegSetValueEx(hKey,L"Enabled",0,REG_DWORD,(LPBYTE)&dwEnabled,dwSize);
 RegSetValueEx(hKey,L"Order",0,REG_DWORD,(LPBYTE)&dwOrder,dwSize);
 RegSetValueEx(hKey,L"Type",0,REG_DWORD,(LPBYTE)&dwType,dwSize);
 MessageBox(NULL,L"Registry Enabled",L"Power Meter",MB_OK | MB_ICONINFORMATION);
} else {
 MessageBox(NULL,L"Cannot Create Registry Key!!",L"PowerReg",MB_OK | MB_ICONINFORMATION);
}
RegCloseKey(hKey);

 return 0;
}

and of course we need the listing for the today item itself, this is simply a Win32 DLL - yes, I know there's information that shows how to write a custom today item in managed code, it seemed to be quicker to lift the example from the Pocket PC 2003 SDK and modify to suit my needs.

#include <windows.h>
#include <Todaycmn.h>

#include "resource.h"

HWND APIENTRY InitializeCustomItem(TODAYLISTITEM *ptli, HWND hwndParent);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

HINSTANCE g_hInstance;
HWND g_hWndParent;
TCHAR szEvent[25] = TEXT("Count Down: ");  //String that specifies the thing we are counting down to
TCHAR szTime[25] = TEXT("\0");             //String to hold the time left.  We initialize this to empty
HBITMAP g_hBitmap;                    //Bitmap we use for painting the background
HDC g_hDCMem;                         //In memory DC used for painting the background
BOOL g_bGetBackgroundSurface = FALSE; //Boolean that is TRUE if the background changed
POINT g_oldPt;                        //Used to keep track of where our window is

HBITMAP hPowerBitmap;
TCHAR tcPower[50];
SYSTEM_POWER_STATUS_EX pStatus; // Power Status.

#define WINDOWHEIGHT 23

//The following two defines will probably be defined in a public header in the future.
//Please comment them out if you are having problems with building this sample.
#define TODAYM_GETCOLOR     (WM_USER + 100)    //Used to get the Today screen text color
#define TODAYCOLOR_TEXT     0x10000004         //Used to get the Today screen text color

// This is the WinProc for the dialog.  Does not do much in
// this example.               
BOOL APIENTRY CustomItemOptionsDlgProc(HWND hDlg, UINT message, UINT wParam, LONG lParam)
{
 switch (message)
 {
  case WM_INITDIALOG:
   return TRUE;

  case WM_COMMAND:
   if (LOWORD(wParam) == IDOK)
            {
    EndDialog(hDlg, LOWORD(wParam));
    return TRUE;
   }
   break;
  default:
   return DefWindowProc(hDlg, message, wParam, lParam);
   }
   return 0;
}

HWND APIENTRY InitializeCustomItem(TODAYLISTITEM *ptli,HWND hwndParent)
{
    WNDCLASS  wc;
    HWND  hWnd = NULL;
   
    if (ptli->fEnabled==0) return NULL;    //Return NULL if we are not enabled.
    g_hInstance = ptli->hinstDLL;         //Store instance handle in our global variable
   
    //Create our window class
    wc.style   = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc  = (WNDPROC) WndProc;
    wc.cbClsExtra  = 0;
    wc.cbWndExtra  = 0;
    wc.hInstance  = g_hInstance;
    wc.hIcon   = 0;
    wc.hCursor   = 0;
    wc.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
    wc.lpszMenuName  = 0;
    wc.lpszClassName = TEXT("MyToday");
    RegisterClass(&wc);
   
   
    g_oldPt.x = 0;        //Initialize our POINT structure that keeps track of our window location
    g_oldPt.y = 0;
    g_bGetBackgroundSurface = TRUE;    //Get the background to start with
   
    hWnd = CreateWindow(TEXT("MyToday"), TEXT("MyTodayScreen"), WS_VISIBLE | WS_CHILD,
        CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hwndParent, NULL, g_hInstance, NULL);
   
    g_hWndParent = hwndParent;
       
    return hWnd;
}

 

//This code checks to see if our window has changed locations.
BOOL HasPositionChanged (HWND hwnd)
{
    POINT pt;
    pt.x = 0;
    pt.y = 0;
    MapWindowPoints(hwnd, g_hWndParent, &pt, 1);
    if (pt.y != g_oldPt.y)
    {
        g_oldPt.x = pt.x;
        g_oldPt.y = pt.y;
        return TRUE;
    }
    return FALSE;
}

//Gets the backgound surface and puts it in a bitmap.  Allows us to do the transparent effect.
VOID GetBackgroundSurface(HWND hwnd)
{
    HDC parentDC = GetDC(g_hWndParent);
    // Create a memory device context compatible with the device.
    g_hDCMem = CreateCompatibleDC (parentDC);
    // Create a bitmap compatible with the device associated with the
    // device context.
    g_hBitmap = CreateCompatibleBitmap (parentDC, 240, WINDOWHEIGHT);
    SelectObject(g_hDCMem, g_hBitmap);

    BitBlt(g_hDCMem, 0, 0, 240, WINDOWHEIGHT, parentDC, g_oldPt.x, g_oldPt.y, SRCCOPY);
    ReleaseDC(g_hWndParent, parentDC);
}

//Draws the text
VOID PaintText(HWND hWnd, HDC hdc)
{
    RECT rt;
 HDC hDCTemp;
 HBITMAP hOldBitmap;
 BITMAP bmp;
 hDCTemp=CreateCompatibleDC(hdc);
 hOldBitmap=(HBITMAP)SelectObject(hDCTemp,hPowerBitmap);


    //First draw our background.
    BitBlt(hdc, 0, 0, 240, WINDOWHEIGHT, g_hDCMem, 0, 0, SRCCOPY);

 GetClientRect(hWnd, &rt);
    //The following gets us the text color for the today screen text.
    SetTextColor(hdc, SendMessage(g_hWndParent, TODAYM_GETCOLOR, TODAYCOLOR_TEXT, 0));
    SetBkMode(hdc,TRANSPARENT);

    rt.left += 30;
    rt.top += 2;
 ExtTextOut(hdc,rt.left,rt.top,ETO_OPAQUE,NULL,tcPower,lstrlen(tcPower),NULL);
//    DrawText(hdc, tcPower, -1, &rt, DT_SINGLELINE);

 GetObject(hPowerBitmap,sizeof(bmp),&bmp);
 TransparentImage(hdc,0,0,bmp.bmWidth,bmp.bmHeight,hDCTemp,0,0,bmp.bmWidth,bmp.bmHeight,RGB(255,0,0));
 SelectObject(hDCTemp,hOldBitmap);
 DeleteDC(hDCTemp);
 

   
}

//Gets the rectangle to just redraw the time.
//This helps reduce some flickering.
VOID GetClippingRect (HWND hWnd, RECT* rt)
{
    GetClientRect(hWnd, rt);
    rt->left += 125;
    rt->top += 2;
    rt->right -= 30;
}

//This is our paint function.  It only paints the whole
//window the first time around and whenever it moves.
VOID PaintAll(HWND hWnd, HDC hdc)
{
    RECT rt;

    if (HasPositionChanged(hWnd))
    {
        rt.left = g_oldPt.x;
        rt.top = g_oldPt.y;
        rt.right = g_oldPt.x + 240;
        rt.bottom = g_oldPt.y + WINDOWHEIGHT;
        g_bGetBackgroundSurface = TRUE;
        ShowWindow(hWnd, SW_HIDE);
        InvalidateRect(g_hWndParent, &rt, TRUE);
        UpdateWindow(g_hWndParent);
        ShowWindow(hWnd, SW_SHOW);
        return;
    }

    if (g_bGetBackgroundSurface == TRUE)
    {
        GetBackgroundSurface(hWnd);
        g_bGetBackgroundSurface = FALSE;
        InvalidateRect(g_hWndParent, NULL, TRUE);
        UpdateWindow(g_hWndParent);
        return;
    }
   
    PaintText(hWnd, hdc);
}

//Our main WndProc.  Does everything after initialization.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;

 switch (message)
 {
  case WM_CREATE:
   hPowerBitmap=LoadBitmap(g_hInstance,MAKEINTRESOURCE(IDB_POWER));
   SetTimer(hWnd,1,1000,NULL);
  break;
        case WM_TIMER:
   GetSystemPowerStatusEx(&pStatus,TRUE);
   wsprintf(tcPower,L"Power at %ld%%",pStatus.BatteryLifePercent);
   if (pStatus.ACLineStatus == 1) { // On Mains Power.
    lstrcat(tcPower,L" - Charging");
   }
            InvalidateRect(hWnd, NULL, FALSE);
        return TRUE;
        case WM_ERASEBKGND:
            //We return true here since we handle our own background painting.
            return TRUE;
  case WM_TODAYCUSTOM_CLEARCACHE :
            //We have no cache to clear so we ignore this message.
   break;
        case WM_TODAYCUSTOM_QUERYREFRESHCACHE:
            //If the cyp member is set to 0 then you must set it to your windows height.
            //This makes sure the shell gives you a certain amout of room for your painting.
            if (((TODAYLISTITEM*)wParam)->cyp == 0)
            {
                ((TODAYLISTITEM*)wParam)->cyp = WINDOWHEIGHT;  //the shell draws a line at the bottom of our window.
                return 1;
            }
            break;
  case WM_PAINT:
            //Paint like you would in any other application.
   hdc = BeginPaint(hWnd, &ps);
            PaintAll(hWnd, hdc);
   EndPaint(hWnd, &ps);
   break;
  case WM_LBUTTONUP:
   break;
  default:
   return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

There are two files you need to add to your project, a .DEF file, and a bitmap (defined as IDB_POWER in this project).

Here's the .DEF File...

EXPORTS
InitializeCustomItem @ 240 NONAME
CustomItemOptionsDlgProc @ 241 NONAME

There are some updates I should make to this - right now the power meter flickers, I should double buffer everthing, I was also thinking of drawing the power meter rather than showing text - now that I have the basics running I think I prefer the text - see what you think...

- Mike