Windows 7程序开发系列之二(JumpList篇1 - User Task)

来源:互联网 发布:淘宝上真正的原单店 编辑:程序博客网 时间:2024/06/05 23:52

相对于上一篇中任务栏特性的开发,JumpList的开发显得稍微麻烦一些。JumpList将分为两次讲解,这次先讲解如何添加用户任务(User Task)。同样以foobar2000为例,当右键点击任务栏按钮时,显示程序的JumpList。

       最下方3个项目为系统任务,一般不需要我们去操作。上方的两个任务:播放、参数选项,即为自定义的用户任务。用户任务本质上是一个快捷方式,对应于程序中由IShellLink接口表示。

一、ICustomDestinationList接口

      同样先创建一个窗口,然后添加一个CreateJumpList方法,在这个方法中创建JumpList。创建JumpList需要几个步骤:1、创建 ICustomDestinationList 接口,这个接口对应的就是JumpList。2、调用BeginList 方法。3、创建IObjectCollection 接口。4、向 IObjectCollection 中添加快捷方式。5、由 IObjectCollection 接口取得IObjectArray 接口。6、将 IObjectArray 加入 ICustomDestinationList 。7、调用CommitList 方法。在CreateJumpList方法中加入下面代码:

[cpp] view plaincopy
  1. void CreateJumpList()  
  2. {  
  3.     HRESULT hr;  
  4.     //创建List  
  5.     ICustomDestinationList *pList = NULL;  
  6.     hr = CoCreateInstance(CLSID_DestinationList, NULL,   
  7.         CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pList));  
  8.     if(SUCCEEDED(hr))  
  9.     {  
  10.         //BeginList  
  11.         UINT uMinSlots;  
  12.         IObjectArray *pOARemoved = NULL;  
  13.         hr = pList->BeginList(&uMinSlots, IID_PPV_ARGS(&pOARemoved));  
  14.         if(SUCCEEDED(hr))  
  15.         {  
  16.             //ObjectCollection  
  17.             IObjectCollection *pOCTasks = NULL;  
  18.             hr = CoCreateInstance(CLSID_EnumerableObjectCollection, NULL,  
  19.                 CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pOCTasks));  
  20.             if(SUCCEEDED(hr))  
  21.             {  
  22.                 hr = AddShellLink(pOCTasks);  
  23.                 if(SUCCEEDED(hr))  
  24.                 {  
  25.                     //ObjectArray  
  26.                     IObjectArray *pOATasks = NULL;  
  27.                     hr = pOCTasks->QueryInterface(IID_PPV_ARGS(&pOATasks));  
  28.                     if(SUCCEEDED(hr))  
  29.                     {  
  30.                         hr = pList->AddUserTasks(pOATasks);  
  31.                         if(SUCCEEDED(hr))  
  32.                         {  
  33.                             hr = pList->CommitList();  
  34.                         }  
  35.                         pOATasks->Release();  
  36.                     }  
  37.                 }  
  38.                 pOCTasks->Release();  
  39.             }  
  40.             pOARemoved->Release();  
  41.         }  
  42.         pList->Release();  
  43.     }  
  44. }  

二、IShellLink接口

       上面的代码还不能编译,AddShellLink方法还没有编写。这个方法用于在IObjectCollection 中加入一个IShellLink 对象。 IShellLink 接口有几个方法用于设置属性:1、SetPath :设置目标的路径。2、SetWorkingDirectory 设置工作目录。3、SetIconLocation 设置图标。4、SetArguments 设置命令行参数。5、设置标题(这一步稍微复杂一点、在独立的方法中设置)。设置完属性后,调用 IObjectCollection 的AddObject 方法,将快捷方式加入。

[cpp] view plaincopy
  1. HRESULT AddShellLink( IObjectCollection *pOCTasks )  
  2. {  
  3.     HRESULT hr;  
  4.     //创建ShellLink  
  5.     IShellLink *pSLAutoRun = NULL;  
  6.     hr = CoCreateInstance(CLSID_ShellLink, NULL,   
  7.         CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSLAutoRun));  
  8.     if(SUCCEEDED(hr))  
  9.     {  
  10.         //应用程序路径  
  11.         hr = pSLAutoRun->SetPath(_T("C://Windows//notepad.exe"));  
  12.         if(SUCCEEDED(hr))  
  13.         {  
  14.             hr = pSLAutoRun->SetWorkingDirectory(_T("C://"));  
  15.             if(SUCCEEDED(hr))  
  16.             {  
  17.                 //图标  
  18.                 hr = pSLAutoRun->SetIconLocation(_T("C://Windows//notepad.exe"), 0);  
  19.                 if(SUCCEEDED(hr))  
  20.                 {  
  21.                     //命令行参数  
  22.                     hr = pSLAutoRun->SetArguments(_T("Test.txt"));  
  23.                     if(SUCCEEDED(hr))  
  24.                     {  
  25.                         hr = SetTitle(pSLAutoRun, _T("Notepad"));  
  26.                         if(SUCCEEDED(hr))  
  27.                         {  
  28.                             hr = pOCTasks->AddObject(pSLAutoRun);  
  29.                         }  
  30.                     }  
  31.                 }  
  32.             }  
  33.         }  
  34.         pSLAutoRun->Release();  
  35.     }  
  36.     return hr;  
  37. }  

      SetTitle方法用于设置标题,由于IShellLink 接口本身不带有设置标题的方法。因此需要用到另一个接口IPropertyStore 来设置标题。首先由 IShellLink 接口得到 IPropertyStore 接口,然后由字符串初始化一个PROPVARIANT 对象,接下来将该 PROPVARIANT 对象设置为标题,最后提交。

[cpp] view plaincopy
  1. HRESULT SetTitle( IShellLink * pShellLink, LPCTSTR szTitle )  
  2. {  
  3.     HRESULT hr;  
  4.     //标题  
  5.     IPropertyStore *pPS = NULL;  
  6.     hr = pShellLink->QueryInterface(IID_PPV_ARGS(&pPS));  
  7.     if(SUCCEEDED(hr))  
  8.     {  
  9.         PROPVARIANT pvTitle;  
  10.         hr = InitPropVariantFromString(szTitle,&pvTitle);  
  11.         if(SUCCEEDED(hr))  
  12.         {  
  13.             hr = pPS->SetValue(PKEY_Title, pvTitle);  
  14.             if(SUCCEEDED(hr))  
  15.             {  
  16.                 hr = pPS->Commit();  
  17.             }  
  18.             PropVariantClear(&pvTitle);  
  19.         }  
  20.         pPS->Release();  
  21.     }  
  22.     return hr;  
  23. }  

      上面的例子将目标路径设置为记事本的路径,并且加上命令行参数"Test.txt",当点击后,将调用记事本并传入参数"Test.txt"。

三、由当前程序实例响应用户任务

       现在问题的是,在大多数情况下,用户任务应该不仅仅对应的是一个指向某个程序的快捷方式,而是应该对应的是当前程序的某个功能。比如在foobar2000中的参数选项,对应着程序中的功能。而且当选择这个用户任务的时候,应该是由当前程序的当前实例来响应这个操作,而不是由新的实例来完成这个操作。接下来要做的就是如何将用户任务反映到当前程序实例中。

       这里涉及到两个问题。第一个问题是,程序必须是单实例应用程序,因为当点击用户任务时,我们不能由新的程序实例来响应。解决办法是当程序启动时,检查该程序是否已有先前实例在运行,如果有,则退出,防止第二个实例运行。第二个问题是,我们的用户任务实际是通过快捷方式传递给程序的参数来反映的。而这个参数只有第二个实例能够收到。那么在第二个实例退出之前,要想办法把参数传递到第一个实例。

       我们先把之前创建的用户任务改为指向自己。修改AddShellLink方法如下,其中注释的地方即为修改过的地方:

[cpp] view plaincopy
  1. HRESULT AddShellLink( IObjectCollection *pOCTasks )  
  2. {  
  3.     HRESULT hr;  
  4.     //创建ShellLink  
  5.     IShellLink *pSLAutoRun = NULL;  
  6.     hr = CoCreateInstance(CLSID_ShellLink, NULL,   
  7.         CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSLAutoRun));  
  8.     if(SUCCEEDED(hr))  
  9.     {  
  10.         //应用程序路径  
  11.         //hr = pSLAutoRun->SetPath(_T("C://Windows//notepad.exe"));  
  12.         //获取应用程序路径  
  13.         TCHAR szPath[MAX_PATH];  
  14.         GetModuleFileName(GetModuleHandle(NULL), szPath, MAX_PATH);  
  15.         hr = pSLAutoRun->SetPath(szPath);  
  16.         if(SUCCEEDED(hr))  
  17.         {  
  18.             //hr = pSLAutoRun->SetWorkingDirectory(_T("C://"));  
  19.             if(SUCCEEDED(hr))  
  20.             {  
  21.                 //图标  
  22.                 //hr = pSLAutoRun->SetIconLocation(_T("C://Windows//notepad.exe"), 0);  
  23.                 hr = pSLAutoRun->SetIconLocation(szPath, 0);  
  24.                 if(SUCCEEDED(hr))  
  25.                 {  
  26.                     //命令行参数  
  27.                     //hr = pSLAutoRun->SetArguments(_T("Test.txt"));  
  28.                     hr = pSLAutoRun->SetArguments(_T("/Task1"));  
  29.                     if(SUCCEEDED(hr))  
  30.                     {  
  31.                         //hr = SetTitle(pSLAutoRun, _T("Notepad"));  
  32.                         hr = SetTitle(pSLAutoRun, _T("User Task 1"));  
  33.                         if(SUCCEEDED(hr))  
  34.                         {  
  35.                             hr = pOCTasks->AddObject(pSLAutoRun);  
  36.                         }  
  37.                     }  
  38.                 }  
  39.             }  
  40.         }  
  41.         pSLAutoRun->Release();  
  42.     }  
  43.     return hr;  
  44. }  

       现在的情况是,当我们点击User Task 1时,一个新的实例启动了。接下来就来解决上面提到的问题。


       第一个问题的解决方法是使用Mutex(互斥量)。当第一个实例启动时,创建一个互斥量。当第二个实例启动时,同样创建这个互斥量。由于该互斥量已经创建,所以必然导致失败,第二个实例退出。当第一个实例结束的时候,撤销该互斥量。在WinMain函数的开头加上下面代码:

[cpp] view plaincopy
  1. HANDLE hMutex = CreateMutexEx(NULL, _T("Local//MutexTestJumpList"), 0, 0);  
  2. if (hMutex == NULL)  
  3. {   //Mutex创建失败,已有先前实例运行,退出当前实例  
  4.     return 0;  
  5. }  

WinMain函数的结尾加上下面代码:

[cpp] view plaincopy
  1. if(hMutex != NULL)  
  2. {  
  3.     CloseHandle(hMutex);  
  4. }  

这次当我们点击User Task 1时已经看不见新的实例启动了(实际上新的实例仍然启动了,只不过检测到先前实例后,迅速退出了)。

      第二个问题是如何将参数传递到第一个实例。对于Windows程序来说,最容易想到的就是通过消息发送了。但又如何得到先前实例的主窗口句柄呢?这里要用到内存映射文件(Memory Mapped File)。第一个实例运行后,将自己的主窗口句柄放入到一个内存映射文件中,第二个实例从该内存映射文件中读出先前实例的主窗口句柄。在窗口创建之后加入下面代码,将当前主窗口句柄放入内存映射文件:

[cpp] view plaincopy
  1. //将主窗口句柄放入内存映射文件  
  2. HANDLE hMMFFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,   
  3.     PAGE_READWRITE, 0, sizeof(g_hWnd), _T("Local//MMFTestJumpList"));  
  4. LPVOID lpVoid = MapViewOfFile(hMMFFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);  
  5. memcpy_s(lpVoid, sizeof(g_hWnd), &g_hWnd, sizeof(g_hWnd));  
  6. UnmapViewOfFile(hMMFFile);  

该内存映射文件的名字不要和之前Mutex的名字相同,否则会创建失败。在程序退出之前要关闭文件:

[cpp] view plaincopy
  1. CloseHandle(hMMFFile);  

在WinMain函数开头,检测到有先前实例运行之后。从这个内存映射文件读入先前实例的主窗口句柄:

[cpp] view plaincopy
  1. //获取先前实例主窗口句柄  
  2. HANDLE hMMFFile = OpenFileMapping(FILE_MAP_READ, FALSE, _T("Local//MMFTestJumpList"));  
  3. if (hMMFFile == NULL) return 0;  
  4. LPVOID lpVoid = MapViewOfFile(hMMFFile, FILE_MAP_READ, 0, 0, 0);  
  5. HWND hWndPrev = NULL;  
  6. memcpy_s(&hWndPrev, sizeof(hWndPrev), lpVoid, sizeof(hWndPrev));  
  7. UnmapViewOfFile(hMMFFile);  
  8. CloseHandle(hMMFFile);  

得到先前实例的窗口句柄后,利用WM_COPYDATA消息,将命令行参数发送到先前实例。

 

[cpp] view plaincopy
  1. //将命令行参数发送到先前实例  
  2. COPYDATASTRUCT cpdata = {0};  
  3. cpdata.dwData = 1;  
  4. cpdata.lpData = szCmdLine;  
  5. cpdata.cbData = (_tcslen(szCmdLine)+1)*sizeof(TCHAR);  
  6. SendMessage(hWndPrev, WM_COPYDATA, 0, reinterpret_cast<LPARAM>(&cpdata));  

然后加入对WM_COPYDATA消息的处理:

[cpp] view plaincopy
  1. case WM_COPYDATA:   //从下一个实例传来命令行参数  
  2.     {  
  3.         COPYDATASTRUCT *pdata = reinterpret_cast<COPYDATASTRUCT *>(lParam);  
  4.         if(pdata->dwData == 1)  
  5.         {  
  6.             LPCTSTR szCmdLine = reinterpret_cast<LPCTSTR>(pdata->lpData);  
  7.             if(szCmdLine != NULL)  
  8.                 HandleCmdLine(szCmdLine);  
  9.         }  
  10.         return 0;  
  11.     }  
  12.     break;  

在HandleCmdLine中,我们只简单显示一条消息即可:

[cpp] view plaincopy
  1. void HandleCmdLine( LPCTSTR szCmdLine )   
  2. {  
  3.     if(NULL != StrStr(szCmdLine, _T("/Task1")))  
  4.     {  
  5.         MessageBox(g_hWnd, _T("User Task 1 Clicked"), NULL, MB_OK);  
  6.     }  
  7. }  

验证一下现在的效果:

因为JumpList即使在程序没有运行时也是存在的,所以我们在窗口创建之后也要调用一次HandleCmdLine方法,以使第一个程序实例可以响应静止状态下的用户点击。如下面的效果:

此时程序未运行,只是锁定到任务栏,点击JumpList上的用户任务,第一个程序实例启动,并响应用户任务

       为了简化问题,上面的代码中省去了很多错误处理。在实际的使用中,我将单实例运行与实例间的数据传递封装在了一个辅助的类中,源代码中将会提供这个类。

       上面代码需要包含的头文件:

[cpp] view plaincopy
  1. #include <ShlObj.h>  
  2. #include <propvarutil.h>  
  3. #include <propkey.h>  

      需要连接的库:shlwapi.lib

 

     本节源代码下载 


原文链接:http://blog.csdn.net/ntwilford/article/details/5635381

0 0
原创粉丝点击