Inside the WinCE GUI

A small user interface can sometimes present big challenges. From menu bars and property sheets to extending Pocket Outlook, Meiyu presents several tips to bring those tasks down to size.


November 01, 2001
URL:http://drdobbs.com/inside-the-wince-gui/184416371

November 2001/Inside the WinCE GUI


Microsoft Windows CE is a compact, scalable operating system designed to run on a variety of devices. Windows CE programming is based on the Win32 API, but there are some tricks to targeting smaller devices. I’m going to discuss Windows CE 3.0 GUI changes using sample code for the Pocket PC platform. Four of the most important GUI characteristics on the Pocket PC are:

  1. The task bar is at the top of the Pocket PC screen.
  2. The menu is located at the bottom of the Pocket PC screen. The resource uses raw data.
  3. All dialog boxes on the Pocket PC must be full screen.
  4. Property sheet tabs are at the bottom of the screen.

I’ll review these along with several other distinctive aspects of Windows CE programming. To get started, download Microsoft’s eMbedded Visual Tools 3.0, available free online or for a small fee on CD. I used eMbedded Visual C++ 3.0, which requires Windows NT or 2000. The CD includes Windows Platform SDK for HPC, Palm PC, and Pocket PC.

The Combo Menu

The menu bar combines menu items and toolbar commands. The menu bar appears at the bottom of the device screen (see Figure 1) so that the user’s hand does not obstruct information content as he or she uses commands to manipulate it. Place menu items appearing on the menu bar on the leftmost portion of the bar.

This resource-definition statement specifies a raw data resource for an application: nameID RCDATA [[optional-statements]] { raw-data . . . }. Raw data resources permit the inclusion of binary data directly in the executable file. For example, the following resource example will create a menu with two menu items.

IDM_MAIN_MENU RCDATA DISCARDABLE
BEGIN
  // nameID, an integer number of menu items
  IDM_MAIN_MENU,
  I_IMAGENONE, IDM_MAIN_MENUITEM2,
  TBSTATE_ENABLED,TBSTYLE_DROPDOWN
  |TBSTYLE_AUTOSIZE,
  IDS_MAIN_MENUITEM2, 0,0,
  I_IMAGENONE, IDM_MAIN_MENUITEM3,
  TBSTATE_ENABLED,TBSTYLE_DROPDOWN
  |TBSTYLE_AUTOSIZE,
  IDS_MAIN_MENUITEM3, 0,1,
END
IDM_MAIN_MENU MENU DISCARDABLE
BEGIN
   POPUP "Colors"
   BEGIN
      MENUITEM "Blue",  IDM_BLUE
      MENUITEM "Red",   IDM_RED
      MENUITEM "Green", IDM_GREEN
   END
END
STRINGTABLE DISCARDABLE
BEGIN
   IDS_MAIN_MENUITEM2   "Colors"
   IDS_MAIN_MENUITEM3   "Weapon"
END

To create a menu bar, add the following code:

  SHMENUBARINFO mbi;
  memset (&mbi, 0,
          sizeof(SHMENUBARINFO));
  mbi.cbSize =
           sizeof(SHMENUBARINFO);
  mbi.hwndParent = hwnd;
  mbi.nToolBarId = IDM_MAIN_MENU;
  mbi.hInstRes   = hInst;
  mbi.nBmpId     = 0;
  mbi.cBmpImages = 0;
  SHCreateMenuBar (&mbi );

To save the window handle of the menu bar:

hwndCB = mbi.hwndMB;

Full Screen Dialogs

To display information and input fields in a clearer manner, the Pocket PC has adopted full-screen child windows, or dialog boxes, as a standard. The SHInitDialog function is primarily used to create a full-screen dialog box with an OK button in the navigation bar. Here is an example showing how to use SHInitDialog to create a full screen dialog.

case WM_INITDIALOG: {
SHINITDLGINFO shidi;
//Create Done button and size it.
shidi.dwMask = SHIDIM_FLAGS;
shidi.dwFlags =
SHIDIF_DONEBUTTON|SHIDIF_SIPDOWN|
           IDIF_SIZEDLGFULLSCREEN;
shidi.hDlg = hDlg;
//initializes the dialog based
//on the dwFlags parameter
SHInitDialog (&shidi);
}
break;

Property Sheets and Property Pages

A property sheet has three main parts: the containing dialog box, one or more property pages shown one at a time, and a tab at the bottom of each page that the user clicks to select that page. (see Figure 2) A property sheet is a dialog box that contains property pages. Each page contains controls, is based on a dialog template resource, and appears on a tab, an enclosure that looks like a file folder with a tab at the bottom. The tab names the page and indicates its purpose. Dialog box controls can be divided into logical groups putting each group on its own property page. Users click a tab in the property sheet to select a set of controls.

Pocket PC property sheets should be supported with tabs for navigating between groups of properties. Property sheets typically allow the user to change the values for a property and then apply those transactions. A Pocket PC property sheet with such functionality usually requires an OK button to make such changes, but should not have an OK button if OK/Cancel controls are present in the client area.

Messages are sent to a tab control to add tabs and to affect the appearance and behavior of the control. Each message has a corresponding macro, which can be used instead of sending the message explicitly. Although individual tabs cannot be disabled in a tab control, a tab control can be disabled in a property sheet by disabling the corresponding page. Property sheets have a capability that standard dialog boxes do not: they allow the user to apply changes they have made before closing the property sheet. The PocketPropSheet sample code is available online.

Control Panel Applets

Users can alter the devices settings using Control Panel applets. Applets can be created that let users examine and modify the settings and operational modes of specific hardware and software. Control Panel applets are just DLLs with special entry points and with .cpl extensions. A Control Panel applet is stored in the windows system directory (See Figure 3). When the control panel is started, the applet will be there. A Control Panel extension is a DLL with a special function called CPlApplet(). It’s a relatively simple function taking a window handle, a message, and a couple of message specific parameters. CplApplet has the following signature: LRESULT CplApplet (HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lParam2).

When the Windows Control Panel (CONTROL.EXE) starts up, it looks for XXX.cpl in the Windows System directory. It loads each DLL and calls its CplApplet function with various messages. The Control Panel application communicates only with files with the “.cpl” extension.

For example, when the Control Panel first starts up, it calls the CplApplet function with msg=CPL_INIT. When the user double-clicks the applet’s icon, it calls CplApplet with msg=CPL_DBLCLK, and the applet displays its dialog. Each Control Panel DLL can support more than one icon or applet. Tell the Control Panel how many applets there are by responding to CPL_GETCOUNT, and the Control Panel requests information about each one by sending CPL_INQUIRE or CPL_NEWINQUIRE. Example 1 is the code sample for the Control Panel callback function. This function must be named CplApplet. It is an export function.

To support more than one applet, create a ListView with an ICON view. Make each icon as one of the applets. After setting up the ICON ListView, each icon will bring up an applet. To display the applet on the first page of the Control Panel, do the following: CPL_IDNAME will need to be handled. The string returned will be the name used to find the applets information in the registry. Set the registry group to the tab where the applet should go.

For example, when handling CPL_IDNAME, return SampleApplet. In the registry, create the key HKLM\Control Panel\SampleApplet and create the Group value under that key. The applet referred to by SampleApplet will be shown on the tab specified in the Group value. The sample code for CplApplet is available online.

//===========================================================
BOOL WINAPI DllMain (
           HINSTANCE hinstDLL,  // handle to DLL module
           DWORD fdwReason,     // reason for calling function
           LPVOID lpReserved )  // reserved
{
    // Perform actions based on the reason for calling.
    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH:
        //Initialize once for each new process. Return FALSE to
        //fail DLL load.
        break;
        case DLL_THREAD_ATTACH:
             // Do thread-specific initialization.
        break;
        case DLL_THREAD_DETACH:
            // Do thread-specific cleanup.
        break;
        case DLL_PROCESS_DETACH:
            // Perform any necessary cleanup.
        break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

Running Apps That Won’t Quit

The first thing needed is to create a parent window with null style, use this window to hide the running program from the OS list.

HWND hwndParent = CreateWindow(
                   TEXT("static"), g_szTitle, 0,
                   CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                   CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

g_hWnd = CreateWindow (
            YOURWINCLASS, g_szTitle, WS_POPUP|WS_VISIBLE,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            CW_USEDEFAULT, hwndParent, NULL, hInstance, NULL);

Custom Drawn Controls on Pocket PC

To create custom-drawn controls, create an owner drawn button in the response, as in Example 2. In this case, a WM_DRAWITEM message will be received. Process the following function to do the owner drawing for the custom buttons (see Figure 4).

To make the dialog divider have a 3-D look, create a static box with a control ID and use the following function:

// This will resize the divider line to look like a small 3D like line.
// This function takes the dialog handle and the handle of the control
// you want to resize.
BOOL ResizeLine (HWND hWndDlg, HWND hWndLine)
{
    RECT rctLine;
    POINT p;

    VERIFY( hWndLine );

    If (hWndLine == NULL)
        return FALSE;

    // Get original rect for line
    GetWindowRect(hWndLine, &rctLine);

    // Center
    // rctLine.top = ((rctLine.bottom - rctLine.top)/2)
    //                                      +rctLine.top-4;
    p.x = rctLine.left;
    p.y = rctLine.top;
    ScreenToClient (hWndDlg, &p);

    // Set new rect
    MoveWindow (hWndLine, p.x, p.y,
                rctLine.right-rctLine.left, 2, TRUE);
    return TRUE;
}

Using Pocket Outlook Object Module

The Pocket Outlook Object Module (POOM) is a smaller subset of the Outlook Object Module (Outlook). There are subtle differences between POOM and Outlook. POOM does not have the NameSpace object that Outlook has to log on to a MAPI session. The main interface to POOM is the Pocket Outlook Application object. This is the only object that can be created by calling CoCreateInstance, and it is from this object that all other objects are derived.

// To Use Pocket Outlook make sure pimstore.dll is
// on the device, and create the instance for IID_IPOutlookApp
#define  POA_OBJECT   TEXT("PocketOutlook.Application")

BOOL ConnectToPocketOutLook (void)
{
   CLSID       clsid;
   HRESULT     hr;
   HRFUNC      pProc;

   // Start by initializing COM
   CoInitializeEx (NULL, 0);

   // Get the CLSID for the application
   hr = CLSIDFromProgID (POA_OBJECT, &clsid);

   if (FAILED(hr))
   {
      g_hPimstore = LoadLibrary
                        (TEXT("\\windows\\pimstore.dll"));
      if (!g_hPimstore)
      {
         // You gotta have pimstore.dll, dude
         return FALSE;
      }

      pProc =(HRFUNC)GetProcAddress (g_hPimstore,
                                    TEXT("DllRegisterServer"));
      if (!pProc)
      {
         return FALSE;
      }

      hr = CLSIDFromProgID(POA_OBJECT, &clsid);
      if (FAILED(hr))
      {
         return FALSE;
      }
   }

   // Now, let's get the main application
   hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER,
                         IID_IPOutlookApp, (PPVOID) &g_polApp);
   if (FAILED(hr))
   {
      return FALSE;
   }
   return TRUE;
}

Extending Pocket Outlook Menus

The Calendar, Tasks, and Contacts applications support menu add-ins for external applications. Providing an add-in for one of these applications allows the menu item to appear on the Tools menu. When the user selects the menu item, Pocket Outlook calls a function in a registered DLL, which passes the object identifiers of the currently selected items in the application. To register your application, create a registry key under the following key:

#define  YOURAPP_REG_KEY \
  TEXT ("Software\Microsoft\PimApps\PimExtensions\pim_app\AddIns")

Any name can be used for the key. To avoid conflicts with other add-ins, I suggest that the key contain the name of your company. Replace pim_app with Contacts, Tasks, or Calendar, depending on which application’s menu you want the add-in to appear.

After creating this key, create two values for the key. The first value is DLL, which is set to the name of the DLL. The second value is Menu, which is set to the name to be displayed on the Tools menu. Others may add tool items, so try to create a unique, descriptive name that will distinguish your application.

For example, the following registry key would register a phone-dialer application in the Contacts application:

Software\Microsoft\PimApps\PimExtensions\Contacts\AddIns\MyDialer
DLL: MyDialer.dll
Menu: "Dial Contact"

Pocket Outlook calls the CePimCommand function to create a menu add-in in the Tools menu in the Contacts, Calendar, or Tasks applications. The DLL that supports the menu add-in must define and expose this function, which is called automatically when the user chooses the menu item. (See Listing 1)

Multiple Threads

Windows CE 3.0 offers improved windows compatibility, better threading response, additional task priorities, and semaphores, allowing the OS to respond immediately to events and interrupts. These make Windows CE 3.0 suited for industrial applications. Improvement in thread response allows developers to know specifically when the thread transitions occur. Windows CE includes some features that increase the capabilities of monitoring and controlling hardware:

To create a thread, call the CreateThread function. The following code example shows the CreateThread function prototype.

HANDLE CreateThread (
    // not supported in CE set to NULL
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    // not supported in CE set to 0
    DWORD    dwStackSize,  
    // Pointer to the start of the thread routine
    LPTHREAD_START_ROUTINE lpStartAddres,
    // Specifies an application-defined value that
    // is passed to the thread
    LPVOID  lpParameter,
    // set to 0 or CREATE_SUSPENDED
    DWORD dwCreationFlags,
    // receives the new thread's identifier
    LPDWORD lpThreadId);

Windows CE does not support the lpThreadAttributes and dwStackSize parameters, so set them to NULL or 0.

If CreateThread is successful, it returns the handle to the new thread and the thread identifier. A thread can be terminated by calling ExitThread, which frees the resources that are used by a thread when they are no longer needed.

The code in Listing 2 will create a progress monitoring dialog running on its own thread.

About the Author

Meiyu E. Lin is a Senior Software Engineer of Bsquare Corp. Specializing in Win32, Windows CE and Web development technologies. She can be reached at [email protected].

November 2001/Inside the WinCE GUI/Figure 1

Figure 1: Pocket PC menu

HexWeb HTML

Figure 2: Property sheets on Pocket PC

November 2001/Inside the WinCE GUI/Figure 3

Figure 3: SampleApp applet in the ControlPanel

November 2001/Inside the WinCE GUI/Figure 4

Figure 4: A custom Up/Down button

November 2001/Inside the WinCE GUI/Listing 1

Listing 1: connectPoom.cpp
Using the Pocket Outlook Object Model


// To Use Pocket OutLook Make sure the pimstore.dll is
// on the device, and create the instance for IID_IPOutlookApp 
#define POA_OBJECT _T("PocketOutlook.Application")
#define YOURAPP_REG_KEY 
 _T("Software\\Microsoft\\PimApps\\PimExtensions\\Contacts\\AddIns\\YourApps")
// In resource file you will need to define the following:
#define IDS_MSCONTACT_MENU 400

STRINGTABLE DISCARDABLE 
BEGIN
IDS_MSCONTACT_MENU "Call Contact" // The string will appear on 
// the tools and popup menu
END

BOOL ConnectToPocketOutLook (void)
{
	CLSID clsid;
	HRESULT hr;
	HRFUNC pProc;

	// Start by initializing COM
	CoInitializeEx (NULL, 0);

	// Get the CLSID for the application
	hr = CLSIDFromProgID (POA_OBJECT, &clsid);

	if (FAILED(hr))
	{
		//example: DebugPrint (_T("ClassName::method Name"),
		// _T("%d,\t%s"), GetLastError(), tcbuffer);
		g_hPimstore = LoadLibrary (TEXT("\\windows\\pimstore.dll"));
		if (!g_hPimstore)
		{
		// You gotta have pimstore.dll, dude
		return FALSE;
		}

		pProc = (HRFUNC) GetProcAddress(g_hPimstore, TEXT("DllRegisterServer"));
		if (!pProc)
		{
		return FALSE;
		}

		// Register, please
		pProc();

		hr = CLSIDFromProgID(POA_OBJECT, &clsid);
		if (FAILED(hr))
		{
		return FALSE;
		}
	}

	// Now, let's get the main application
	hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER,
	IID_IPOutlookApp, (PPVOID) &g_polApp);
	if (FAILED(hr))
	{
	return FALSE;
	}

	return TRUE;
}

// To dial from poom contact, first we need to create the registry key and 
// add a menu to contact for dialing directly from Contacts 
BOOL CreatePoomDialerRegistry ()
{
	HKEY hKey;
	TCHAR tcMenu[MAX_PATH+1] = _T("\0");
	BOOL bReturn = FALSE;

	if (ERROR_SUCCESS == RegCreateKeyEx (HKLM, YOURAPP_REG_KEY, 0, NULL, 0, 
	KEY_ALL_ACCESS, NULL, &hKey, NULL))
	{
		LPTSTR ptcData = _T("YourPoom.dll");

		LoadString (IDS_MSCONTACT_MENU, tcMenu, MAX_PATH);
		// lpValueName: the name of the value to set.
		// lpData: [in] Pointer to a buffer containing the data to be stored 
		// with the specified value name. 
		RegSetValueEx( hKey, _T("DLL"), NULL, REG_SZ, (BYTE*)ptcData, 
		(_tcslen(ptcData)+1)*sizeof (TCHAR));
		RegSetValueEx( hKey, _T("MENU"), NULL, REG_SZ, (BYTE*)tcMenu, 
		(_tcslen(tcMenu)+1)*sizeof (TCHAR));

		RegCloseKey( hKey );
		bReturn = TRUE;
	}

	return bReturn;
}
//End of File 
November 2001/Inside the WinCE GUI/Listing 2

Listing 2: ProgressThread.cpp
Creating a progress monitoring dialog


//==============================================================================
// Multiple threads

unsigned long    g_lThreadID;
HWND            g_hProgDlg;

// In your user command you can start to create the thread:
#define WM_BEGIN_PROGRESS     (WM_USER+100)
#define WM_FINISHED              (WM_USER+101)

BOOL CALLBACK DlgProgressProc (HWND hDlg, UINT iMessage, WPARAM wParam,
                            LPARAM lParam);
static unsigned __stdcall DeviceProgressThread(void *arg);
BOOL MonitorDProgressThread (void);

//================== Begin of Resource =====================================
// Move this to your resource file 
IDD_PROGRESS_DLG DIALOG DISCARDABLE  0, 0, 131, 67
STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "ProgressDlg"
FONT 9, "Tahoma"
BEGIN
    RTEXT           "Folder:",IDC_STATIC,5,7,26,9
    LTEXT           "",IDC_PATH,33,7,93,9
    RTEXT           "File:",IDC_STATIC_SCAN,9,21,22,8
    LTEXT           "",IDC_FILENAME,33,20,93,9
    CONTROL         "Progress1",IDC_PROGRESS,"msctls_progress32",
                    WS_BORDER,5,33,121,8
    LTEXT           "0 file(s) found",IDC_FILE_FOUND,10,46,65,8
    PUSHBUTTON      "Cancel",IDCANCEL,79,43,47,14
END
//================= End of Resource ===============================

int CreateProgressDlg ()
{
    int    nRetVal = TRUE;
    
    if(!DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_PROGRESS_DLG), g_hWnd, 
(DLGPROC)DlgProgressProc)) nRetVal = FALSE; return nRetVal; } //in your dialog box call back function you will need to do: BOOL CALLBACK DlgProgressProc (HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam) { BOOL bRtnVal = FALSE; switch (iMessage) { case WM_INITDIALOG: g_hProgDlg = hDlg; // Startup the thread to show the process PostMessage(hDlg, WM_BEGIN_PROGRESS, 0L, 0L); break; case WM_BEGIN_PROGRESS: CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)DeviceProgressThread, (LPVOID)0, 0, &g_lThreadID); break; case WM_FINISHED: EndDialog(hDlg, (int)lParam); break; } return bRtnVal; } unsigned __stdcall ProgressThreadAction (void *arg) { UNREFERENCED_PARAMETER(arg); BOOL bResult = MonitorDProgressThread (); PostMessage (g_hProgDlg, WM_FINISHED, 0L, bResult); ExitThread (bResult); return bResult; } BOOL MonitorDProgressThread () { BOOL bRtnVal = TRUE; TCHAR tchAttachments; DWORD dwUsed = 0; ULARGE_INTEGER ulBytes, ulTOfBytes, ulTOfFreeBytes; // TODO: Add your code here..... if(GetFileAttributes(TEXT("\\Storage Card")) != 0xFFFFFFFF) { if(GetDiskFreeSpaceEx(TEXT("\\Storage Card"), &ulBytesAvailable, &ulTOfBytes, &ulTOfFreeBytes)) { dwUsed+= (DWORD)ulTOfBytes.LowPart - ulBytes.LowPart; } } ..... return bRtnVal; } //End of File

November 2001/Inside the WinCE GUI/Example 1

Example 1:
The Control Panel callback function


// if _declspec(export) doesn't work for this function (ie.  The 
// control panel app does not Link to it correctly), use the DEF file 
// to export this function from the DLL. You will need a 
// File name: ControlApplet.def 
LIBRARY ControlApplet
DESCRIPTION 'ControlPanel Applet'
EXPORTS
    CplApplet   @1 

// This is what ControlApplet.cpp will look like.
// This function takes a window handle, a message and a couple of 
// message specific parameters.   
// hWnd: handle to Control Panel window
LRESULT  __stdcall CplApplet (HWND  hWnd, UINT uMsg, 
                              LPARAM lParam1, LPARAM lParam2)
{
    switch (uMsg)
   {
        case CPL_INIT:
             // The message prompts CPlApplet to perform 
             // initialization.
        break;
        case CPL_GETCOUNT:
            // sent to Control Panel app to retrieve the
            // number of dialogs supported by the application.
        break;
        case CPL_IDNAME:
             _tcscpy ((LPTSTR)lParam2, TEXT("SampleApplet"));
              lParam1 = 1;
         return (lParam2);        
         case CPL_NEWINQUIRE:
            // Control Panel sends the CPL_NEWINQUIRE message 
            // once for each Dialog box supported by your 
            // application. 
        break;
        case CPL_DBLCLK:
            // This message sent to Control Panel application 
            // when the user double-clicks the icon in the 
            // control panel explorer.
        break;
        case CPL_STOP:
             // Sent once for each dialog box when the 
             // application  controlling the
             // Control Panel application closes.
        break; 
        case CPL_EXIT:
            // Sent once to a Control Panel application before 
            // the  controlling application 
            // releases the DLL that contains the application.
        break;      
   }
}

November 2001/Inside the WinCE GUI/Example 2

Example 2:
Drawing an Up/Down button on a dialog


LRESULT DrawUpDownButton (HWND hDlg, LPDRAWITEMSTRUCT lpdis) 
{
    int   nCtrlId = lpdis->CtlID;
    int   bmpId = 0;
    int   nDraw = 0;
    int   nIncDec = -1;
    int   xPos = 0;   
    int   yPos = 0;
    int   nImgSize = 2;
    RECT  rct;

    // Set enable/disable bitmap for up/down buttons.
   switch (nCtrlId)
   {
       case IDC_LOG_UP:    
           bmpId = IDB_UP_TOOLBAR;
           break;
        case IDC_LOG_DOWN:
            bmpId = IDB_DOWN_TOOLBAR;
        break;
   }

    // ODT_BUTTON => Owner-drawn button 
    if (lpdis->CtlType == ODT_BUTTON)
    {
        SetRectEmpty (&rct);
        GetClientRect (GetDlgItem(hDlg, nCtrlId), &rct);

        if (lpdis->itemState & ODS_SELECTED)
            DrawFrameControl (lpdis->hDC, &rct, DFC_BUTTON, 
                               DFCS_BUTTONPUSH | DFCS_PUSHED);
        else
            DrawFrameControl (lpdis->hDC, &rct, 
                              DFC_BUTTON, DFCS_BUTTONPUSH);

        // This bit is set if the item has input focus. 
        If (lpdis->itemState & ODS_FOCUS) 
        {
            InflateRect (&rct, nIncDec, nIncDec);
            DrawFocusRect (lpdis->hDC, &rct); 
            InflateRect (&rct, nIncDec, nIncDec);
        } 
        else
            InflateRect (&rct, -2, -2);

        Rectangle (lpdis->hDC, rct.left, rct.top, 
                               rct.right, rct.bottom);
        InflateRect (&rct, -1, -1);

         // Before returning from processing WM_DRAWITEM message, 
         // an application should ensure that the device context 
         // identified by the hDC member of the DRAWITEMSTRUCT 
         // structure is in the default state. 

         int savedDC = SaveDC (lpdis->hDC);
         if (lpdis->itemState == ODS_DISABLED)
             nDraw = 1;

         xPos = (rct.right - rct.left)/2 - 3;
         yPos = (rct.bottom - rct.top)/2 - 3;

         if ((nCtrlId==IDC_LOG_UP)||(nCtrlId==IDC_LOG_DOWN)
         {
             g_hCLogBitMap = ImageList_LoadBitmap (
                                    g_hResInst, 
                                    MAKEINTRESOURCE (bmpId),
                                    16, 2, RGB(255, 255, 255));  
                                                                
             ImageList_Draw (g_hCLogBitMap, nDraw, lpdis->hDC, 
                                       xPos, yPos, ILD_NORMAL); 

             // Destroying the ImageList right away seems 
             // to help the memory leak a lot.
             ImageList_Remove (g_hCLogBitMap, 0);
             ImageList_Destroy (g_hCLogBitMap);
         }

        // restoring the DC.
        RestoreDC (lpdis->hDC, savedDC);
    }

    return TRUE;
}

November 2001/Inside the WinCE GUI/Figure 1

Figure 1: Pocket PC menu

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.