Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Customizing the DocList

0.00/5 (No votes)
7 Jun 2003 1  
Manipulate the Folders drop-down in a PocketPC 2002 application.

Sample Image - ce_doclist.gif

Introduction

The DocList is what you see when you launch Pocket Word or Pocket Excel or the other standard PocketPC apps. It shows a list of files, and the user can choose which folder to view via the drop-down in the top left. It's the PocketPC's answer to FileOpen.

This article shows how to customize the folder list. Here, on a whim, I made it show only those folders that have an "L" in their names, and hides the "All Folder" and "Add/Remove Folder" options. More seriously, for an app that always saves documents on SD card, you could limit it to showing SD-card folders. Or remove the silly 'Templates' and 'Annotations' folders from the list.

Background

The DocList control has some bugs in it. First, although it shows folders that exist on every storage card as well as under My Documents, it doesn't let the user select folders from anything beyond the first storage card. This is problematic on systems such as the Mitac, who consider their built-in backup flash disk to be primary and the SD card to be secondary. Doh! To be friendly to the user, you might hide all folders that are on secondary storage cards.

Second, if there is a folder of the same name in more than one location (e.g. \My Documents\Lu as well as Storage Card\Lu) then the drop-down folder list will merge the two together. But if one happens to be written with different capitalization, then they two are merged for selection purposes but are left unmerged for display purposes.

Preliminaries

This code uses the Standard Template Library (STL). Everyone should use the STL. Download the STL, ported to eVC++ by Giuseppe Govi. Unzip it into a subdirectory called stl_eVC of your project. Next, under Project > Settings > Compiler > Preprocessor, add the directory stl_eVC to the list of search paths.

The following shows which libraries to include. I have disabled warning 4018 within the STL, because it is spurious. Also, under Project > Settings > Linker > Input, link with doclist.lib and note_prj.lib. The first is for the DocList control, and the second is for enumerating folders.

#include <windows.h>
#include <doclist.h>
#include <projects.h>
#pragma warning( push )
#pragma warning( disable: 4018 )
#include <string>
#include <list>
#pragma warning( pop )
using namespace std;

HINSTANCE hInstance;
HWND hdlc=0;

The global variable hdlc is the DocList window.

Building a fake folder-list

The first problem is, although we can manipulate the DocList's drop-down folder list, we can't retrieve the names of the items. Our solution is to build our own "fake" list of what we reckon the DocList will end up containing, and in the same order. That way we won't have to manipulate the DocList blind.

// BuildProjectsList -- the goal here is to to build exactly
// the same list as the DocList will build. (We also
// annotate it to say whether each entry is valid, according
// to our criteria).

struct TProjectInfo
{ wstring fn; bool valid;  int id;
  bool operator<(const TProjectInfo &b) const
  { return _wcsicmp(fn.c_str(),b.fn.c_str())<0;
  }
};
list<TProjectInfo> dlcprojects;

The global variable dlcprojects maintains our fake list. Note that DocList sorts folders insensitive to case; we must do the same, hence the comparison operator. The variable id will be used later when we subclass the DocList window...

The following function populates the list. (It also returns the name of a valid entry in the list.)

wstring BuildProjectsList()
{ wstring validfn;
  dlcprojects.clear();
  EnumProjectsEx(EnumCallback,0,PRJ_ENUM_ALL_DEVICES,0);
  dlcprojects.sort();

  // The DocList's drop-down has some extra items:
  TProjectInfo pi; pi.valid=false;
  pi.fn=L"All Folders"; dlcprojects.push_front(pi);
  pi.fn=L"---"; dlcprojects.push_back(pi);
  pi.fn=L"Add/Delete..."; dlcprojects.push_back(pi);

  // Now we "unique" the list: if a folder exists in My Doc-
  // uments and also in a flash card, we only show it once.
  // This is in accordance with the behaviour of DocList.
  for (list<TProjectInfo>::iterator i=dlcprojects.begin();
       i!=dlcprojects.end(); i++)
  { list<TProjectInfo>::iterator j=i; j++;
    while (j!=dlcprojects.end() && (*i).fn==(*j).fn)
    { if ((*j).valid && !(*i).valid) *i=*j;
      j=dlcprojects.erase(j);
    }
    if (validfn==L"" && (*i).valid) validfn=(*i).fn;
  }
  return validfn;
}

Note the function EnumProjectsEx. In PocketPC-speak, a "project" is any immediate (non-nested) subfolder of My Documents, or any immediate subfolder of any storage card. It uses the following callback:

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

  TProjectInfo pi; 
  // Get the "display-name" (ie. without the path)
  wchar_t *c=fn, *lastslash=c;
  while (*c!=0) {if (*c=='\\') lastslash=c+1; c++;}
  pi.fn = lastslash;
  // and our arbitrary 'validity' criterion:
  pi.valid =  (wcschr(lastslash,'l')!=0);
  pi.valid |= (wcschr(lastslash,'L')!=0);
  
  dlcprojects.push_back(pi);
  return TRUE;
}

Subclassing the DocList

Next, we will subclass the DocList control. We will intercept it's Menu event, and change the menu as we see fit. We use a cunning ploy to hide the ones we don't want: namely, we intercept WM_MEASUREITEM and set their height to zero! Here is the subclass procedure:

WNDPROC OldDLCProc=0;
LRESULT CALLBACK NewDLCProc(HWND hs,UINT msg,
                            WPARAM wParam,LPARAM lParam)
{ if (msg==WM_INITMENUPOPUP)
  { BuildProjectsList();   // just to be sure we're fresh
    // We will correlate menu-item IDs with 'dlcprojects'
    LRESULT ret = CallWindowProc(OldDLCProc,hs,msg,
                                 wParam,lParam);
    HMENU hm = (HMENU)wParam; wstring ws;
    list<TProjectInfo>::iterator it=dlcprojects.begin();
    for (unsigned int pos=0; ; pos++,it++)
    { MENUITEMINFO minf; ZeroMemory(&minf,sizeof(minf));
      minf.cbSize=sizeof(minf); minf.fMask=MIIM_ID;
      BOOL res=GetMenuItemInfo(hm,pos,TRUE,&minf);
      if (!res) break;
      (*it).id=minf.wID;
    }
    return ret;
  }
  else if (msg==WM_MEASUREITEM)
  { MEASUREITEMSTRUCT *ms=(MEASUREITEMSTRUCT*)lParam;
    bool valid=false;
    for (list<TProjectInfo>::const_iterator i=
             dlcprojects.begin(); i!=dlcprojects.end(); i++)
    { if ((*i).id==(int)ms->itemID) valid=(*i).valid;
    }
    LONG ret = CallWindowProc(OldDLCProc,hs,msg,
                              wParam,lParam);
    if (!valid) {ms->itemWidth=1; ms->itemHeight=0;}
    return ret;
  } 
  return CallWindowProc(OldDLCProc,hs,msg,wParam,lParam);
}

And the following is how to install the subclass, in response to WM_CREATE of our main window (i.e. the normal place to create the DocList). Observe our choice of initial folder. That's because, in this example app, I have disallowed the "All Folders" option -- and it would be rude for the DocList to start in the default All Folders if the user couldn't subsequently select it.

case WM_CREATE:
{ wstring initf=BuildProjectsList();

  DOCLISTCREATE dlc; ZeroMemory(&dlc,sizeof(dlc));
  dlc.dwStructSize=sizeof(dlc);
  dlc.hwndParent=hwnd;
  dlc.pszFolder = initf.c_str();
  dlc.pstrFilter = L"All files\0*.*\0Text\0*.pwd;*.txt\0";
  dlc.wId=102;
  hdlc = DocList_Create(&dlc);
  OldDLCProc=(WNDPROC)SetWindowLong(hdlc,GWL_WNDPROC,
                                     (LONG)NewDLCProc);

  RECT rc; GetClientRect(hwnd,&rc);
  MoveWindow(hdlc,0,0,rc.right,rc.bottom,false);
  DocList_Refresh(hdlc);

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here