|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionMost 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:
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 structurePocketPC 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:
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). PreliminariesThese 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 #endif 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 SHACTIVATEINFO shai={sizeof(SHACTIVATEINFO),0,0,0,0,0}; bool close_on_ok=false; The window layout: there is a main window The variable The And // 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; SendMessage(hmain,WM_COPYDATA,NULL,(LPARAM)&cs); } 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 // within WinMain, after registering classes: SHInitExtraControls(); hmain = CreateWindow(L"LuMainClass", L"My App", WS_VISIBLE, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hmain,nCmdShow); UpdateWindow(hmain); if (arg!=0 && wcslen(arg)!=0) { COPYDATASTRUCT cs; cs.dwData=0; cs.cbData=2+2*wcslen(arg); cs.lpData=arg; SendMessage(hmain,WM_COPYDATA,NULL,(LPARAM)&cs); } Using Managing the windowsWe 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. case WM_CREATE: { SHMENUBARINFO mbi; ZeroMemory(&mbi,sizeof(mbi)); mbi.cbSize=sizeof(mbi); mbi.hwndParent = hwnd; mbi.nToolBarId = 101; mbi.hInstRes = hInstance; SHCreateMenuBar(&mbi); hmbar = mbi.hwndMB; SHGetSpecialFolderPath(hwnd,dlcfolder,CSIDL_PERSONAL,TRUE); DOCLISTCREATE dlc; ZeroMemory(&dlc,sizeof(dlc)); dlc.dwStructSize=sizeof(dlc); dlc.hwndParent=hwnd; dlc.pstrFilter = L"Text\0*.txt\0"; dlc.wId=102; hdlc = DocList_Create(&dlc); hcard = CreateWindow(L"LuCardClass",L"",WS_CHILD,0,0,0,0, hwnd,(HMENU)103,hInstance,0); hed = CreateWindow(L"CAPEDIT",L"",WS_CHILD|WS_VISIBLE |WS_HSCROLL|WS_VSCROLL|ES_AUTOHSCROLL |ES_AUTOVSCROLL|ES_MULTILINE|WS_TABSTOP,0,0,0,0, hcard,(HMENU)104,hInstance,0); CreateWindow(WC_SIPPREF,L"",WS_CHILD,0,0,0,0, hcard,(HMENU)105,hInstance,0); MakeLayout(mlShowHide|mlRefreshDocList|mlResize,0); int nitems=DocList_GetItemCount(hdlc); if (nitems==0) { SendMessage(hwnd,WM_COMMAND,IDM_NEWMENUMAX+1,0); } } return 0; case WM_HIBERNATE: { if (!doc.open) SetWindowText(hed,L""); } return 0; case WM_DESTROY: { if (doc.open && doc.changed) doc.Save(); CommandBar_Destroy(hmbar); PostQuitMessage(0); } return 0; The 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 RCDATA MOVEABLE BEGIN 101, 3, I_IMAGENONE, IDM_SHAREDNEW, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_SHNEW, 0, 0, I_IMAGENONE, 202, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, 202, 0, 1, I_IMAGENONE, 203, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 203, 0, NOMENU, END This STRINGTABLE DISCARDABLE BEGIN 201 "DummyNew" 202 "Tools" 203 "Edit" END Within each row of 101 MENU DISCARDABLE BEGIN POPUP "" BEGIN MENUITEM "DummNewEntry" -1 END POPUP "" BEGIN MENUITEM "Exit" 301 END END The final item in each row of The interaction between
MakeLayoutThe function // 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, doc.open?SW_SHOW:SW_HIDE); ShowWindow(hdlc, doc.open?SW_HIDE:SW_SHOW); // the 'edit' button TBBUTTONINFO tbbi; ZeroMemory(&tbbi,sizeof(tbbi)); tbbi.cbSize=sizeof(tbbi); tbbi.dwMask=TBIF_STATE; tbbi.fsState= (doc.open&&doc.readonly)?TBSTATE_ENABLED :TBSTATE_HIDDEN; SendMessage(hmbar,TB_SETBUTTONINFO,203,(LPARAM)&tbbi); // readonly in the card itself SendMessage(hed,EM_SETREADONLY,doc.readonly?TRUE:FALSE,0); if (doc.open && !doc.readonly) SetFocus(hed); Like most PocketPC apps, by default we open documents in 'readonly' mode (indicated by our edit control having // OK/X. If OK is hidden, then X will be visible SHDoneButton(hmain, doc.open?SHDB_SHOW:SHDB_HIDE); 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 // 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; SHSipInfo(SPI_GETSIPINFO,wmsc_lparam,&si,0); int x=si.rcVisibleDesktop.left; int y=si.rcVisibleDesktop.top; int w=si.rcVisibleDesktop.right-x; int h=si.rcVisibleDesktop.bottom-y; RECT rc; GetWindowRect(hmbar,&rc); int mh=rc.bottom-rc.top; if ((si.fdwFlags&SIPF_ON)) h+=1; else h-=mh-1; MoveWindow(hmain,x,y,w,h,FALSE); GetClientRect(hmain,&rc); MoveWindow(hcard,0,0,rc.right,rc.bottom,FALSE); MoveWindow(hdlc,0,0,rc.right,rc.bottom,TRUE); MoveWindow(hed,0,0,rc.right,rc.bottom,FALSE); } // We need to refresh after files have been changed, // to reshow their time/size if (!doc.open && (flags&mlRefreshDocList)) { DocList_Refresh(hdlc); } } It is costly to get the SIP info and also to refresh the Activation and commandsThe responses to case WM_ACTIVATE: { SHHandleWMActivate(hwnd, wParam, lParam, &shai, FALSE); } return 0; case WM_SETTINGCHANGE: { SHHandleWMSettingChange(hwnd, wParam, lParam, &shai); MakeLayout(mlResize,lParam); } 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 case WM_APP: { // we are a background instance being reactivated close_on_ok=false; // maybe have to refresh the doclist. if (!doc.open) MakeLayout(mlRefreshDocList); } return 0; case WM_COPYDATA: { // we are a background instance being told to open a doc. if (doc.open && doc.changed) doc.Save(); close_on_ok=true; COPYDATASTRUCT *cs = (COPYDATASTRUCT*)lParam; doc.Open((wchar_t*)cs->lpData); MakeLayout(mlShowHide); } return 0; And this is the code to respond to clicks on the menubar and on the OK button: case WM_COMMAND: { int id=LOWORD(wParam); if (id==IDOK && doc.open) // OK button { if (doc.open && doc.changed) doc.Save(); SHSipPreference(hmain,SIP_FORCEDOWN); doc.open=false; 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 Note also the 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.open && doc.readonly) { doc.readonly=false; MakeLayout(mlShowHide); } } else if (id==IDM_NEWMENUMAX+1) // new.text { if (doc.open && doc.changed) doc.Save(); doc.New(); MakeLayout(mlShowHide); } } return 0; case WM_NOTIFY: { 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; doc.Open(dln->pszPath); MakeLayout(mlShowHide); } else if (nmhdr->code==DLN_FOLDER) { DLNHDR *dln = (DLNHDR*)lParam; GetPathForFolder(dln->pszPath,dlcfolder); } } return 0; The response to The are bugs in the // 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; else { CEOIDINFO cinf; CeOidGetInfo(pa->m_fileOID,&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. *path=0; EnumProjectsInfo epi; epi.folder=folder; epi.path=path; EnumProjectsEx(EnumCallback,0,PRJ_ENUM_ALL_DEVICES, (LPARAM)&epi); if (*path==0) SHGetSpecialFolderPath(NULL,epi.path, CSIDL_PERSONAL,FALSE); } } The documentOur application is a version of Notepad. So, our document window is basically an 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
LRESULT CALLBACK CardWndProc(HWND hwnd, UINT msg,
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 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, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); 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; CloseHandle(hf); The interesting functionality in // Unicode detector: wchar_t *unc=0; if (buf[0]==-1 && buf[1]==-2) unc=(wchar_t*)(buf+2); else { 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); else { unc = new wchar_t[size]; MultiByteToWideChar(CP_ACP,0,buf,-1,unc,size); SetWindowText(hed,unc); delete[] unc; } wcscpy(fn,ofn); open=true; changed=false; readonly=true; return true; } As for void TDocument::Save() { if (!open || !changed) return; int len=GetWindowTextLength(hed); if (len==0) return; wchar_t *unc=new wchar_t[len+1]; GetWindowText(hed,unc,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);} wcscpy(d,L".txt"); 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 HANDLE hf = CreateFile(fn,GENERIC_WRITE,0,NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); if (unicode) { const char byteorder[2]={-1,-2}; DWORD writ; WriteFile(hf,byteorder,2,&writ,NULL); WriteFile(hf,unc,sizeof(wchar_t)*len,&writ,NULL); } else { int size = WideCharToMultiByte(CP_ACP,WC_DEFAULTCHAR, unc,-1,0,0,NULL,NULL); char *buf = new char[size]; WideCharToMultiByte(CP_ACP,WC_DEFAULTCHAR, unc,-1,buf,size,NULL,NULL); DWORD writ; WriteFile(hf,buf,size,&writ,NULL); delete[] buf; } CloseHandle(hf); delete[] unc; changed=false; } 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 PostscriptIf 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. History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||