📄 howto.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<TITLE>File: HOWTO.html</TITLE>
<META NAME="GENERATOR" CONTENT="Mozilla/3.01Gold (Win95; I) [Netscape]">
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<P><A NAME="How"></A></P>
<H3 ALIGN=CENTER><U>如何使用Delphi2.01编写一个Desktop Menu</U><BR>
</H3>
<UL>
<LI><B><A HREF="#0">内容简介</A></B></LI>
<LI><B><A HREF="#1">第一步:建立Project</A></B></LI>
<LI><B><A HREF="#2">第二步:建立Desktop Popup Menu</A></B></LI>
<LI><B><A HREF="#3">第三步:获得Desktop items</A></B></LI>
<UL>
<LI><B><A HREF="#31">(一)使用FindFirst/FinNext收集Desktop items</A></B></LI>
<UL>
<LI><B><A HREF="#311">1)获得Desktop的物理路径</A></B></LI>
<LI><B><A HREF="#312">2)向DesktopMenu中填充Desktop items</A></B></LI>
</UL>
<LI><B><A HREF="#32">(二)使用FindFirst/FinNext带来的问题</A></B></LI>
<LI><B><A HREF="#33">(三)使用IShellFolder收集Desktop items</A></B></LI>
<UL>
<LI><B><A HREF="#331">1)获得IShellFolder对象(object)</A></B></LI>
<LI><B><A HREF="#332">2)用IShellFolder来填充DesktopMenu的items</A></B></LI>
</UL>
</UL>
<LI><B><A HREF="#4">第四步:运行得到的item</A></B></LI>
<UL>
<LI><B><A HREF="#41">(一)使用ShellExecuteEx运行item的基础知识</A></B></LI>
<LI><B><A HREF="#42">(二)存储PItemIDList和FileInfo</A></B></LI>
<LI><B><A HREF="#43">(三)完善FillMenuItemsFromShellFolder</A></B></LI>
<LI><B><A HREF="#44">(四)运行</A></B></LI>
</UL>
<LI><B><A HREF="#5">第五步:在Tray Icon中添加Icon</A></B></LI>
<LI><B><A HREF="#6">第六步:处理WM_TRAYICON和WM_SHOWMENU消息</A></B></LI>
<LI><B><A HREF="#7">第七步:隐藏MainForm</A></B></LI>
<LI><B><A HREF="#8">作者的话</A></B></LI>
</UL>
<PRE><A NAME="0"></A><B>内容简介:
</B>本文采用 <B>Step by step</B> 方式, 通俗易懂地叙述了一个<B>"Desktop Menu"</B>应用程序
的编写过程。所谓的<B>"Desktop Menu"</B>是在windows95 taskbar上的 Notify Tray
中放上一个ICON, 在用mouse点按时可以Popup出包括所有 desktop Items 的menu,
并能运行这些Items的应用程序。其中不仅涉及到关于Windows95 shell API的应用,
Delphi Component 的动态建立等编程技术, 还阐明了一些结构化编程的观点。
多说无益, 直接进入正题.
</PRE>
<PRE><A NAME="1"></A><B>第一步:建立Project
</B> 从Delphi主菜单中选择 File | New Application, 让Delphi生成一个新Project
在 Object Inspector 窗口中将 Form1 的 Name porperty 中写入 MainForm
然后选择 File Save As 保存 Project, 当 Save As Dialog 出现后, 输入
UntMain 做为主单元文件名, 按Save保存。
<B>Tip: </B>在做AP时, 为所有的From, <B>unit</B>起一个有意义的名字, 可提高程序的可读性。
<A NAME="2"></A><B>第二步:建立Desktop Popup Menu
</B> 在Delphi Component Palette 的Standard Tabset中选择PopupMenu Icon并将其
放入MainForm中, 在MainForm中用mouse点取新放入的PopupMenu component并在
Object Inspector 窗口中将其更名为DesktopMenu为了能够自动显示它, 将其指
定给 MainForm 的 PopupMenu property。
<B>Tip:
</B> 1)在Delphi Component Palette 的icon上双击mouse左键可以直接将所选
component放入当前的Form中。
2)在点选Palette icon时按住shift, Delphi进入一种连续place component状态,
此时每当在Form上按mouse左键时, 都会在Form上添加一个新component。
要取消这种状态可以用mouse左键按Palette左侧的Arrow Icon。
<A NAME="3"></A><B>第三步:获得Desktop items
</B> 接下来我们要考虑的是如何将Windows 95桌面上的项目收集到我们的DesktopMenu中!
这也是本篇文章中我们需要讨论的关键问题之一。
<A NAME="31"></A><B>(一)使用FindFirst/FinNext收集Desktop items
</B> 通常我们会想到采用FindFirst/FindNext这两个Pascal函数, 将Win95\Desktop
子目录下的文件找出来。
好, 我们现在就来试一下。
<A NAME="311"></A><B>1)获得Desktop的物理路径
</B> 首先我们遇到的问题是“如何取得Desktop所在的子目录”,很明显,所有细心的人
都会注意到它处在 Windows 95 所在目录下的Desktop子目录中。但事情有时不象
我们所预期的那样。请注意,这只是通常的情况,假设某位“高手”修改了系统的
配置, 将其另易其地或Microsoft在未来的Windows版本中不再采用Desktop这个
子目录, 那又会发生什么?有一点可以肯定,得到的并不是我们想要的。写程序时
应尽可能采取最通用的处理方法,这样可使程序获得较好的适应性并大幅度地降低
程序的维护量,是一举多得的做法。从Windows 95 的Registry中,可以得到Desktop
子目录的路径, 在Delphi 2.01的ShlObj单元中定义了
REGSTR_PATH_SPECIAL_FOLDERS 的常量, 这正是我们所要的!
既然存取Windows 95的 Registry 就要用到 Registry Unit
在<B>uses</B>段中加入 Registry, ShellApi, ShlObj
其中ShellApi由于以后的编程中需要用到所以一起加入。
接下来为MainForm添加一个新的<B>private</B>方法 <B>procedure</B> FillMenuItemsFromFileList;
<B>Tip</B>:在代码中善用一些注释。
<B>procedure</B> TMainForm.FillMenuItemsFromFileList;
<B>var
</B> Reg: TRegistry;
DesktopPath: <B>string</B>;
<B>begin
</B> DesktopPath := '';
<B>with</B> TRegistry.Create <B>do
</B> <B>try
</B> <I><FONT COLOR="#000080">//打开REGSTR_PATH_SPECIAL_FOLDERS Key 并读取其值
</FONT></I> <B>if</B> OpenKey(REGSTR_PATH_SPECIAL_FOLDERS, False) <B>then
</B> DesktopPath:= ReadString('Desktop')
<B>else
</B> Application.MessageBox('无法打开KEY:'+REGSTR_PATH_SPECIAL_FOLDERS,
'Open Registry Key Error', MB_ICONSTOP <B>or</B> MB_OK);
<B>finally
</B> <I><FONT COLOR="#000080">//确保释放TRegistry.Create所分配的内存
</FONT></I> Free;
<B>end</B>;
<I><FONT COLOR="#000080">//失败, MainForm.Caption 保持不变
</FONT></I> <B>if</B> DesktopPath = '' <B>then</B> Exit;
Caption := DesktopPath;
<B>end</B>;
在MainForm的OnCreate Event 中写入下列代码:
<B>procedure</B> TMainForm.FormCreate(Sender: TObject);
<B>begin
</B> FillMenuItemsFromFileList;
<B>end</B>;
然后运行Project, Ohhhh! Have a Error! MessageBox出现了! 同时我们注意到
MessageBox所报告的键串为
'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\Shell Folders'
不难得出结论问题出在'\\'上, 串的一部分是C的语法, 这可以说是Delphi 2.01
的一个<B>Bug!</B> 在ShlObj中定义的REGSTR_PATH_SPECIAL_FOLDERS为
REGSTR_PATH_EXPLORER + '\Shell Folders' 而REGSTR_PATH_EXPLORER定义在
RegStr.pas Unit中, 用Delphi IDE打开RegStr.Pas(位于Source\RTL\WIN下),
发现REGSTR_PATH_等串的定义是依照C语法实现的(Borland 可能忽略了这个问题)。
解决的方法有两个, 一个是修改RegStr.Pas中的内容并重新编译Delphi的Lib,
另一个就是自己定义。为了避免其它类似的程序也可能遇到相同的问题我采取了
前面的做法。(PS:Delphi 2.01 的 Shlobj 中还存在一些其它BUG! 但 Delphi3
中都进行了纠正, 请参考WEB上的相关讨论)
打开位于 Source\RTL\WIN 下的 Regstr.pas , 使用 '\' 替换所有的 '\\',
从Component Menu下选择Install..., 在Search path中添加上述路径和
'Source\VCL'路径, click "OK" 按钮以重新编译整个VCL。编译完成后,
将在上述路径中生成的'DCU'文件移到'Lib'目录下, 覆盖原有的同名'DCU'文件,
最后将Search path中的新添加部分删除, 以避免在Project每次Build All时
对这些文件的重复编译并可避免在Debug时误入其中。
重新运行Project, OK, 一切正常, MainForm的Caption 变为Desktop的路径。
<A NAME="312"></A><B>2)向DesktopMenu中填充Desktop items
</B> 接下来开始将文件名放入MenuItem中,
将FillMenuItemsFromFileList <B>procedure</B> 中键入下列代码
(位于Caption := DesktopPath;语句之后)
<I><FONT COLOR="#000080">//查找除VolumeID之外的所有文件
</FONT></I> ItemsAttr:= faAnyFile <B>xor</B> faVolumeID;
<I><FONT COLOR="#000080">//查找第一个匹配文件
</FONT></I> Result := FindFirst(DesktopPath+'\*.*', ItemsAttr, SearchRec);
<I><FONT COLOR="#000080">//继续找到所有文件
</FONT></I> <B>while</B> Result = 0 <B>do
</B> <B>begin
</B> <I><FONT COLOR="#000080">//建立新的MenuItem
</FONT></I> ThisMenuItem := TMenuItem.Create(DesktopMenu);
ThisMenuItem.Caption := SearchRec.Name;
DesktopMenu.Items.Add(ThisMenuItem);
<I><FONT COLOR="#000080">//查找下一个匹配文件
</FONT></I> Result := FindNext(SearchRec);
<B>end</B>;
<I><FONT COLOR="#000080">//关闭查找操作
</FONT></I> FindClose(SearchRec);
并在<B>var</B>段中加入
ItemsAttr: Integer;
SearchRec: TSearchRec;
Result: Integer;
ThisMenuItem: TMenuItem;
运行Project, 当MainForm出现后在其客户区按 mouse 右键, Desktop Menu
出现了, 一切如我们所预期的那样。
<B>Tip</B>: 函数和变量的命名要尽量采用一些有意义的单词, 可提高程序的可读性。
<A NAME="32"></A><B>(二)使用FindFirst/FinNext带来的问题
</B> 但随后的问题出现了, 对于系统级的My Computer, Recycle Bin等并未出现在
Win95\Desktop子目录下, 该如何做?
采用Windows 95的Explorer浏览Win95\Desktop子目录, 同样不包括系统级的
Item, 但当用其浏览最顶层的Desktop时, 显示出的Items与桌面上所看到的相同
看来, 看来若要实现一个真正的Desktop Menu必须要从Windows 95 的 Shell
入手。查阅相关文档, 得知 Windows 95 提供了一种被称为"Name space"的新
概念, 其中有一个IShellFolder的Ole对象(可以理解为一个虚拟的Folder),
最顶层的 Desktop 就是这个Ole对象(Object)。
<B>Tip:</B>透过现象看本质。
<A NAME="33"></A><B>(三)使用IShellFolder收集Desktop items
</B> <A NAME="331"></A><B>1)获得IShellFolder对象(object)
</B> 这个IShellFolder Ole对象要如何取得呢? 在ShlObj Unit中有一个函数
SHGetDesktopFolder可以取得Desktop的IShellFolder对象(对于其它Shell Folder
则要费些周折)。现在我们就来将它取回:
首先为TMainForm添加一个新函数 <B>procedure</B> FillMenuItemsFromShellFolder;
并增加几个 <B>private</B> 变量
isfDesktopFolder: IShellFolder;
imShellAllocator: IMalloc;
在<B>uses</B>段中添加ole2 <B>unit
procedure</B> TMainForm.FillMenuItemsFromShellFolder;
<B>begin
</B> <I><FONT COLOR="#000080">//获得Task allocator
</FONT></I> <B>if</B> SHGetMalloc(imShellAllocator) <> NOERROR <B>then
</B> <B>begin
</B> Application.MessageBox('无法取得 IMalloc',
'运行错误', MB_ICONSTOP <B>or</B> MB_OK);
Exit;
<B>end</B>;
<I><FONT COLOR="#000080">//取回DesktopFolder对象
</FONT></I> <B>if</B> SHGetDesktopFolder(isfDesktopFolder) <> NOERROR <B>then
</B> <B>begin
</B> Application.MessageBox('无法取得IShellFolder Object',
'运行错误', MB_ICONSTOP <B>or</B> MB_OK);
<I><FONT COLOR="#000080">//释放shell task Allocator
</FONT></I> imShellAllocator.Release;
Exit;
<B>end</B>;
Caption := 'IShellFolfer';
<I><FONT COLOR="#000080">//释放DesktopFolder Object
</FONT></I> isfDesktopFolder.Release;
<I><FONT COLOR="#000080">//释放shell task Allocator
</FONT></I> imShellAllocator.Release;
<B>end</B>;
你可能会注意到, 这里用到了一个叫 SHGetMalloc 的Shell API函数,
其目的是, 对于所有 Shell Object 的编程, 要先分配一个Task allocator,
确切地讲是memory allocator详见下面的描述(摘自ShlObj.Pas), 虽然现在
不是编写一个Shell extensions, 但后面我们会用到 PITEMIDLIST, 其所占用
的内存是由Shell对象(object)分配后再将指针交给我们, 对于这些存储块的
释放是采用Task allocator提供的Free成员函数来实现的。(笔者层跟踪过一些
类似的AP, 发现此方面大多被忽略, 表现在每次更新Menu的Items后, 总会在
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -