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

📄 howto.html

📁 用Delphi编写一个Desktop Menu
💻 HTML
📖 第 1 页 / 共 3 页
字号:
memory中残留一些未被释放的空间,其后果也就不言而寓了)

<B>Tip:</B>若想编写一个健壮的程序, 需要多留意各种文档中的细节, 并把它反映
到程序的代码中来--最好的编程指南是Online help和代码本身所提供的注释。

 All the shell extensions MUST use the task allocator (see OLE 2.0
 programming guild for its definition) when they allocate or free
 memory objects (mostly ITEMIDLIST) that are returned across any
 shell interfaces. There are two ways to access the task allocator
 from a shell extension depending on whether or not it is linked with
 OLE32.DLL or not (virtual; stdcall; abstractly for efficiency).


 <A NAME="332"></A><B>2)用IShellFolder来填充DesktopMenu的items
</B> ShellFolder找到了, 接下来的工作该是填满我们的DesktopMenu了, 这也是我们
 编程中最有意思的地方。参考Online Help, IShellFolder Object 有一个
 EnumObjects 的方法(Enumerates the objects in the folder), 这又是一个
 Object -- IEnumIDList。从某些方面讲这个 IEnumIDList 与Delphi 中的 TList
 有些类似, 只是遍历的方法不同。

在TMainForm中的<B>private</B>段中添入
FRetrieveItemFlag: UINT;
FEnumItemFlag:     DWord;
ieDesktopItemsObj: IEnumIDList;

并在OnCreate事件中加入
  FRetrieveItemFlag := SHGFI_DISPLAYNAME <B>or</B> SHGFI_PIDL;
  FEnumItemFlag := SHCONTF_NONFOLDERS <B>or</B> SHCONTF_FOLDERS;

<B>Tip:</B> 使用上述变量可使程序具有较大的灵活性并易于维护, 设想一下如果我们需要
  从SHGetFileInfo中获得其它信息, 而程序中又不只一次地使用了SHGetFileInfo
  我们就可从中收益(代码只更改了一处)。或者程序中有一个option可以按用户的
  需求进行定制, 同样我们可以在初始化时接收用户的请求并一次设置好这些参数。
  其实, 最理想的做法是将所有可能变动全局变量和常量全放到一个公用的unit中。
  对于通用的<B>procedure</B>也是一样(最好按功能进行分组--我个人的一点建议)。

并将FillMenuItemsFromShellFolder修改为下列代码,

<B>procedure</B> TMainForm.FillMenuItemsFromShellFolder;
<B>var
</B>  pceltFetched: ULONG;
  tmpItemID: PItemIDList;
  tmpItemInfo: TSHFileInfo;
  ThisMenuItem: TMenuItem;
  tmpMenuNo : Integer;
  pItem: PMenuItemType;
  tmpMenuItemInfo: TMENUITEMINFO;
<B>begin

</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);
    Exit;
  <B>end</B>;
  Caption := 'IShellFolder';

  tmpMenuNo := 0;
  <I><FONT COLOR="#000080">//枚举DesktopFolder中的Items
</FONT></I>  <B>if</B> isfDesktopFolder.EnumObjects (Application.Handle,
                                  FEnumItemFlag,
                                  ieDesktopItemsObj) &lt;&gt; NOERROR <B>then
</B>    Application.MessageBox('无法EnumObject',
                           '运行错误', MB_ICONSTOP <B>or</B> MB_OK)
  <B>else
</B>  <I><FONT COLOR="#000080">//第一次指向第一个PItemList, 每次取一个item
</FONT></I>  <B>while</B> NOERROR = ieDesktopItemsObj.Next(1, tmpItemID, pceltFetched)  <B>do
</B>  <B>begin
</B>    <I><FONT COLOR="#000080">//取得Item的有关信息
</FONT></I>    SHGetFileInfo( PChar(tmpItemID), 0, tmpItemInfo,
                   SizeOf(TSHFileInfo),
                   FRetrieveItemFlag);
    New(pItem);
    pItem^.ID := tmpItemID;
    pItem^.Info := tmpItemInfo;
    FMenuItemList.Add(pItem);
    <I><FONT COLOR="#000080">//建立这个MenuItem
</FONT></I>    ThisMenuItem:= TMenuItem.Create(DesktopMenu);
    <I><FONT COLOR="#000080">//填充MenuItem
</FONT></I>    <B>with</B> ThisMenuItem <B>do
</B>    <B>begin
</B>      <I><FONT COLOR="#000080">//采用MenuItem序列号为其命名
</FONT></I>      Name := 'DesktopMenu_'+IntToStr(tmpMenuNo);
      Caption := StrPas(tmpItemInfo.szDisplayName);
      <I><FONT COLOR="#000080">//连接OnClick事件
</FONT></I>      OnClick := OnDesktopMenuClick;
    <B>end</B>;
    <I><FONT COLOR="#000080">//添加到DesktopMenu中, 此为必须
</FONT></I>    DesktopMenu.Items.Add(ThisMenuItem);
    Inc(tmpMenuNo);
  <B>end</B>;
  <I><FONT COLOR="#000080">//释放Desktop EnumObject
</FONT></I>  ieDesktopItemsObj.Release;
  <I><FONT COLOR="#000080">//释放DesktopFolder Object
</FONT></I>  isfDesktopFolder.Release;

<B>end</B>;

运行Project, 在Form中按Mouse右键, 我们就会看到Desktop Items 的Menu了。
当然这只迈出了第一步, 单纯的显示没有任何实际意义, 还要想办法运行它。


<A NAME="4"></A><B>第四步:运行得到的item

</B>Windows 95 提供了很多运行程序的方法, 如 WinExec, CreateProcess,
ShellExecute, ShellExecuteEx 等。 WinExec和ShellExecute使用起来最简便
CreateProcess功能非常强大, 但它们都是与文件名打交道, 对于那些不具备
物理文件名的Items就无能为力了, 所以我们采用ShellExecuteEx这个API。
这也是本篇文章中的另一个关键所在。

<A NAME="41"></A><B>(一)使用ShellExecuteEx运行item的基础知识

</B>ShellExecuteEx是windows95中新增的API函数, 在使用之前我们有必要对其进行一下
了解,现在我们对其进行详细讨论

Online Help 中的声明如下:
WINSHELLAPI BOOL WINAPI ShellExecuteEx(
    LPSHELLEXECUTEINFO  lpExecInfo      // pointer to SHELLEXECUTEINFO structure
   );
这是一个C style的描述(Online Help中对于Win32 API的说明是从Microsoft SDK中
直接照搬下来的, 没有使用Pascal style的语法说明), 为便于理解, 从ShellApi.Pas
中取出其Pascal style的描述

<B>function</B> ShellExecuteEx(lpExecInfo: PShellExecuteInfo):BOOL; <B>stdcall</B>;

  PShellExecuteInfoA = ^TShellExecuteInfoA;
  PShellExecuteInfo = PShellExecuteInfoA;
  TShellExecuteInfoA = <B>record
</B>    cbSize: DWORD;
    fMask: ULONG;
    Wnd: HWND;
    lpVerb: PAnsiChar;
    lpFile: PAnsiChar;
    lpParameters: PAnsiChar;
    lpDirectory: PAnsiChar;
    nShow: Integer;
    hInstApp: HINST;
    <I><FONT COLOR="#000080">{ Optional fields }
</FONT></I>    lpIDList: Pointer;
    lpClass: PAnsiChar;
    hkeyClass: HKEY;
    dwHotKey: DWORD;
    hIcon: THandle;
    hProcess: THandle;
  <B>end</B>;
  TShellExecuteInfo = TShellExecuteInfoA;

  这个结构中包括了15个成员(在此我们只讨论相关的几个)

  cbSize: DWORD
  指明此结构的尺寸(bytes), 可以使用SizeOf(TShellExecuteInfo)取得。

  fMask: ULONG;
  指明其它成员的意义及有效性的标志, 可以是很多值的组合, 请详见Online Help
  和SDK有关文档, 前面我们已经可以成功地取得 PItemIDList 并通过它取回了
  Items的Display name, 因而我们感兴趣的是 SEE_MASK_IDLIST 和
  SEE_MASK_INVOKEIDLIST 这两个Mask, Windows 95 Desktop 中的Items或称为
  Object 从行式上基本可分为两类, 其一是文件夹类(Folder), 从Explorer中看
  就是可以显示在左侧TreeView中的东西;剩下的就是文件类了, 不论是Application
  还是Document还有等等其它的。我们采用 SEE_MASK_IDLIST 处理文件夹;
  SEE_MASK_INVOKEIDLIST 来打开文件。另外还有一个SEE_MASK_FLAG_NO_UI,
  可以在出现错误时禁止显示MessageBox。

  Wnd: HWND;
  指明Parent的窗口Handle, 可以是Application.Handle或MainForm.Handle
  (Delphi的Application是一个隐含的窗口,当然最终的MainForm也要被隐含-
  对于这个Application它没有任何显示的必要)

  lpVerb: PAnsiChar;
  已串的形式指明Application所执行的动作, 如 'open', 'explorer'等,
  可采用默认值, 简单的赋值为nil即可, 对其它无关的类似参数我们也采用
  同样的做法

  lpFile, lpParameters, lpDirectory
  这几个参数的意义从名称上已经非常明确了, 同ShellExecute是一样的,
  因我们的程序中采用PItemIDList, 只是简单的将其指向nil。

  lpIDList: Pointer;
  这是我们所取得的 PItemIDList 最终将要落脚的地方


  既然用到了 PItemIDList 我们就需要将其暂存起来。同时由于运行Folder和
  其它文件的不同, 还需要保存SHGetFileInfo所取回的信息。

<A NAME="42"></A><B>(二)存储PItemIDList和FileInfo

</B>  在Delphi中, 实现这一功能的方法有很多。但最根本的方法是先定义一个结构,

  <B>type
</B>    PMenuItemType = ^TMenuItemType;
    TMenuItemType = <B>record
</B>      ID   : PItemIDList;
      Info : TSHFileInfo;
    <B>end</B>;

  然后可根据个人的偏好采用数组(阵列), 链表或干脆直接分配一大块memory
  对其直接采用指针定位(我偏爱在DOS下采用此方法)等等。
  在这我采用Delphi提供的TList这个Object, 这是一个不很起眼但非常好用
  的Object。

  (PS:作完程序之后, 突然发现我忽略了一个更简便的方法:将这个记录(record)
  指针通过强制类型转换, 放到Tag这个property中可不用维护这个TList了--
  写完了就懒得改了, 记得很久以前在Apple II 上写6502 ASM时可没那麽轻松,
  为了节省几个字节和几个时钟周期,遇到这种事情是不能轻易让它过去的; 现在
  则不同啦, 是在用大量的资源和高速的CPU来换取开发的速度和更好的界面,
  优化代码?谁管呢?记得有一个叫 John C. Dvorak 的外国人在一篇名为
  『我的机器人在哪儿?』的文章中写到“令人奇怪的是,快的处理器总是倾向于
  产生膨胀的代码,而不是好的应用-当然有人会说:处理器快一些才能运行高端
  的图形和多媒体应用。......很快我的台式机上就会有一个每秒能运行几十亿条
  指令的处理器了,但用它来干什麽?运行Word?下载一个WEB页?玩
  Duke Nukem 8D?<ps:我没打错>我的机器人在哪里呢?”, 不过如果时间允许
  的话我们还是应该尽可能地优化我们的代码, 真的很想念以前的那段时光)

<A NAME="43"></A>(三)<B>完善FillMenuItemsFromShellFolder
</B>  好了, 现在增加一个叫FMenuItemList的<B>private</B> TList型变量, 在MainForm
  的OnCreate事件中加入

  FMenuItemList := TList.Create;
  <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>;

  将FRetrieveItemFlag改为SHGFI_DISPLAYNAME <B>or</B> SHGFI_PIDL <B>or</B> SHGFI_ATTRIBUTES;
  因为我们需要用Attributes来确定Item的类型。

  在OnClose事件中加入
  <I><FONT COLOR="#000080">//从FillMenuItemsFromShellFolder中移到此处
</FONT></I>  imShellAllocator.Release;
  FMenuItemList.Free;

   按下列代码修改FillMenuItemsFromShellFolder函数

<B>procedure</B> TMainForm.FillMenuItemsFromShellFolder;
<B>var
</B>  pceltFetched: ULONG;
  tmpItemID: PItemIDList;
  tmpItemInfo: TSHFileInfo;
  ThisMenuItem: TMenuItem;
  tmpMenuNo : Integer;
  pItem: PMenuItemType;
  tmpMenuItemInfo: TMENUITEMINFO;
<B>begin

</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);
    Exit;
  <B>end</B>;
  Caption := 'IShellFolder';

  tmpMenuNo := 0;
  <I><FONT COLOR="#000080">//枚举DesktopFolder中的Items
</FONT></I>  <B>if</B> isfDesktopFolder.EnumObjects (Application.Handle,
                                  FEnumItemFlag,
                                  ieDesktopItemsObj) &lt;&gt; NOERROR <B>then
</B>    Application.MessageBox('无法EnumObject',
                           '运行错误', MB_ICONSTOP <B>or</B> MB_OK)
  <B>else

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -