用delphi实现外壳扩展

I

import

Unregistered / Unconfirmed
GUEST, unregistred user!
用delphi实现外壳扩展
作者:hubdog
当用户在资源管理器中调用右键菜单时,会显示一个"属性"菜单项,点击属性菜单项会显示一个属性页,用户可以获得甚至修改文件信息。我们可以定制属性页通过实现属性页扩展。如下图所示,本文实现了一个显示wave(波形)文件的信息如声道数等信息的属性页扩展。
属性页扩展通常是同某类文件相关联的来实现同之相关的操作和信息显示,另外可以同驱动器相关联,我们还可以用属性页扩展来替换控制面板程序的属性页。象其他外壳扩展程序一样,属性页扩展也是以动态连接库形式实现的进程内COM对象。它除了IUnknown接口外还要实现IShellExtInit和IShellPropSheetExt接口。
建立同文件关联的属性页扩展
首先,我们用命令File|New...,创建一个ActiveX Library,然后新建一个COM Object,实现的接口为IShellExtInit和IShellPropSheetExt。
同文件建立关联需要注册属性页,要在注册表中同相应文件对应的表项下添加Shellex/PropertySheetHandlers子键 ,每增加一个页面就需要注册一个表项,最大可以添加的页面数是24,我们可以用一个扩展实现多个页面。这里我们通过从TComObjectFactory继承类实现的UpdateRegistry实现了注册。
 
type
TCXPropSheetFactory=class(TComObjectFactory)
public
procedure UpdateRegistry(Register: Boolean); override;
end;
procedure TCXPropSheetFactory.UpdateRegistry(Register: Boolean);
var
ClassID: string;
Str,KeyName : string;
begin
inherited UpdateRegistry(Register);
if Register then
begin
ClassID:=GUIDToString(Class_CXPropSheet);
with TRegistry.Create do
try
RootKey:=HKEY_CLASSES_ROOT;
OpenKey('.wav',TRUE);
KeyName := ReadString('');
if Keyname = '' then
begin
WriteString('','WaveFile');
OpenKey('.wav',TRUE);
KeyName := ReadString('');
end;
OpenKey('+KeyName+'',TRUE);
WriteString('',Classid);
finally
Free;
end;
if(Win32Platform=VER_PLATFORM_WIN32_NT)then
begin
with TRegistry.Create do
try
RootKey:=HKEY_LOCAL_MACHINE;
OpenKey('SOFTWAREExtensions', True);
OpenKey('Approved', True);
WriteString(ClassID, 'Wave File Property Sheet');
finally
Free;
end;
end;
end
else
删除注册表项.......................
end;
初始化扩展是通过IShellExtInit实现的,当外壳调用IShellExtInit.Initialize时,它传递一个数据对象包含来文件对应的目录的PIDL标识符。Initialize方法需要从数据对象中提取文件名,并把文件名和PIDL标识符保存起来为了以后使用。
 
function TCXPropSheet.SEIInitialize(pidlFolder: PItemIDList;
lpdobj: IDataObject; hKeyProgID: HKEY): HResult;
var
StgMedium: TStgMedium;
FormatEtc: TFormatEtc;
szFile: array[0..MAX_PATH+1]of Char;
filecount: integer;
begin
Result:=E_FAIL;
if(lpdobj=nil)then begin
Result:=E_INVALIDARG;
messagebox(0, '1', '错误', mb_ok);
Exit;
end;
with FormatEtc do begin
cfFormat:=CF_HDROP;
ptd:=nil;
dwAspect:=DVASPECT_CONTENT;
lindex:=-1;
tymed:=TYMED_HGLOBAL;
end;
Result:=lpdobj.GetData(FormatEtc, StgMedium);
if Failed(Result)then
Exit;
// 如果只有一个文件被选中,获得文件名并保存。
filecount:=DragQueryFile(stgmedium.hGlobal, $FFFFFFFF, nil, 0);
if filecount=1 then begin
Result:=NOERROR;
DragQueryFile(stgmedium.hGlobal, 0, szFile, SizeOf(szFile));
FFilename:=strpas(szFile);
end;
ReleaseStgMedium(StgMedium);
end;
添加页面的操作是通过IShellPropSheetExt接口来实现的。如果属性页是和文件相关联,外壳会调用 IShellPropSheetExt.AddPages给属性页添加一个页面。如果属性页同控制面板程序相关联,外壳调用 IShellPropSheetExt.ReplacePage来替换页面。
IShellPropSheetExt.AddPages方法有两个参数,lpfnAddPage是一个指向AddPropSheetPageProc 回调函数的指针,回调函数用来提供要添加的页面信息给外壳。lParam是一个用户自定义的值,这里我们用它来返回给回调函数对象。
一般的IShellPropSheetExt.AddPages方法实现步骤是:
给PROPSHEETPAGE结构设定正确的值,特别是:
把扩展的对象引用记数变量付值给pcRefParent成员,这可以防止页面还在显示时,扩展对象被卸载。
实现 PropSheetPageProc 回调函数来处理页面创建和销毁的情况。
调用CreatePropertySheetPage函数来创建页面。
调用lpfnAddPage指向的函数来来添加创建好的页面。
function TCXPropSheet.AddPages(lpfnAddPage: TFNADDPROPSHEETPAGE;
lParam: LPARAM): HResult;
var
PSP: TPropSheetPage;
HPSP: HPropSheetPage;
begin
result:=E_FAIL;
try
psp.dwSize:=SizeOf(psp);
psp.dwFlags:=PSP_USEREFPARENT or PSP_USETITLE or PSP_USECALLBACK;
psp.hInstance:=hInstance;
//这里我们使用了事先储存在wave.res中的对话框模板,模板是用delphi5自带的
//resource workshop编辑的,使用delphi5.exe编译的。
psp.pszTemplate:=MakeIntResource(100);
//标题名
psp.pszTitle:='波文件信息';
//设定回调函数
psp.pfnDlgProc:=@DialogProc;
psp.pfnCallBack:=@PropCallback;
//设定对象引用记数变量
psp.pcRefParent:=@comserver.objectcount;
//用lParam向回调函数传递对象
psp.lParam:=integer(self);
HPSP:=CreatePropertySheetPage(psp);
if HPSP<>nil then begin
if not lpfnAddPage(HPSP, lParam)then begin
DestroyPropertySheetPage(HPSP);
end else begin
_addref;//增加引用记数,否则一脱离这个方法的作用域,delphi自动释放对象。
result:=S_OK;
end
end
except
on e: exception do begin
e.message:='添加页面 '+e.message;
messagebox(0, pchar(e.message), '错误', mb_ok);
end;
end;
end;
function TCXPropSheet.ReplacePage(uPageID: UINT;
lpfnReplaceWith: TFNADDPROPSHEETPAGE; lParam: LPARAM): HResult;
begin
Result:=E_NOTIMPL;//同文件关联时,外壳不调用ReplacePage,所以不用实现
end;
回调函数处理属性页的消息,主要要响应WM_INITDIALOG消息来初始化页面显示信息,响应WM_COMMAND消息来处理用户交互,响应WM_NOTIFY消息来处理页面切换或关闭后处理操作结果。
 
function DialogProc(hwndDlg: HWnd; Msg: UINT; wParam: wParam;
lParam: LPARAM): Bool; stdcall;
var
PageObj: TCXPropSheet;
filename: string;
displayName : string;
buffer: array[0..255]of char;
SheetHWnd: HWnd;
begin
result:=false;
try
if Msg=WM_INITDIALOG then begin//初始化界面
//获得lparam传递过来的对象
pageObj:=TCXPropSheet(PPropSheetPage(lParam)^.lParam);
//保存对象信息
SetWindowLong(hwndDlg, DWL_USER, integer(pageObj));
//设置界面显示波文件信息
SetDlgItemText(hwndDlg, 100, PChar(ExtractFileName(PageObj.FFileName)));
OpenMedia(PageObj.FFileName);
SetDlgItemText(hwndDlg, 101, PChar(IntToStr(GetWavStatus(MCI_WAVE_STATUS_AVGBYTESPERSEC))));
SetDlgItemText(hwndDlg, 102, PChar(IntToStr(GetWavStatus(MCI_WAVE_STATUS_BITSPERSAMPLE))));
SetDlgItemText(hwndDlg, 103, PChar(IntToStr(GetWavStatus(MCI_WAVE_STATUS_CHANNELS))));
CloseMedia;
SetWindowLong(hwndDlg, DWL_MSGRESULT, 0);
Result:=TRUE;
end
else if(Msg=WM_COMMAND)then begin
if Lo(wParam)=110 then//用户点击了关于按钮(id=110)
MessageBox(0,'作者:hubdog'+#13#10+'email:hubdog@263.net','关于...',MB_OK);
end else if(msg=WM_NOTIFY)then begin
sheetHwnd:=getparent(hwndDlg);//获得属性页的窗口句柄
case PNMHdr(lparam)^.code of
//页面失去焦点
PSN_KILLACTIVE:begin
SetWindowLong(hwndDlg, DWL_MSGRESULT, 0);
Result:=TRUE;
end;
end;
end;
except
on e: exception do begin
e.message:='回调处理'+e.message;
messagebox(0, pchar(e.message), '错误', mb_ok);
end;
end;
end;
 
建立同驱动器相关联的属性页扩展用
同上面讲的有两点不同:
IShellExtInit.Initialize方法传递过来的数据对象包含的驱动器路径可能是 CFSTR_MOUNTEDVOLUME 格式而不是 CF_HDROP格式的。标准驱动器是CF_HDROP格式的,而在NTFS文件系统中映射的远程设备则是CFSTR_MOUNTEDVOLUME格式的。
注册表项是HKEY_CLASSES_ROOT子键。
建立控制面板属性页扩展
同上面讲的有两点不同:
控制面板程序调用IShellPropSheetExt.ReplacePage方法来替换页面,它不调用 IShellPropSheetExt。AddPages方法。
注册方式:子键可以在不同位置创建,这依赖于扩展是针对用户还是针对机器的。 对用户方式子键是HKEY_CURRENT_USER_PATH_CONTROLPANEL,否则子键是 HKEY_LOCAL_MACHINE_PATH_CONTROLSFOLDER。
本程序在Delphi5,Win NT 4.0,K6-233系统下调试成功。例子程序可以到
http://chaozhi.com/lgc去下载
 
顶部