📄 howto.html
字号:
</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>;
你可能会注意到imShellAllocator.Free(tmpItemID)被取消了, 这是由于在以后
我们需要它作为ShellExecuteEx的参数, 这样我们就需要一个新方法(method)
<B>procedure</B> ReleaseShellFolderMenuItems来释放它, 其在OnClose中执行
<B>procedure</B> TMainForm.ReleaseShellFolderMenuItems;
<B>var
</B> pItem: PMenuItemType;
tmpMenuItemInfo: TMENUITEMINFO;
<B>begin
</B> <I><FONT COLOR="#000080">//释放FMenuItemList
</FONT></I> <B>while</B> FMenuItemList.Count > 0 <B>do
</B> <B>begin
</B> pItem := FMenuItemList.Items[0];
<I><FONT COLOR="#000080">//释放PItemIDList占用的memory
</FONT></I> imShellAllocator.Free(pItem^.ID);
FMenuItemList.Delete(0);
<B>end</B>;
<I><FONT COLOR="#000080">//Empty DesktopMenu, PItemIDList没了MenuItem也就失去了存在的价值
</FONT></I> <B>while</B> DesktopMenu.Items.Count > 0 <B>do
</B> DesktopMenu.Items.Delete(0);
<B>end</B>;
<A NAME="44"></A><B>(四)运行
</B>为接收OnClick事件定义一个OnDesktopMenuClick(Sender: TObject)的方法,
我采用了从MenuItem的Name property中提取Item序列号的做法来确定是哪个
MwnuItem触发的事件, 并以其作为索引取出相应MenuItemType记录。
<B>procedure</B> TMainForm.OnDesktopMenuClick(Sender: TObject);
<B>var
</B> i : Integer;
s : <B>string</B>;
pItem: PMenuItemType;
tmpMenuItemInfo: TMENUITEMINFO;
ExecItemInfo: TSHELLEXECUTEINFO;
<B>begin
</B> <B>with</B> (Sender <B>as</B> TMenuItem) <B>do
</B> <B>begin
</B> <I><FONT COLOR="#000080">//取出序列号
</FONT></I> s := Name;
System.Delete(s, 1, Pos('_', s));
i := StrToInt(s);
<I><FONT COLOR="#000080">//取出record指针,若采用Tag此处就应为pItem := PMenuItemType(Tag);啦
</FONT></I> pItem := FMenuItemList.Items[i];
<I><FONT COLOR="#000080">//填充ShellExecuteEx
</FONT></I> <B>with</B> ExecItemInfo <B>do
</B> <B>begin
</B> <I><FONT COLOR="#000080">//struct size
</FONT></I> cbSize := SizeOf(TSHELLEXECUTEINFO);
<I><FONT COLOR="#000080">//Execute Folder and Execute File are both differents
</FONT></I> <B>if</B> Bool(pItem^.Info.dwAttributes <B>and</B> SFGAO_FOLDER) <B>then
</B> <I><FONT COLOR="#000080">//为Folder类型使用SEE_MASK_IDLIST mask
</FONT></I> fMask := SEE_MASK_IDLIST <B>or</B> SEE_MASK_FLAG_NO_UI
<B>else
</B> <I><FONT COLOR="#000080">//其它采用SEE_MASK_INVOKEIDLIST mask
</FONT></I> fMask := SEE_MASK_INVOKEIDLIST <B>or</B> SEE_MASK_FLAG_NO_UI;
<I><FONT COLOR="#000080">//parent window
</FONT></I> Wnd := Application.Handle;
<I><FONT COLOR="#000080">//Execute method
</FONT></I> lpVerb := <B>nil</B>;
<I><FONT COLOR="#000080">//Execute Filename
</FONT></I> lpFile := <B>nil</B>;
lpParameters := <B>nil</B>;
lpDirectory := <B>nil</B>;
<I><FONT COLOR="#000080">//Show command
</FONT></I> nShow := SW_SHOWNORMAL;
<I><FONT COLOR="#000080">//Application Instance
</FONT></I> hInstApp := hInstance;
<I><FONT COLOR="#000080">//将PItemIDList赋值给lpIDList成员,是我们此行的主要目的
</FONT></I> lpIDList := pItem^.ID;
hkeyClass := 0;
dwHotKey := 0;
hIcon := 0;
hProcess := 0;
<B>end</B>;
<I><FONT COLOR="#000080">//Call ShellExecuteEx method to Execute Selected item
</FONT></I> ShellExecuteEx(@ExecItemInfo);
<B>end</B>;
<B>end</B>;
接下来的工作:编译, 运行......
大功告成啦?NO!还远不止如此,屏幕上还有一个MainForm--没用。我们要将它
隐藏起来, 同时还要在StartBar的右边加一个Tray Icon并让它响应mouse的click
以弹出(popup)Desktop Menu, 还有......如果累了可以暂且休息一下或明天再干
或听听音乐......我等着你, 反正也没有多少了。
<A NAME="5"></A><B>第五步:在Tray Icon中添加Icon
</B>Windows95推出不久, 就有许多类似的示范程序相继推出, 我们只对其进行简单
讨论, 详细资料请参考有关文档。
在ShellApi.pas中有一个TNotifyIconData的record type是专门用于处理Tray
Icon 的数据类型, 定义如下:
TNotifyIconDataA = <B>record
</B> cbSize: DWORD;
Wnd: HWND;
uID: UINT;
uFlags: UINT;
uCallbackMessage: UINT;
hIcon: HICON;
szTip: <B>array</B> [0..63] <B>of</B> AnsiChar;
<B>end</B>;
TNotifyIconData = TNotifyIconDataA;
其中,
cbSize 为结构的尺寸
Wnd 拥有者的窗口handle, 用于标识需要接收系统通知的窗口
uID 用户自定义的标识号
uFlags 用于标识结构成员的有效性(也就是让系统认可的那些成员,
可为下列值的组合:
NIF_ICON hIcon 成员是有效的
NIF_MESSAGE uCallbackMessage 成员是有效的
NIF_TIP szTip 成员是有效的
uCallbackMessage
用户定义的回调(Callback)消息
hIcon Icon的handle
szTip HintWindow所显示的提示信息
由于我们的目的是要在Tray上放一个Icon并能响应mouse click, 所以上述
的成员是我们所必须的。
首先定义消息常量
WM_TRAYICON = WM_USER+100;
WM_SHOWMENU = WM_USER+101; //以后我们会用到
WM_USER是Windows留给用户自己定义消息的起点。
在MainForm的<B>private</B>段中添加 FTrayIconStruct: TNotifyIconData;
在OnCreate Event中添入:
<B>with</B> FTrayIconStruct <B>do
</B> <B>begin
</B> cbSize:= SizeOf(FTrayIconStruct);
Wnd:= Handle;
uID:= 1;
uFlags:= NIF_MESSAGE <B>or</B> NIF_ICON <B>or</B> NIF_TIP;
hIcon:= LoadIcon(HInstance, 'MAINICON');
uCallBackMessage := WM_TRAYICON;
szTip:= 'Hello Desktop Menu';
<B>end</B>;
<I><FONT COLOR="#000080">//Register NotifyIcon
</FONT></I> Shell_NotifyIcon(NIM_ADD, @FTrayIconStruct);
接下来, 我们还需要在程序中处理这个WM_TRAYICON消息
<A NAME="6"></A><B>第六步:处理WM_TRAYICON和WM_SHOWMENU消息
</B>Delphi中预定义了很多Windows本身的消息处理函数, 对于自定义的消息该如何
作呢?没关系, 分析一下, 所有的消息无非就是一些integer罢了, 照着做就
可以了。
加入下面的函数定义
<B>procedure</B> WMTrayIcon(<B>var</B> <B>Message</B>: TMessage); <B>message</B> WM_TRAYICON;
<B>procedure</B> TMainForm.WMTrayIcon(<B>var</B> <B>Message</B>: TMessage);
<B>var
</B> ACursorPos : TPoint;
<B>begin
</B> <B>with</B> <B>Message</B> <B>do
</B> <B>case</B> LParam <B>of
</B> WM_LButtonDown:
<B>begin
</B> GetCursorPos(ACursorPos);
PostMessage(Handle, WM_SHOWMENU, 0,
ACursorPos.x + (AcursorPos.y <B>shl</B> 16));
<B>end</B>;
<B>end</B>;
<B>end</B>;
采用 case ... of 结构在此程序中并非绝对必要, 但从编程的观点来考虑
可以为以后的扩充提供较好的基础。PostMessage 亦处于同样的目的。
<B>Tip: </B>对于没有接触过的(例如, procedure WMTrayIcon), 只要善于思考
找出其最根本的东西, 就可以做到“触类旁通”的效果。同时要为以后
做一些考虑, 真正的受益者是编程者自己。
同样我们需要处理WM_SHOWMENU消息, 代码很简单
<B>procedure</B> WMShowMenu(<B>var</B> <B>Message</B>: TMessage); <B>message</B> WM_SHOWMENU;
<B>procedure</B> TMainForm.WMShowMenu(<B>var</B> <B>Message</B>: TMessage);
<B>begin
</B> <I><FONT COLOR="#000080">//首先将本程序切换到前台
</FONT></I> SetForegroundWindow(Handle);
<B>with</B> <B>Message</B> <B>do
</B> DesktopMenu.Popup(LParamLo, LParamHi);
<B>end</B>;
<A NAME="7"></A><B>第七步:隐藏MainForm
</B>隐藏Form的做法有很多很多, 当然了最理想的做法是在user无法察觉的情况下
就将其隐藏起来, 所以只有直接在DPR文件中动手了。
在Application.Initialize;之后加入下面三行代码
<I><FONT COLOR="#000080">//不显示MainForm
</FONT></I>Application.ShowMainForm := False;
<I><FONT COLOR="#000080">//建立Application的handle
</FONT></I>Application.CreateHandle;
<I><FONT COLOR="#000080">//隐藏Application, 从而只在Tray上留下一个Icon
</FONT></I>ShowWindow(Application.Handle, SW_HIDE);
至此, 程序已经基本完成, 但只能算是在功能方面, 距一个性能齐备的软件还有
很多地方不尽人意, 例如:
程序没有设立退出口, (这不能不说是一个极大的缺陷)不能感知Desktop Items
的变化, 甚至还可以考虑在Desktop Menu放入items的icon, 就象Windows95的
StartMenu一样等等... 不过我们现在的目的是在讨论如何建立Desktop Menu
这样的程序的方法。若要编写一个实用的Desktop Menu我认为不应采用Delphi
的VCL, 应采用Windows的标准消息循环体系, 最起码可节省很多宝贵的资源。
有兴趣的读者可以自己尝试去编写一个真正的Desktop Menu!
</PRE>
<P><A NAME="8"></A><B>作者的话</B></P>
<PRE>最近作者本人在空余时间里经常做一些有关Windows95 Shell方面的编程, 此篇
文章就是将编程当中的一些收获, 经整理后写出的, 由于水平有限, 文章中难免
存在这样或那样的错误, 请多包含。另计划编写一系列以实战为基础, 面向
Windows95 shell和Api等相关议题的文章, 内容以自己在写AP时所遇到的问题
为主, 真心希望看过此篇文章的读者能提出一些宝贵的建议(PS:无论赞助与否)
可以是议题方向方面的或是您需要什么等等, 总之无论什么方面的建议, 都会
得到我对您的深深谢意和永久的祝福。
如果你在阅读时遇到什么问题, 或者您有更好的建议请用mail通知我, 谢谢。
</PRE>
<CENTER><P>
<HR>Copyright Homearts software lib.<BR>
作者:王学胜 <BR>
<A HREF="mailto:wxsheng@public.tpt.tj.cn">wxsheng@public.tpt.tj.cn</A></P></CENTER>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -