Click here to Skip to main content
15,897,704 members
Articles / Mobile Apps / Windows Mobile

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 117.9K   276   55  
A complete PocketPC app in plain Win32/C++.
#include <windows.h>
#include <aygshell.h> // link against aygshell.lib
#include <doclist.h>  // link against doclist.lib
#include <projects.h> // link against note_prj.lib
#ifndef IDM_NEWMENUMAX
#include <newmenu.h>  // under PPC2000, it was already included in aygshell
#endif
#include <commctrl.h> 
#include <shellapi.h>


HINSTANCE hInstance;
HWND hmain=0;
HWND hcard=0;
HWND hdlc=0;
HWND hmbar=0;
HWND hed=0;
wchar_t dlcfolder[MAX_PATH]={0}; // the full path of the current DLC folder
SHACTIVATEINFO shai={sizeof(SHACTIVATEINFO),0,0,0,0,0};
bool close_on_ok=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;
}

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

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


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

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);
  // We may have 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);
  }
  // really, if it's being saved on SD, we should use FILE_FLAG_WRITE_THROUGH
  // to hopefully prevent data loss due to buggy SD card filesystems.
  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 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);
  // if there's a BOM we'll know it's unicode. If not, we'll guess, based on
  // whether the first 256bytes look ascii-ish or not.
  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;
}






// MakeLayout - this shows/hides the edit/doclist as appropriate,
// resizes according to the SIP, and 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);
  // OK/X. When we hide the OK, that lets the X underneath show up
  SHDoneButton(hmain, doc.open?SHDB_SHOW:SHDB_HIDE);
  // Now resize the windows to accommodate SIP. If this is called from
  // WM_SETTINGSCHANGE, then use the same lParam for efficency; otherwise 0
  if (flags&mlResize)
  { SIPINFO si; SHSipInfo(SPI_GETSIPINFO,wmsc_lparam,&si,0);
    int x=si.rcVisibleDesktop.left, y=si.rcVisibleDesktop.top;
    int w=si.rcVisibleDesktop.right-x, 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);
}




// GetPathForFolder(const wchar_t *folder, wchar_t *path):
// Given a folder name we return its path (assume path>=MAX_PATH).
//   All Folders  -->  \My Documents
//   Business     -->  \My Documents\Business
//   MySdFolder   -->  \Storage Card\MySdFolder
//
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; // 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);
}




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);
}



LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
  switch (msg)
  { 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_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);
      }
      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_ACTIVATE:
    { SHHandleWMActivate(hwnd, wParam, lParam, &shai, FALSE);
    } return 0;
    case WM_SETTINGCHANGE:
    { SHHandleWMSettingChange(hwnd, wParam, lParam, &shai);
      MakeLayout(mlResize,lParam);
    } 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;

    case WM_APP:
    { // this messages gets sent to activate a previous instance
      close_on_ok=false;
      // maybe have to refresh the doclist.
      if (!doc.open) MakeLayout(mlRefreshDocList);
    } return 0;

    case WM_COPYDATA:
    { // this message gets sent to open a document.
      close_on_ok=true;
      // it might be sent by a new process to an existing one - hence
      // the need for WM_COPYDATA to share data between processes.
      if (doc.open && doc.changed) doc.Save();
      COPYDATASTRUCT *cs = (COPYDATASTRUCT*)lParam;
      doc.Open((wchar_t*)cs->lpData);
      MakeLayout(mlShowHide);
    } return 0;

    case WM_NOTIFY:
    { NMHDR *nmhdr = (NMHDR*)lParam;
      // This first is called by the menubar for the "shared new"
      if (nmhdr->code==NMN_GETAPPREGKEY)
      { 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_FOLDER)
      { DLNHDR *dln = (DLNHDR*)lParam;
        GetPathForFolder(dln->pszPath,dlcfolder);
      }
      else if (nmhdr->code==DLN_ITEMACTIVATED)
      { DLNHDR *dln = (DLNHDR*)lParam;
        doc.Open(dln->pszPath);
        MakeLayout(mlShowHide);
      }
    } return 0;
  }
  return DefWindowProc(hwnd,msg,wParam,lParam);
}



int WINAPI WinMain(HINSTANCE h, HINSTANCE, LPTSTR arg, int nCmdShow)
{ hInstance=h;

  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;
  }


  WNDCLASS wc; ZeroMemory(&wc,sizeof(wc));
  wc.lpfnWndProc = MainWndProc;
  wc.hInstance = hInstance;
  wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
  wc.lpszClassName= L"LuMainClass";
  RegisterClass(&wc);
  //
  wc.lpfnWndProc = CardWndProc;
  wc.lpszClassName = L"LuCardClass";
  RegisterClass(&wc);
  //
  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);
  }


  MSG msg;
  while (GetMessage(&msg,NULL,0,0)) 
  { TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  return msg.wParam;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
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