Click here to Skip to main content
15,400,312 members
Articles / Mobile Apps / Windows Mobile
Posted 7 Jun 2003


55 bookmarked

A prototypical PocketPC program

Rate me:
Please Sign up or sign in to vote.
3.80/5 (13 votes)
7 Jun 2003Public Domain10 min read
A complete PocketPC app in plain Win32/C++.



Most PocketPC programming is done in MFC using wizards. If instead you like to program in plain Win32/C++, you'll have found there to be not enough documentation or examples. This article and the accompanying source code provide a complete minimal example - a Notepad clone. It covers:

  • The DocList control (i.e. front page list of files, like in Pocket Word)
  • The "Card" architecture (that's what PocketPC calls an opened document window)
  • The Soft Input Panel (SIP, the keyboard/transcriber)

I haven't tried to wrap up the Windows functionality into my own API. That's because I think people want to learn the Windows API itself, not my own API. And also, the Windows API for PocketPC is rather clean and simple already. PocketPC is actually the only native implementation of Win32: it doesn't rest upon an internal layer like NT/Win2K/XP, and it doesn't rest upon 16bit like Win95/98/ME.

Program structure

PocketPC C++ programs are written using Microsoft eMbedded Visual Tools 3.0 (free). You should also download a free STL port for eVC++, by Giuseppe Govi. Although this article doesn't use it.

To write clean programs for PocketPC, you must (as Yoda says) unlearn what you have learnt:

  • Keep a small memory footprint.
  • There will never be more than one instance of your app running at a time; nor will there be more than one document open at a time.
  • Therefore, global variables are good. Per-app variables can be global. Per-document variables can be global.
  • Because the variables are declared statically, not created dynamically, it means that memory-allocation need not be done at runtime. This is a good thing.

Incidentally, the development environment often fails to build unless the "WCE Configuration" toolbar is visible. (This is the toolbar with drop-downs to select target OS, target device, target processor).


These are the header files and app-specific global variables. Also, under Project > Settings > Linker > Input, you must link against doclist.lib, aygshell.lib and note_prj.lib.

#include <windows.h>
#include <aygshell.h>   // for SIP
#include <doclist.h>    // for DocList control
#include <projects.h>   // for Folder drop-down
#include <newmenu.h>    // for New menu
#include <commctrl.h>
#include <shellapi.h>
#ifndef IDM_NEWMENUMAX        // newmenu.h was already
#include <newmenu.h>    // included in PPC2000

HINSTANCE hInstance;
HWND hmain=0;       // Main window
HWND hmbar=0;       // Menu-bar
HWND hdlc=0;        // DocList control
HWND hcard=0;       // Document window
HWND hed=0;         // Edit window inside Doc-window
wchar_t dlcfolder[MAX_PATH]={0}; // current DLC folder
bool close_on_ok=false;

The window layout: there is a main window hmain. It owns a menu-bar hmbar which sits underneath. In the client area is a DocList control hdlc and a document-window hcard occupying the same area; only one will be visible at a time. Our app is a Notepad-clone, so we have an edit-window hed inside.

The variable dlcfolder holds the currently-selected folder by the DocList. That's because, when it comes to saving a new document, this will tell us which folder to put it in.

The SHACTIVATEINFO shai is used by the system to maintain the SIP's state: there are standard calls we must make in WM_ACTIVATE and WM_SETTINGCHANGED, involving this structure, for the SIP to behave properly.

And close_on_ok is for the following reason. If the user clicked on an associated document within File Explorer and so opened our application, then the OK button should close us and return to File Explorer. But if the user opened us from the Start menu, then closing the document should return the user to the main DocList control. This flag records which mode of closure was most recently indicated.

// At the start of WinMain:

hmain = FindWindow(L"LuMainClass",L"My App");
if (hmain!=0)
{ SetForegroundWindow((HWND)((ULONG)hmain|1)); 
  // the |1 will activate any owned windows
  if (arg!=0 && wcslen(arg)!=0)
  { COPYDATASTRUCT cs; cs.dwData=0;
    cs.cbData=2+2*wcslen(arg); cs.lpData=arg;
  else SendMessage(hmain,WM_APP,0,0);
  return 0;

To save on memory, only one instance of our app should ever be loaded. When we're loaded, we check if an instance already exists. If we're loaded with a filename argument to open, we have to communicate this filename to the other instance: hence WM_COPYDATA. We use WM_APP to signal a general wakeup, whereupon some refreshes are performed.

// within WinMain, after registering classes:


hmain = CreateWindow(L"LuMainClass", L"My App", WS_VISIBLE,
    NULL, NULL, hInstance, NULL);
if (arg!=0 && wcslen(arg)!=0)
{ COPYDATASTRUCT cs; cs.dwData=0;
  cs.cbData=2+2*wcslen(arg); cs.lpData=arg;

Using SHInitExtraControls() allows us to use CAPEDIT (like a normal EDIT but it capitalizes the first letter) and also WC_SIPPREF (automatic management of the SIP).

Managing the windows

We start with creation and destruction of the application window. Note that most of our child windows need not be destroyed, since that happens automatically. The exception is the menu-bar, which must be destroyed explicitly.

{ SHMENUBARINFO mbi; ZeroMemory(&mbi,sizeof(mbi));
  mbi.hwndParent = hwnd;
  mbi.nToolBarId = 101;
  mbi.hInstRes = hInstance;
  hmbar = mbi.hwndMB;

  DOCLISTCREATE dlc; ZeroMemory(&dlc,sizeof(dlc));
  dlc.pstrFilter = L"Text\0*.txt\0";
  hdlc = DocList_Create(&dlc);

  hcard = CreateWindow(L"LuCardClass",L"",WS_CHILD,0,0,0,0,
  hed = CreateWindow(L"CAPEDIT",L"",WS_CHILD|WS_VISIBLE


  int nitems=DocList_GetItemCount(hdlc);
  if (nitems==0)
  { SendMessage(hwnd,WM_COMMAND,IDM_NEWMENUMAX+1,0);
} return 0;

{ if (! SetWindowText(hed,L"");
} return 0;

{ if ( && doc.changed) doc.Save();
} return 0;

The WM_DESTROY message is an odd case. It is never called during the normal operation of a program. That's because, if the user clicks the Close button, it doesn't close but merely minimizes the app (so that, subsequently, the app can be opened up instantly). If the system is running low on memory, it sends a WM_HIBERNATE message asking applications to clean themselves up. If it's running really low, it sends a WM_CLOSE which (in DefWindowProc) calls DestroyWindow, so finally triggering our WM_DESTROY.

The resources for the menubar are as follows. I'm writing it here because it doesn't seem to be documented elsewhere.

// A resource file for a menubar
#include <windows.h>
#include <commctrl.h>
#include <aygshell.h>

  101, 3,

This RCDATA is for a menubar. The first row (101,3) says to look up MENU#101, and that there will be three items in this menubar. In each row, the second item (IDM_SHAREDNEW, 203, 203) is the WM_COMMAND id that will be sent upon clickage. Here, "Shared New" means that it's a menu that the system will fill up with all the standard items (Task, Note, ...) and then will offer to us to fill up the rest.

   201 "DummyNew"
   202 "Tools"
   203 "Edit"

Within each row of RCDATA, the third-last item (IDS_SHNEW, 202, 203) is an index into the STRINGTABLE for the screen-names of each item. IDS_SHNEW is a system-defined string.

  POPUP ""
      MENUITEM "DummNewEntry" -1
  POPUP ""
      MENUITEM "Exit" 301

The final item in each row of RCDATA says which menu popup to choose from the MENU#101 resource. The first item (0) is actually ignored, since it is overridden by IDM_SHAREDNEW. The second item (1) points to popup index 1. And the third item (NOMENU) says that there is no popup here.

The interaction between IDM_SHAREDNEW and the popup number is a bit idiosyncratic:

  • IDM_SHAREDNEW, popup#0 -- uses the global menu, but without an arrow
  • IDM_SHAREDNEW, NOMENU -- global menu, with uparrow, and 'new' acts like a button not a menu.
  • cmd#201, popup#0 -- just our own menu, exactly like the others
  • cmd#201, NOMENU -- has the uparrow, and a button, but neither does anything


The function MakeLayout is as follows. It's job is to rearrange and refresh windows as necessary according to the current state.

// MakeLayout - this shows/hides the edit/doclist as approp-
// riate, resizes according to SIP, refreshes the DocList.
const DWORD mlShowHide=0, mlRefreshDocList=1, mlResize=2;

void MakeLayout(DWORD flags, LPARAM wmsc_lparam=0)
{ ShowWindow(hcard,;
  // the 'edit' button
  TBBUTTONINFO tbbi; ZeroMemory(&tbbi,sizeof(tbbi));
  tbbi.cbSize=sizeof(tbbi); tbbi.dwMask=TBIF_STATE;
  tbbi.fsState= (
  // readonly in the card itself
  if ( && !doc.readonly) SetFocus(hed);

Like most PocketPC apps, by default we open documents in 'readonly' mode (indicated by our edit control having ES_READONLY). The user can click the "Edit" button in the menubar to switch to editing mode.

// OK/X. If OK is hidden, then X will be visible

Windows can have either an X, or an OK, or nothing at all in their top right. Notionally the OK is "stronger" than the X: thus, if both are enabled, only the OK will be visible. It's normally to show OK when you're editing a document, and X when you're in DocList mode.

  // Now resize the windows to accommodate SIP. If this
  // is called from WM_SETTINGSCHANGE, then use the same
  // lParam for efficency; otherwise set wmsc_lparam=0
  if (flags&mlResize)
  { SIPINFO si;
    int x=si.rcVisibleDesktop.left;
    int w=si.rcVisibleDesktop.right-x;
    int h=si.rcVisibleDesktop.bottom-y;
    RECT rc; GetWindowRect(hmbar,&rc);
    if ((si.fdwFlags&SIPF_ON)) h+=1; else h-=mh-1;
  // We need to refresh after files have been changed,
  // to reshow their time/size
  if (! && (flags&mlRefreshDocList))
  { DocList_Refresh(hdlc);

It is costly to get the SIP info and also to refresh the DocList. That's why we control both by flags, to be done only if necessary.

Activation and commands

The responses to WM_ACTIVATE and WM_SETTINGCHANGE are standard, required by the system to handle the SIP. Also, we call MakeLayout, to accommodate the new size of the SIP:

{ SHHandleWMActivate(hwnd, wParam, lParam, &shai, FALSE);
} return 0;

{ SHHandleWMSettingChange(hwnd, wParam, lParam, &shai);
} return 0;

Also, when our app was running in the background but another instance was launched, then we have to wake up the old instance: either WM_APP if we just need to activate it, or WM_COPYDATA if we need to get it to open a document.

case WM_APP:
{ // we are a background instance being reactivated
  // maybe have to refresh the doclist.
  if (! MakeLayout(mlRefreshDocList);
} return 0;

{ // we are a background instance being told to open a doc.
  if ( && doc.changed) doc.Save();
} return 0;

And this is the code to respond to clicks on the menubar and on the OK button:

{ int id=LOWORD(wParam);
  if (id==IDOK && // OK button
  { if ( && doc.changed) doc.Save();
    SHSipPreference(hmain,SIP_FORCEDOWN);; MakeLayout(mlShowHide|mlRefreshDocList);
    if (close_on_ok) ShowWindow(hwnd,SW_MINIMIZE);

The OK button means that the user has finished editing the current document. That's why we save it. We also force down the SIP. That's because we know the next thing that the user sees will be the DocList control, and we know that this doesn't need the SIP: without the force-down, the SIP would wait two seconds before hiding.

Note also the close_on_ok. If the user had opened up most recently from an outside source (e.g. within File Explorer, clicked on an app associated with our app), then a subsequent click on OK should return straight to File Explorer.

In the following, we respond to a menu item Tools > Exit, by terminating. I include this for debugging purposes. The menu item should be removed in the release version of the code.

  // ... continued from WM_COMMAND

  else if (id==301) // tools.exit
  { DestroyWindow(hwnd); 
  else if (id==203) // tools.edit
  { if ( && doc.readonly)
    { doc.readonly=false; MakeLayout(mlShowHide);
  else if (id==IDM_NEWMENUMAX+1) // new.text
  { if ( && doc.changed) doc.Save();
    doc.New(); MakeLayout(mlShowHide);
} return 0;

{ NMHDR *nmhdr = (NMHDR*)lParam;
  if (nmhdr->code==NMN_GETAPPREGKEY)
  { // Called by the system for us to fill in our New menu.
    NMNEWMENU *nmnew = (NMNEWMENU*)lParam;
    AppendMenu(nmnew->hMenu, MF_ENABLED,
               IDM_NEWMENUMAX+1, L"Text");
    AppendMenu(nmnew->hMenu, MF_SEPARATOR, 0, 0);
  else if (nmhdr->code==DLN_ITEMACTIVATED)
  { DLNHDR *dln = (DLNHDR*)lParam;
  else if (nmhdr->code==DLN_FOLDER)
  { DLNHDR *dln = (DLNHDR*)lParam;
} return 0;

The response to DLN_FOLDER means that the user has clicked on a folder from the DocList's drop-down folder list. We call our own function (below) to retrieve this folder's full path. That's so that, when we subsequently save a new document, we can save it into the place the user was looking at. We need a separate function because dln->pszPath merely gives the folder name, not its full path.

The are bugs in the DocList control. It's meant to display folders from My Documents and every storage card, and it does, but it only allows the user to select folders from the first storage card or My Documents - secondary storage cards don't work. This is problematic on some devices which have a permanent built-in primary storage card, and external SDs count as secondary! Another problem is that, if a folder "Name" exists in two different locations, it collapses them into just one entry -- unless they happen to have different capitalizations, in which case it collapses them for some purposes (selection) but not for other purposes (display).

// GetPathForFolder(const wchar_t *folder, wchar_t *path):
// Given a folder name we return its path
//   All Folders  -->  \My Documents
//   Business     -->  \My Documents\Business
//   MySdFolder   -->  \Storage Card\MySdFolder
// Assume path>=MAX_PATH.

struct EnumProjectsInfo
{ const wchar_t *folder; wchar_t *path;

BOOL CALLBACK EnumCallback(PAstruct *pa, LPARAM lp)
{ wchar_t *fn; 
  if (pa->m_IDtype!=FILE_ID_TYPE_OID) fn=pa->m_szPathname;
  { CEOIDINFO cinf; 
    fn = cinf.infDirectory.szDirName;
  const wchar_t *c=fn, *lastslash=c;
  while (*c!=0) {if (*c=='\\') lastslash=c+1; c++;}
  EnumProjectsInfo *epi = (EnumProjectsInfo*)lp;
  if (wcsicmp(epi->folder,lastslash)!=0) return TRUE;
  // TRUE means keep enumerating
  wcscpy(epi->path,fn); return FALSE;

void GetPathForFolder(const wchar_t *folder, wchar_t *path)
{ // assume 'path' is at least MAX_PATH.
  EnumProjectsInfo epi; epi.folder=folder; epi.path=path;
  if (*path==0) SHGetSpecialFolderPath(NULL,epi.path,

The document

Our application is a version of Notepad. So, our document window is basically an EDIT control. (Actually, a CAPEDIT, which is unique to PocketPC and which capitalizes the first character). Here's how we represent our document:

struct TDocument
{ wchar_t fn[MAX_PATH];
  bool open, changed, readonly;
  bool unicode;

  TDocument() : open(false) {*fn=0;}
  void New();
  bool Open(const wchar_t *ofn);
  void Save();
} doc;

Note at the bottom: we declare a global static variable doc, for our document. Global variables make PocketPC applications much easier and cleaner. Most of the fields are obvious:

  • open indicates whether there is an open document (and hence whether the rest of the flags are meaningful)
  • readonly is because existing documents are normally opened in read-only mode; the user has to click Edit to start altering them
  • changed says whether any changes have been made (and hence have to be saved)
  • fn is the name of the file, if we opened an existing one, empty otherwise.
  • unicode says whether this file should be saved to disk in Unicode format. Unicode is native for PocketPC. But if we had opened an ASCII document, it would be rude to convert it.
                             WPARAM wParam, LPARAM lParam)
{ if (msg==WM_COMMAND)
  { int id=LOWORD(wParam), code=HIWORD(wParam);
    if (id==104 && code==EN_CHANGE) doc.changed=true;
  return DefWindowProc(hwnd,msg,wParam,lParam);

Most of the fields are set by the New/Open/Save methods, but changed is also set when there have been any changes to the edit control, above.

void TDocument::New()
{ *fn=0; SetWindowText(hed,L"");
  open=true; changed=false; readonly=false; unicode=true;

bool TDocument::Open(const wchar_t *ofn)
{ HANDLE hf=CreateFile(ofn,GENERIC_READ,0,
  if (hf==INVALID_HANDLE_VALUE) return false;
  DWORD size=GetFileSize(hf,0); char *buf=new char[size+2];
  DWORD red; ReadFile(hf,buf,size,&red,NULL);
  buf[size]=0; buf[size+1]=0;

The interesting functionality in Open is to detect whether the file is Unicode or not. If it starts with the Unicode Byte-Order-Mark (BOM), we know it's Unicode. Otherwise, we'll guess, based on whether the first 256 bytes look ASCII-ish or not. That's because many systems save Unicode files without the BOM.

  // Unicode detector:
  wchar_t *unc=0;
  if (buf[0]==-1 && buf[1]==-2) unc=(wchar_t*)(buf+2);
  { for (unsigned int i=3; i<256 && i<size && unc==0; i+=2)
    { if (buf[i]==0) unc=(wchar_t*)buf;
  unicode = (unc!=0);    
  if (unicode) SetWindowText(hed,unc);
  { unc = new wchar_t[size];
    delete[] unc;
  open=true; changed=false; readonly=true;
  return true;

As for Save(), its complication is that it might have to auto-generate a filename for a newly created document. That's the way of things in the PocketPC: trouble the user as little as possible with pesky things like FileOpen or FileSave. We make use of a trivial function isalphanum(), which is defined below:

void TDocument::Save()
{ if (!open || !changed) return;
  int len=GetWindowTextLength(hed); if (len==0) return;
  wchar_t *unc=new wchar_t[len+1];

  // To construct the filename from the doc's content...
  if (*fn==0)
  { wcscpy(fn,dlcfolder); wchar_t *d=fn+wcslen(fn);
    *d='\\'; d++; bool any=false;
    int i=0; wchar_t *c=unc;
    for (; !isalphanum(*c) && *c!=0 && i<32; i++) {}
    for (; isalphanum(c[i]) && c[i]!=0 && i<32; i++)
    { *d=c[i]; d++; any=true;
    if (!any) {wcscpy(d,L"unnamed"); d+=wcslen(d);}
    for (i=1; GetFileAttributes(fn)!=0xFFFFFFFF; i++)
    { wsprintf(d,L" (%i).txt",i);

There's a bug in PocketPC's handling of SD cards. Every now and again, like one time in 400, it simply fails to save a file onto the card -- even though all the functions report success. I don't know what to do about it. One idea is to use FILE_FLAG_WRITE_THROUGH to avoid lazy write caching. Another is to re-open the file after saving it (maybe even 30 seconds after saving it) to check whether it worked.

  HANDLE hf = CreateFile(fn,GENERIC_WRITE,0,NULL,
  if (unicode)
  { const char byteorder[2]={-1,-2};
    DWORD writ; WriteFile(hf,byteorder,2,&writ,NULL);
  { int size = WideCharToMultiByte(CP_ACP,WC_DEFAULTCHAR,
    char *buf = new char[size];
    DWORD writ; WriteFile(hf,buf,size,&writ,NULL);
    delete[] buf;
  delete[] unc;

bool isalphanum(const wchar_t c)
{ if (c>='a' && c<='z') return true;
  if (c>='A' && c<='Z') return true;
  if (c>='0' && c<='9') return true;
  if (c==' ' || c=='-') return true;
  return false;

The function isalphanum() assists in auto-constructing save filenames. Our idea: considering up to the first 32 characters, use the first span of alphanumeric characters as the filename.


If anyone posts a complaint that the code doesn't compile, with a stdafx.h error, they had better post anonymously -- otherwise I will track them down and bite their heads off. To avoid decapitation, read your compiler documentation on Project Settings > Compiler > Precompiled Headers.


  • 10 June 2003 - Now, also compiles under PPC2000 (as per Mike Dimmick's suggestion) and under PPC2003 (with eVC4).


This article, along with any associated source code and files, is licensed under A Public Domain dedication


About the Author

Technical Lead
United States United States
Lucian studied theoretical computer science in Cambridge and Bologna, and then moved into the computer industry. Since 2004 he's been paid to do what he loves -- designing and implementing programming languages! The articles he writes on CodeProject are entirely his own personal hobby work, and do not represent the position or guidance of the company he works for. (He's on the VB/C# language team at Microsoft).

Comments and Discussions

General[Message Deleted] Pin
it.ragester28-Mar-09 5:32
Memberit.ragester28-Mar-09 5:32 
GeneralSneaky bug... Pin
s_tec8-Jan-07 12:04
Members_tec8-Jan-07 12:04 
GeneralEmulator Pin
WillianBR7-Jul-06 12:43
MemberWillianBR7-Jul-06 12:43 
GeneralHelp Required!! Pin
Sreekanth Muralidharan7-Dec-05 23:41
MemberSreekanth Muralidharan7-Dec-05 23:41 
Help Required..

I have installed VS .NET 2005 and PocketPC and Smartphone SDKs on my
128MB RAM machine and the installation was successful let alone for
some nasty messages issued by Norton AV. But while I deploy my small
"Hello World" application to the emulator, I get a "Deploy Succeeded"
message. But still I could not see the changes I have made in the
application running in the emulator. Please help me.
My system configuration is as follows. (I think I have only the minimum

My emulator settings are:

Pocket PC Emulator:
Name: Windows Mobile 5.0 Pocket PC Emulator.
Platform: Windows Mobile 5.0 Pocket PC SDK.
Default Output Location: /Program Files
Transport: TCP Connect Transport
Bootstrapper: Device Emulation Startup Provider.

Emulator Options:
Selected Specify RAM size option: as 256

All the other fields remain empty.

Smartphone Emulator:
Name: Windows Mobile 5.0 Smartphone Emulator.
Platform: Windows Mobile Smartphone SDK.
Default Output Location: /Program Files
Transport: TCP Connect Transport
Bootstrapper: Device Emulation Startup Provider.

Emulator Options:
Selected Specify RAM size option: as 128

All the other fields remain empty.

CPU: Pentium III
RAM: 128MB

OS: Win2K Professional with IE 6.0
Others: Office 2K

Sreekanth Muralidharan,
Corporate Systems Consultant [Embedded Systems],
GeneralPost code that compiles! Pin
Mike Dimmick9-Jun-03 2:10
MemberMike Dimmick9-Jun-03 2:10 
GeneralRe: Post code that compiles! Pin
ljw10049-Jun-03 2:44
Memberljw10049-Jun-03 2:44 
GeneralRe: Post code that compiles! Pin
Mike Dimmick9-Jun-03 3:11
MemberMike Dimmick9-Jun-03 3:11 
GeneralRe: Post code that compiles! Pin
ljw100410-Jun-03 2:59
Memberljw100410-Jun-03 2:59 
GeneralRe: Post code that compiles! Pin
LunaticFringe7-Jul-09 11:48
MemberLunaticFringe7-Jul-09 11:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.