您的位置:首页 > 运维架构 > Shell

在应用程序中集成外壳的上下文菜单

2004-08-08 18:59 791 查看
关键字:Shell, Namespace, IContextMenu, 外壳, 名字空间, 上下文菜单<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
文章难度:初级
 

介绍(What is the shell contextmenu)

 

随着越来越多的软件对外壳的扩展,资源管理器变得是越来越强大和易用,以前要压缩一个或多个文件,你得老老实实找到压缩软件的快捷方式并打开他,接着进行一系列的选择操作,这对我这种懒人来说绝对是很烦的一件事,而有了外壳的菜单扩展机制,你只需在资源管理器中选好你要操作的文件然后弹出右键菜单选择相应的项就搞定了,多方便啊。

呃,打住,本文并不是要介绍怎么给资源管理器加入一个菜单扩展项(注1),而是介绍怎么在自己的程序中调用那个有着丰富功能的上下文菜单。例如FlashFXP中左边的本地文件窗口(图1):



 
 
(图1:假设你在打开FTP软件想把一些文件上传时才发现这些文件尚未打包,怎么办?切换到资源管理器把他们打包再回到FlashFXP吗?No no no,别做这么没效率的事,在FlashFXP左窗口中把相应文件选中后按住shift键后弹出右键菜单看看,简直太美妙了,以后在FTP上down到什么好音乐我直接就可以用喜欢的播放器来欣赏了。)

 

(注1:基本上认为写一个上下文菜单扩展项是比较容易实现的,有大量的文章和示例代码可从http://www.codeproject.com/shell得到)

 

 

原理(How to implement)

 

我们知道,在win32中是以外壳名字空间(注2)的形式来组织文件系统的,在外壳名字空间里的每一个文件夹都实现了一个IShellFolder的接口,通过这个接口我们可以直接查询或间接得到其他相关的接口。

跟对象(注3)的上下文菜单相关的接口是IContextMenu,通过对象的父文件夹的IShellFolder::GetUIObjectOf方法可得到该接口。得到该接口后,可以用IContextMenu::QueryContextMenu方法来生成上下文菜单的菜单项,用IContextMenu::InvokeCommand调用相应的命令。看起来很简单不是吗?下面就以一个实例来说明具体的实现,包括怎么得到一个对象的父文件夹IShellFolder接口,怎么得到IContextMenu,及怎么调用IContextMenu的各个方法。

 

(注2:关于外壳名字空间的概念,推荐大家看看姜伟华的Windows外壳名字空间的浏览一文,更多资料可从MSDN中得到)

 

(注3:这里的对象指的是外壳名字空间中的一个节点,对象有可能是一个文件夹,有可能是一个文件,也有可能是一个虚拟文件夹,例如:我的电脑,网上邻居,控制面板等)

 

 

具体实现和示例

 

为了简单起见,本例以MFC中的基于对话框工程为基础,在一个ListBox控件中列出C盘下的子文件夹和文件,右键单击ListBox中的每一项会弹出对应的外壳上下文菜单。

(一)  枚举C驱动器下的文件夹和文件并添加到ListBox中:
步骤如下:
a)         用SHGetDesktopFolder得到桌面的IShellFolder接口指针,这是我们取得其他对象IShellFolder的一个途径。
b)        用IShellFolder::ParseDisplayName得到驱动器C的PIDL,接着把得到的PIDL做为参数传给IShellFolder::BindToObject便可得到驱动器C的IShellFolder接口指针了。
c)        用刚才得到C盘的IShellFolder::EnumObjects方法可得到一个枚举器,通过该枚举器可以枚举出C盘下的子文件夹和文件的PIDL。通过GetDisplayNameOf可得到一个PIDL所代表的对象的显示名,于是我们把该名字加到ListBox中,同时用CListBox::SetItemData把相应的PIDL保存起来,以后用得着J
示例代码:
         …

LPMALLOC pMalloc;

    LPITEMIDLIST pidlC = NULL;

    LPITEMIDLIST pidlItems = NULL;

    IShellFolder *psfDeskTop = NULL;

    IShellFolder *psfFolderC = NULL;

    LPENUMIDLIST ppenum = NULL;

    ULONG celtFetched;

    HRESULT hr;

    STRRET strDispName;

    TCHAR pszDisplayName[MAX_PATH];

         ULONG uAttr;

 

         hr = SHGetMalloc(&pMalloc);

         hr = SHGetDesktopFolder(&psfDeskTop);

         hr = psfDeskTop->ParseDisplayName(GetSafeHwnd(), NULL, L"C://", NULL, &pidlC, NULL);

         hr = psfDeskTop->BindToObject(pidlC, NULL, IID_IShellFolder, (void**)&psfFolderC);

         psfDeskTop->Release();

         hr = psfFolderC->EnumObjects(GetSafeHwnd(), SHCONTF_FOLDERS | SHCONTF_NONFOLDERS , &ppenum);

 

         while (hr = ppenum->Next(1, &pidlItems, &celtFetched) == S_OK && (celtFetched) == 1)

    {

        psfFolderC->GetDisplayNameOf(pidlItems, SHGDN_INFOLDER, &strDispName);

 

                   uAttr = SFGAO_FOLDER;

        psfFolderC->GetAttributesOf(1, (LPCITEMIDLIST *) &pidlItems, &uAttr);

                   StrRetToBuf(&strDispName, pidlItems, pszDisplayName, MAX_PATH);

                   CString strDisplayName;

 

                   if(uAttr & SFGAO_FOLDER)

        {

            strDisplayName.Format("%s%s%s","[",pszDisplayName,"]");

        }

                   else

                   {

                            strDisplayName = pszDisplayName;

                   }

                   m_listBox.SetItemData(m_listBox.InsertString(-1, strDisplayName), (DWORD)pidlItems);

    }

        

         ppenum->Release();

         pMalloc->Free(pidlC);

         pMalloc->Release();

         m_psfFolderC = psfFolderC;

    ...
 

(二)  响应鼠标单击消息,获取IContextMenu接口并弹出菜单;
通过C盘的IShellFolder接口的GetUIObjectOf方法我们可以得到该节点的一个或多个指定子节点的IContextMenu接口,该方法原型如下:













 







有了菜单项,我们就可以弹出该菜单了,我们用TPM_RETURNCMD标志指定TrackPopupMenu必须返回用户所选菜单项的ID,以便稍后通过IContextMenu::InvokeCommand来调用真正的Shell动作:

idCmd = Menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RETURNCMD | TPM_RIGHTBUTTON,

                                                                                                pt.x,

                                                                                                pt.y,

                                                                                                AfxGetMainWnd());

if(idCmd)

{

         CMINVOKECOMMANDINFO  cmi;

         cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);

         cmi.fMask = 0;

         cmi.hwnd = GetSafeHwnd();

         cmi.lpVerb = (LPCSTR)(INT_PTR)(idCmd - 1);

         cmi.lpParameters = NULL;

         cmi.lpDirectory = NULL;

         cmi.nShow = SW_SHOWNORMAL;

         cmi.dwHotKey = 0;

         cmi.hIcon = NULL;

         hr = pcm->InvokeCommand(&cmi);

}

//注:事实上还有两个跟上下文菜单相关的接口,分别为IContextMenu2和IContextMenu3,他们是用来实现扩展菜单项的自画行为的,这里为简单起见,并没有调用他们,在通常使用中我们必须有查询这两个接口并在我们的窗口收到WM_DRAWITEM/ WM_MENUCHAR/ WM_MEASUREITEM/ WM_INITMENUPOPUP等菜单相关的消息时调用这两个接口的相关方法,否则那些自画菜单项和子级菜单项都不会正常显示。具体请看本文附带源码。
 

(三)  不弹出菜单直接调用菜单项相应的命令?
大家还记得怎么显示一个文件或文件夹的属性对话框吗?

Yes,用ShellExecuteEx并指定SHELLEXECUTEINFO的
lpVerb
域为
properties就可,但是这种方法只能查看一个文件的属性,怎么同时查看多个的?

要知道ShellExecuteEx查看文件属性最终也是靠IContextMenu帮忙的,所以答案还是在IContextMenu上,我们只要在调用GetUIObjectOf时把想查看的文件或文件件的PIDL做为参数传进去,然后直接调用InvokeCommand方法就OK啦。

if (m_psfFolderC != NULL)

{

         HRESULT        hr;

         IContextMenu   *pcm = NULL;

         hr = m_psfFolderC->GetUIObjectOf(GetSafeHwnd(),aryListBoxSel.GetSize(), (LPCITEMIDLIST*)pPidls,IID_IContextMenu, NULL, (void**)&pcm);

         if(SUCCEEDED(hr) && pcm != NULL)

         {

                   CMINVOKECOMMANDINFO  cmi;

                   cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);

                   cmi.fMask = 0;

                   cmi.hwnd = GetSafeHwnd();

                   cmi.lpVerb = "properties";//在弹出菜单的时候,这里是用户选择的cmdid,但这时候我们没弹出菜单,所以只能以verb来调用了,关于verb的更多信息请参考MSDN

                   cmi.lpParameters = NULL;

                   cmi.lpDirectory = NULL;

                   cmi.nShow = SW_SHOWNORMAL;

                   cmi.dwHotKey = 0;

                   cmi.hIcon = NULL;

                   hr = pcm->InvokeCommand(&cmi);

                   pcm->Release();

         }

}
 

 

后记和参考

         其实现在网上介绍shell编程的文章不少(当然,大部分是英文的,国内能找到的多是托盘编程L),在www.codeproject.com上就有不少好文章,但由于他们侧重于reusable上,用C++把乱七八糟的都封装了,反而使我们没法很容易的学习外壳扩展和调用的机制,本文以最直白的方法向你展示了怎么在你的程序中调用Shell。

         当然,小弟水平有限,文中有错误难免,非常希望得到您的指正。

 

       本文演示源码下载: http://iunknown.com.cn/csdn/kShellContextMenu.rar

 

         参考:

 Windows外壳名字空间的浏览 by 姜伟华

 

Use Shell ContextMenu in your applications

(该文作者提供了一个类,在你的程序中调用shell ContextMenu只需两三行代码,很方便J)

 

Platform sample: EnumDesk

 

MSDN Library->Platform SDK Document->User Interface Services->Windows Shell

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐