在Windows中,用鼠标右键单击文件或者文件夹时弹出的那个菜单便称为上下文相关菜单。要动态地在上下文相关菜单中增添菜单项,可以通过写Context Menu Handler来实现。比如大家所熟悉的WinZip和UltraEdit等软件都是通过编写Context Menu Handler来动态地向菜单中增添菜单项的。如果系统中安装了WinZip,那么当用右键单击一个名为abc的文件(夹)时,其上下文相关菜单就会有一个名为Add to abc.zip的菜单项。本文要实现的Context Menu Handler与WinZip提供的上下文菜单相似,它将在任意类型文件的上下文菜单中增加一个名为“用写字板打开XXX”(其中XXX为当前选定的文件名称)的菜单项,只要你选择该菜单项,Windows就会启动写字板并打开当前所选的文件      编写Context Menu Handler必须实现IShellExtInit和IContextMenu两个接口。除了IUnknown接口所定义的函数之外,Context Menu Handler还需要用到QueryContextMenu、InvokeCommand和GetCommandString这三个非常重要的成员函数。   (1)QueryContextMenu函数:每当系统要显示一个文件对象的上下文相关菜单时,它首先要调用该函数。为了在上下文相关菜单中添加菜单项,我们在该函数中调用InsertMenu函数。   (2)InvokeCommand函数:当用户选定了某个Context Menu Handler登记过的菜单项后,该函数将会被调用,系统将会传给该函数一个指向LPCMINVOKECOMMANDINFO结构的指针。在该函数中要执行与所选菜单项相对应的操作。   (3)GetCommandString函数:当鼠标指针移到一个上下文相关菜单项上时,在当前窗口的状态条上将会出现与该菜单项相关的帮助信息,此信息就是系统通过调用该函数获取的。   具体编写方法请参阅网上的程序实例,网址为www.pccomputing.com.cn。   3.增添上下文相关菜单项说明   如果要静态地为目录或者某一类文件增添上下文相关菜单项,那么就用不着编写Context Menu Handler,可以通过直接修改Windows注册表来达到此目的。比如,可以将下面的内容存成一个扩展名为.REG的文件,然后双击它将其导入注册表,你会发现所有类型文件的上下文相关菜单中都多了一个名叫“记事本”的菜单项。   REGEDIT4   [HKEY_CLASSES_ROOT\*\shell\记事本]   [HKEY_CLASSES_ROOT\*\shell\记事本\command]   @="notepad.exe\"%1\""   通过比较,很容易发现这两种方式所得结果的差异。通过直接修改注册表来增添菜单项的确比较简单,然而它不具有交互性,所增添的菜单项是静态的,并且所能实现的功能也非常有限。但是Context Menu Handler则不同,它使我们可以根据上下文的具体情况动态地添加菜单项,比如可以判断当前选定的是哪一类文件、是不是文件夹、选定的文件(夹)的个数以及获取被选定文件(夹)的属性。有时,这些信息对于程序很有用,如果需要得到此类信息,并且需要根据不同的上下文来执行不同的操作,那么只好依靠Context Menu Hander来实现。本文的实例中,其动态性体现在仅当用户选定了一个文件时,才会在上下文相关菜单中增添菜单项,并且菜单项的名字随着所选文件名的不同而相应地变化。

解决方案 »

  1.   

    to squiffy:
    谢谢!
    第一个方法我还在写程序。第二个方法对于WINDOWS NT
      

  2.   

    to squiffy:
    谢谢!
    第一个方法我还在写程序。第二个方法对于WINDOWS NT 好象有问题??
      

  3.   

    to squiffy:
    可怜可怜我吧!有没有例程给我一个:-)。
      

  4.   

    用Tregistery类来编写第二中方式,在程序启动时检查是否有该右键的相应注册表项,如果没有就自动添加,在程序退出时也可以在程序卸载时去除注册表相应选项。
    也算时“动态吧”:)
    注意需要在uses里加上registery才能使用,其实说句实在话,直接用Api也未尝不可,不过Tregistery类提供的属性比较符合delphi的语法特点。
      

  5.   

    有一个类似的菜单扩展的例子:
    //Copy的。
    用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+'\shellex\PropertySheetHandlers\Wav',TRUE);
           WriteString('',Classid);
         finally
           Free;
         end;     if(Win32Platform=VER_PLATFORM_WIN32_NT)then
         begin
           with TRegistry.Create do
           try
             RootKey:=HKEY_LOCAL_MACHINE;
             OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions', 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\bin\brcc32.exe编译的。
          psp.pszTemplate:=MakeIntResource(100);
          //标题名  
          psp.pszTitle:='波文件信息';
          //设定回调函数
          psp.pfnDlgProc:=@DialogProc;
          psp.pfnCallBack:=@PropCallback;
          //设定对象引用记数变量  
          psp.pcRefParent:[email protected];      //用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:[email protected]','关于...',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\Drive\Shellex\PropertySheetHandlers子键。 
    建立控制面板属性页扩展同上面讲的有两点不同: 控制面板程序调用IShellPropSheetExt.ReplacePage方法来替换页面,它不调用 IShellPropSheetExt。AddPages方法。 
    注册方式:子键可以在不同位置创建,这依赖于扩展是针对用户还是针对机器的。 对用户方式子键是HKEY_CURRENT_USER\REGSTR_PATH_CONTROLPANEL,否则子键是 HKEY_LOCAL_MACHINE\REGSTR_PATH_CONTROLSFOLDER。 本程序在Delphi5,Win NT 4.0,K6-233系统下调试成功。例子程序可以到
    http://chaozhi.com/lgc去下载 
      

  6.   

    to kingron :
    非常感谢你的热心帮助,不过http://chaozhi.com/lgc我好象怎么也打不开,能麻烦你一点时间把例城和帮助文档发到我的信箱来吗!
    谢谢大家!