自定义 DocList
在 PocketPC 2002 应用程序中操作 "Folders" 下拉列表。
引言
当您启动 Pocket Word 或 Pocket Excel 或其他标准的 PocketPC 应用程序时,您看到的就是 DocList
。它显示文件列表,用户可以通过左上角的下拉列表选择要查看的文件夹。 它是 PocketPC 对 FileOpen 的回应。
本文介绍了如何自定义文件夹列表。在这里,我心血来潮,让它只显示名称中带有 "L" 的文件夹,并隐藏 "所有文件夹" 和 "添加/删除文件夹" 选项。 更严重的是,对于一个总是将文档保存在 SD 卡上的应用程序,您可以将其限制为只显示 SD 卡文件夹。或者从列表中删除愚蠢的“模板”和“注释”文件夹。
背景
DocList
控件存在一些错误。首先,尽管它显示了存在于每个存储卡以及 My Documents 下的文件夹,但它不允许用户从第一个存储卡之外的任何地方 选择 文件夹。这在诸如 Mitac 的系统上是很有问题的,他们认为其内置的备份闪存磁盘是主要的,而 SD 卡是次要的。 哎呀! 为了对用户友好,您可能需要隐藏所有位于次要存储卡上的文件夹。
其次,如果多个位置存在同名文件夹(例如 \My Documents\Lu 以及 Storage Card\Lu),那么下拉文件夹列表将把这两个文件夹合并在一起。但是,如果其中一个文件夹的命名方式不同,则它们会为了选择目的而合并,但为了显示目的而保持未合并状态。
准备工作
此代码使用标准模板库 (STL)。 每个人都应该使用 STL。 下载 STL,由 Giuseppe Govi 移植到 eVC++。 将其解压缩到您项目的名为 stl_eVC 的子目录中。 接下来,在 Project > Settings > Compiler > Preprocessor 下,将目录 stl_eVC 添加到搜索路径列表中。
以下显示了要包含的库。 我在 STL 中禁用了警告 4018,因为它是不真实的。 另外,在 Project > Settings > Linker > Input 下,链接 doclist.lib 和 note_prj.lib。 第一个用于 DocList
控件,第二个用于枚举文件夹。
#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;
全局变量 hdlc
是 DocList
窗口。
构建一个假文件夹列表
第一个问题是,虽然我们可以操作 DocList
的下拉文件夹列表,但我们无法检索项目的名称。 我们的解决方案是构建我们自己的“假”列表,列出我们认为 DocList
最终将包含的内容,并按相同的顺序。 这样我们就不必盲目地操作 DocList
了。
// 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;
全局变量 dlcprojects
维护我们的假列表。 请注意,DocList
对文件夹进行不区分大小写的排序; 我们必须这样做,因此使用比较运算符。 变量 id
稍后将在我们子类化 DocList
窗口时使用...
以下函数填充列表。 (它还返回列表中有效条目的名称。)
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; }
请注意函数 EnumProjectsEx
。 在 PocketPC 术语中,“项目”是 My Documents 的任何直接(非嵌套)子文件夹,或任何存储卡的任何直接子文件夹。 它使用以下回调
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; }
子类化 DocList
接下来,我们将对 DocList
控件进行子类化。 我们将拦截它的 Menu
事件,并根据需要更改菜单。 我们使用一个巧妙的策略来隐藏我们不想要的:也就是说,我们拦截 WM_MEASUREITEM
并将其高度设置为零! 这是子类化过程
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); }
以下是如何在我们的主窗口的 WM_CREATE
响应中安装子类,(即创建 DocList
的正常位置)。 观察我们对初始文件夹的选择。 这是因为,在这个示例应用程序中,我禁止了“所有文件夹”选项 - 如果用户以后无法选择它,那么 DocList
从默认的 所有文件夹 开始是不礼貌的。
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);