⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 howto.html

📁 用Delphi编写一个Desktop Menu
💻 HTML
📖 第 1 页 / 共 3 页
字号:
<!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>&quot;Desktop Menu&quot;</B>应用程序
的编写过程。所谓的<B>&quot;Desktop Menu&quot;</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 &quot;OK&quot; 按钮以重新编译整个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 提供了一种被称为&quot;Name space&quot;的新
  概念, 其中有一个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) &lt;&gt; 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) &lt;&gt; 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 + -