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