📄 0517001.htm
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 4.0">
<meta name="ProgId" content="FrontPage.Editor.Document">
<title></title>
<link rel="stylesheet" type="text/css" href="../../vckbase.css">
</head>
<body>
<div align="justify">
<table border="0" width="100%" class="font" height="57">
<tr>
<td width="27%" height="6" class="bigfont" bgcolor="#B8CFE7" align="center" bordercolor="#800080">
<font color="#800080">VC知识库(五)</font>
</td>
<td width="73%" height="6" class="bigfont" bgcolor="#B8CFE7" align="center" bordercolor="#800080">
<font color="#800080">www.vckbase.com</font>
</td>
</tr>
<tr>
<td width="100%" height="4" class="header" valign="top" align="center" colspan="2">
<hr>
</td>
</tr>
<tr>
<td width="100%" height="17" class="header" valign="top" align="center" colspan="2">
<small>
Windows的外壳扩展
</small>
</td>
</tr>
<tr>
<td width="100%" height="17" class="info" align="center" colspan="2">
<small>刘明华
</small>
</td>
</tr>
<tr>
<td width="100%" height="22" class="font" colspan="2">
<hr>
</td>
</tr>
<tr>
<td width="100%" height="5" class="font" colspan="2">
<small>
<p>
Windows操作系统(本文中的Windows是指Windows 95/98和NT 4.0)提供了颇受用户喜爱的图形界面(GUI)。微软为Windows的用户界面保留了可扩充性。基于32位Windows的应用程序可以通过多种方式来增强系统所提供的操作环境(也称为外壳)。通过对外壳的扩展,开发人员可以为用户提供其他的文件对象操作方式或者简化文件系统和网络的浏览,或者使用户能更方便地调用文件系统中对各种对象进行处理的工具。例如,用户可以将Word或者WordPad中的文档内容直接拖放到桌面上,Windows给History和Subscriptions文件夹赋予了与众不同的图标以显示它们的特殊性,还有一些应用程序(比如WinZip)可以向文件或目录的上下文相关菜单动态地添加命令等等。这些功能的实现都是依靠Windows外壳扩展(Shell Extensions)。
<p>
1.外壳扩展概述
<p>
下面是与外壳扩展相关的三个重要术语:
<p>
(1)文件对象(File Object)
<p>
文件对象是外壳中的一项,大家最熟识的文件对象是文件和目录,此外,打印机、控制面板程序、共享网络等也都是文件对象。
<p>
(2)文件类(File Class)
<p>
文件类是具有某种共同特性的文件对象的集合,比如,扩展名相同的文件属于同一文件类。
<p>
(3)处理程序(Handler)
<p>
处理程序是具体实现某个外壳扩展的代码。
<p>
Windows支持七种类型的外壳扩展(称为Handler),它们相应的作用简述如下:
<p>
(1)Context menu handlers向特定类型的文件对象增添上下文相关菜单;
<p>
(2)Drag-and-drop handlers用来支持当用户对某种类型的文件对象进行拖放操作时的OLE数据传输;
<p>
(3)Icon handlers用来向某个文件对象提供一个特有的图标,也可以给某一类文件对象指定图标;
<p>
(4)Property sheet handlers给文件对象增添属性页,属性页可以为同一类文件对象所共有,也可以给一个文件对象指定特有的属性页;
<p>
(5)Copy-hook handlers在文件夹对象或者打印机对象被拷贝、移动、删除和重命名时,就会被系统调用,通过为Windows增加Copy-hook handlers,可以允许或者禁止其中的某些操作;
<p>
(6)Drop target handlers在一个对象被拖放到另一个对象上时,就会被系统被调用;
<p>
(7)Data object handlers在文件被拖放、拷贝或者粘贴时,就会被系统被调用。
<p>
Windows的所有外壳扩展都是基于COM(Component Object Model) 组件模型的,外壳是通过接口(Interface)来访问对象的。外壳扩展被设计成32位的进程中服务器程序,并且都是以动态链接库的形式为操作系统提供服务的。因此,如果要对Windows的用户界面进行扩充的话,则具备写COM对象的一些知识是十分必要的。
<p>
写好外壳扩展程序后,必须将它们注册才能生效。所有的外壳扩展都必须在Windows注册表的HKEY_CLASSES_ROOT\CLSID键之下进行注册。在该键下面可以找到许多名字像{0000002F-0000-0000-C000-000000000046}的键,这类键就是全局唯一类标识符。每一个外壳扩展都必须有一个全局唯一类标识符,Windows正是通过此唯一类标识符来找到外壳扩展处理程序的。在类标识符之下的InProcServer32子键下记录着外壳扩展动态链接库在系统中的位置。与某种文件类型关联的外壳扩展注册在相应类型的shellex主键下。如果所处的Windows操作系统为Windows NT,则外壳扩展还必须在注册表中的HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\ShellExtensions\Approved主键下登记。
<p>
注册表HKEY_CLASSES_ROOT主键下有几个特殊的子键,如*、Folder、Drive以及Printer。如果把外壳扩展注册在*子键下,那么这个外壳扩展将对Windows中所有类型的文件有效;如果把外壳扩展注册在Folder子键下,则对所有目录有效。以下是在*子键下注册的外壳扩展的一个示例(其中登记了一个属性页和一个WinZip提供的上下文相关菜单处理程序):
<p>
[HKEY_CLASSES_ROOT\*\shellex]
<p>
@=""
<p>
[HKEY_CLASSES_ROOT\*\shellex\PropertySheetHandlers]
<p>
[HKEY_CLASSES_ROOT\*\shellex\PropertySheetHandlers\{3EA48300-8CF6-101B-84FB-666CCB9BCD32}]
<p>
@=""
<p>
[HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers]
<p>
@=""
<p>
[HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\WinZip]
<p>
@="{E0D79300-84BE-11CE-9641-444553540000}"
<p>
编译完外壳扩展的DLL程序后就可以用Windows本身提供的regsvr32.exe来注册该DLL服务器程序了。如果使用Delphi,也可以在Run菜单中选择Register ActiveX Server来注册。
<p>
如前所述,Windows的外壳扩展都是基于微软公司的COM组件模型的,从这个意义上来讲,编写外壳扩展的过程其实就是构造COM对象的过程。但由于各种外壳对象的功能不同,它们要遵循的规则也不同。鉴于Context Menu Handler这种类型的外壳扩展的应用性比较广,下面以一个实例来具体介绍Context Menu Handler外壳扩展的实现方法。只要熟练地掌握了其中一种外壳扩展程序的编写方法,在需要编写其他类型的外壳扩展时再具体查阅一下相应的规则,就可以比较容易地实现其他类型的外壳扩展了。建议读者到微软公司的网站上去找相关资料,为此先进入http://www.microsoft.com站点,然后单击Search按钮,输入Shell extension作为关键字,查找范围应选Developer Resources,再按Search按钮即可。
<p>
2.上下文相关菜单处理程序的编写
<p>
在Windows中,用鼠标右键单击文件或者文件夹时弹出的那个菜单便称为上下文相关菜单。要动态地在上下文相关菜单中增添菜单项,可以通过写Context Menu Handler来实现。比如大家所熟悉的WinZip和UltraEdit等软件都是通过编写Context Menu Handler来动态地向菜单中增添菜单项的。如果系统中安装了WinZip,那么当用右键单击一个名为abc的文件(夹)时,其上下文相关菜单就会有一个名为Add to abc.zip的菜单项。本文要实现的Context Menu Handler与WinZip提供的上下文菜单相似,它将在任意类型文件的上下文菜单中增加一个名为“用写字板打开XXX”(其中XXX为当前选定的文件名称)的菜单项,只要你选择该菜单项,Windows就会启动写字板并打开当前所选的文件
<p>
<p>
编写Context Menu Handler必须实现IShellExtInit和IContextMenu两个接口。除了IUnknown接口所定义的函数之外,Context Menu Handler还需要用到QueryContextMenu、InvokeCommand和GetCommandString这三个非常重要的成员函数。
<p>
(1)QueryContextMenu函数:每当系统要显示一个文件对象的上下文相关菜单时,它首先要调用该函数。为了在上下文相关菜单中添加菜单项,我们在该函数中调用InsertMenu函数。
<p>
(2)InvokeCommand函数:当用户选定了某个Context Menu Handler登记过的菜单项后,该函数将会被调用,系统将会传给该函数一个指向LPCMINVOKECOMMANDINFO结构的指针。在该函数中要执行与所选菜单项相对应的操作。
<p>
(3)GetCommandString函数:当鼠标指针移到一个上下文相关菜单项上时,在当前窗口的状态条上将会出现与该菜单项相关的帮助信息,此信息就是系统通过调用该函数获取的。
<p>
具体编写方法请参阅网上的程序实例,网址为www.pccomputing.com.cn。
<p>
3.增添上下文相关菜单项说明
<p>
如果要静态地为目录或者某一类文件增添上下文相关菜单项,那么就用不着编写Context Menu Handler,可以通过直接修改Windows注册表来达到此目的。比如,可以将下面的内容存成一个扩展名为.REG的文件,然后双击它将其导入注册表,你会发现所有类型文件的上下文相关菜单中都多了一个名叫“记事本”的菜单项。
<p>
REGEDIT4
<p>
[HKEY_CLASSES_ROOT\*\shell\记事本]
<p>
[HKEY_CLASSES_ROOT\*\shell\记事本\command]
<p>
@="notepad.exe\"%1\""
<p>
通过比较,很容易发现这两种方式所得结果的差异。通过直接修改注册表来增添菜单项的确比较简单,然而它不具有交互性,所增添的菜单项是静态的,并且所能实现的功能也非常有限。但是Context Menu Handler则不同,它使我们可以根据上下文的具体情况动态地添加菜单项,比如可以判断当前选定的是哪一类文件、是不是文件夹、选定的文件(夹)的个数以及获取被选定文件(夹)的属性。有时,这些信息对于程序很有用,如果需要得到此类信息,并且需要根据不同的上下文来执行不同的操作,那么只好依靠Context Menu Hander来实现。本文的实例中,其动态性体现在仅当用户选定了一个文件时,才会在上下文相关菜单中增添菜单项,并且菜单项的名字随着所选文件名的不同而相应地变化。PCC
<p>
(作者地址:西安交通大学计算机系9649# 710049 收稿日期:1998.11.20)
<p>
<p>
上下文相关菜单处理程序编写方法的实例。
<p>
//Context Menu Handler
<p>
//Written Nov 1998 by Ming-Hua LIU
<p>
//E-mail: minghua_liu@263.net
<p>
unit ContextMenuHandler;
<p>
interface
<p>
uses Windows,ActiveX,ComObj,ShlObj;
<p>
type
<p>
TContextMenu = class(TComObject,IShellExtInit,IContextMenu)
<p>
private
<p>
FFileName: array[0..MAX_PATH] of Char;
<p>
protected
<p>
{ IShellExtInit }
<p>
function IShellExtInit.Initialize = SEIInitialize; // Avoid compiler warning
<p>
function SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
<p>
hKeyProgID: HKEY): HResult; stdcall;
<p>
{ IContextMenu }
<p>
function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast,
<p>
uFlags: UINT): HResult; stdcall;
<p>
function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
<p>
function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
<p>
pszName: LPSTR; cchMax: UINT): HResult; stdcall;
<p>
end;
<p>
const
<p>
Class_ContextMenu: TGUID = '{19780513-C829-11D1-8233-0020AF3E97A9}';
<p>
{全局唯一标识符(GUID)是一个16字节(128为)的值,它唯一地标识一个接口(interface)}
<p>
implementation
<p>
uses ComServ, SysUtils, ShellApi, Registry;
<p>
<p>
function TContextMenu.SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
<p>
hKeyProgID: HKEY): HResult;
<p>
var
<p>
StgMedium: TStgMedium;
<p>
FormatEtc: TFormatEtc;
<p>
begin
<p>
//如果lpdobj等于Nil,则本调用失败
<p>
if (lpdobj = nil) then begin
<p>
Result := E_INVALIDARG;
<p>
Exit;
<p>
end;
<p>
with FormatEtc do begin
<p>
cfFormat := CF_HDROP;
<p>
ptd := nil;
<p>
dwAspect := DVASPECT_CONTENT;
<p>
lindex := -1;
<p>
tymed := TYMED_HGLOBAL;
<p>
end;
<p>
<p>
Result := lpdobj.GetData(FormatEtc, StgMedium);
<p>
if Failed(Result) then Exit;
<p>
{用DragQueryFile函数来查询选定的文件的个数。本例中仅当只选定
<p>
一个文件时才在上下文相关菜单中增加菜单项。}
<p>
<p>
if (DragQueryFile(StgMedium.hGlobal,$FFFFFFFF,nil,0) = 1) then begin
<p>
DragQueryFile(StgMedium.hGlobal, 0, FFileName, SizeOf(FFileName));
<p>
Result := NOERROR;
<p>
end
<p>
else begin
<p>
FFileName[0] := #0;
<p>
Result := E_FAIL;
<p>
end;
<p>
ReleaseStgMedium(StgMedium);
<p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -