Windows 7程式開發系列之二(JumpList篇1 – User Task)

來源:互聯網
上載者:User

      相對於上一篇中工作列特性的開發,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方法中加入下面代碼:

void CreateJumpList()<br />{<br />HRESULT hr;<br />//建立List<br />ICustomDestinationList *pList = NULL;<br />hr = CoCreateInstance(CLSID_DestinationList, NULL,<br />CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pList));<br />if(SUCCEEDED(hr))<br />{<br />//BeginList<br />UINT uMinSlots;<br />IObjectArray *pOARemoved = NULL;<br />hr = pList->BeginList(&uMinSlots, IID_PPV_ARGS(&pOARemoved));<br />if(SUCCEEDED(hr))<br />{<br />//ObjectCollection<br />IObjectCollection *pOCTasks = NULL;<br />hr = CoCreateInstance(CLSID_EnumerableObjectCollection, NULL,<br />CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pOCTasks));<br />if(SUCCEEDED(hr))<br />{<br />hr = AddShellLink(pOCTasks);<br />if(SUCCEEDED(hr))<br />{<br />//ObjectArray<br />IObjectArray *pOATasks = NULL;<br />hr = pOCTasks->QueryInterface(IID_PPV_ARGS(&pOATasks));<br />if(SUCCEEDED(hr))<br />{<br />hr = pList->AddUserTasks(pOATasks);<br />if(SUCCEEDED(hr))<br />{<br />hr = pList->CommitList();<br />}<br />pOATasks->Release();<br />}<br />}<br />pOCTasks->Release();<br />}<br />pOARemoved->Release();<br />}<br />pList->Release();<br />}<br />}

二、IShellLink介面

       上面的代碼還不能編譯,AddShellLink方法還沒有編寫。這個方法用於在IObjectCollection

中加入一個IShellLink

對象。

IShellLink

介面有幾個方法用於設定屬性:1、SetPath

:設定目標的路徑。2、SetWorkingDirectory

設定工作目錄。3、SetIconLocation

設定表徵圖。4、SetArguments

設定命令列參數。5、設定標題(這一步稍微複雜一點、在獨立的方法中設定)。設定完屬性後,調用

IObjectCollection

AddObject

方法,將捷徑加入。

HRESULT AddShellLink( IObjectCollection *pOCTasks )<br />{<br />HRESULT hr;<br />//建立ShellLink<br />IShellLink *pSLAutoRun = NULL;<br />hr = CoCreateInstance(CLSID_ShellLink, NULL,<br />CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSLAutoRun));<br />if(SUCCEEDED(hr))<br />{<br />//應用程式路徑<br />hr = pSLAutoRun->SetPath(_T("C://Windows//notepad.exe"));<br />if(SUCCEEDED(hr))<br />{<br />hr = pSLAutoRun->SetWorkingDirectory(_T("C://"));<br />if(SUCCEEDED(hr))<br />{<br />//表徵圖<br />hr = pSLAutoRun->SetIconLocation(_T("C://Windows//notepad.exe"), 0);<br />if(SUCCEEDED(hr))<br />{<br />//命令列參數<br />hr = pSLAutoRun->SetArguments(_T("Test.txt"));<br />if(SUCCEEDED(hr))<br />{<br />hr = SetTitle(pSLAutoRun, _T("Notepad"));<br />if(SUCCEEDED(hr))<br />{<br />hr = pOCTasks->AddObject(pSLAutoRun);<br />}<br />}<br />}<br />}<br />}<br />pSLAutoRun->Release();<br />}<br />return hr;<br />}

      SetTitle方法用於設定標題,由於IShellLink

介面本身不帶有設定標題的方法。因此需要用到另一個介面IPropertyStore

來設定標題。首先由

IShellLink

介面得到

IPropertyStore

介面,然後由字串初始化一個PROPVARIANT

對象,接下來將該

PROPVARIANT

對象設定為標題,最後提交。

HRESULT SetTitle( IShellLink * pShellLink, LPCTSTR szTitle )<br />{<br />HRESULT hr;<br />//標題<br />IPropertyStore *pPS = NULL;<br />hr = pShellLink->QueryInterface(IID_PPV_ARGS(&pPS));<br />if(SUCCEEDED(hr))<br />{<br />PROPVARIANT pvTitle;<br />hr = InitPropVariantFromString(szTitle,&pvTitle);<br />if(SUCCEEDED(hr))<br />{<br />hr = pPS->SetValue(PKEY_Title, pvTitle);<br />if(SUCCEEDED(hr))<br />{<br />hr = pPS->Commit();<br />}<br />PropVariantClear(&pvTitle);<br />}<br />pPS->Release();<br />}<br />return hr;<br />}

      上面的例子將目標路徑設定為記事本的路徑,並且加上命令列參數"Test.txt",當點擊後,將調用記事本並傳入參數"Test.txt"。

三、由當前程式執行個體響應使用者任務

       現在問題的是,在大多數情況下,使用者任務應該不僅僅對應的是一個指向某個程式的捷徑,而是應該對應的是當前程式的某個功能。比如在foobar2000中的參數選項,對應著程式中的功能。而且當選擇這個使用者任務的時候,應該是由當前程式的當前執行個體來響應這個操作,而不是由新的執行個體來完成這個操作。接下來要做的就是如何將使用者任務反映到當前程式執行個體中。

       這裡涉及到兩個問題。第一個問題是,程式必須是單一實例應用程式,因為當點擊使用者任務時,我們不能由新的程式執行個體來響應。解決辦法是當程式啟動時,檢查該程式是否已有先前執行個體在運行,如果有,則退出,防止第二個執行個體運行。第二個問題是,我們的使用者任務實際是通過捷徑傳遞給程式的參數來反映的。而這個參數只有第二個執行個體能夠收到。那麼在第二個執行個體退出之前,要想辦法把參數傳遞到第一個執行個體。

       我們先把之前建立的使用者任務改為指向自己。修改AddShellLink方法如下,其中注釋的地方即為修改過的地方:

HRESULT AddShellLink( IObjectCollection *pOCTasks )<br />{<br />HRESULT hr;<br />//建立ShellLink<br />IShellLink *pSLAutoRun = NULL;<br />hr = CoCreateInstance(CLSID_ShellLink, NULL,<br />CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSLAutoRun));<br />if(SUCCEEDED(hr))<br />{<br />//應用程式路徑<br />//hr = pSLAutoRun->SetPath(_T("C://Windows//notepad.exe"));<br />//擷取應用程式路徑<br />TCHAR szPath[MAX_PATH];<br />GetModuleFileName(GetModuleHandle(NULL), szPath, MAX_PATH);<br />hr = pSLAutoRun->SetPath(szPath);<br />if(SUCCEEDED(hr))<br />{<br />//hr = pSLAutoRun->SetWorkingDirectory(_T("C://"));<br />if(SUCCEEDED(hr))<br />{<br />//表徵圖<br />//hr = pSLAutoRun->SetIconLocation(_T("C://Windows//notepad.exe"), 0);<br />hr = pSLAutoRun->SetIconLocation(szPath, 0);<br />if(SUCCEEDED(hr))<br />{<br />//命令列參數<br />//hr = pSLAutoRun->SetArguments(_T("Test.txt"));<br />hr = pSLAutoRun->SetArguments(_T("/Task1"));<br />if(SUCCEEDED(hr))<br />{<br />//hr = SetTitle(pSLAutoRun, _T("Notepad"));<br />hr = SetTitle(pSLAutoRun, _T("User Task 1"));<br />if(SUCCEEDED(hr))<br />{<br />hr = pOCTasks->AddObject(pSLAutoRun);<br />}<br />}<br />}<br />}<br />}<br />pSLAutoRun->Release();<br />}<br />return hr;<br />}

       現在的情況是,當我們點擊User Task 1時,一個新的執行個體啟動了。接下來就來解決上面提到的問題。


       第一個問題的解決方案是使用Mutex(互斥量)。當第一個執行個體啟動時,建立一個互斥量。當第二個執行個體啟動時,同樣建立這個互斥量。由於該互斥量已經建立,所以必然導致失敗,第二個執行個體退出。當第一個執行個體結束的時候,撤銷該互斥量。在WinMain函數的開頭加上下面代碼:

HANDLE hMutex = CreateMutexEx(NULL, _T("Local//MutexTestJumpList"), 0, 0);<br />if (hMutex == NULL)<br />{//Mutex建立失敗,已有先前執行個體運行,退出當前執行個體<br />return 0;<br />}

WinMain函數的結尾加上下面代碼:

if(hMutex != NULL)<br />{<br />CloseHandle(hMutex);<br />}

這次當我們點擊User Task 1時已經看不見新的執行個體啟動了(實際上新的執行個體仍然啟動了,只不過檢測到先前執行個體後,迅速退出了)。

      第二個問題是如何將參數傳遞到第一個執行個體。對於Windows程式來說,最容易想到的就是通過訊息發送了。但又如何得到先前執行個體的主視窗控制代碼呢?這裡要用到記憶體對應檔(Memory Mapped File)。第一個執行個體運行後,將自己的主視窗控制代碼放入到一個記憶體對應檔中,第二個執行個體從該記憶體對應檔中讀出先前執行個體的主視窗控制代碼。在視窗建立之後加入下面代碼,將當前主視窗控制代碼放入記憶體對應檔:

//將主視窗控制代碼放入記憶體對應檔<br />HANDLE hMMFFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,<br />PAGE_READWRITE, 0, sizeof(g_hWnd), _T("Local//MMFTestJumpList"));<br />LPVOID lpVoid = MapViewOfFile(hMMFFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);<br />memcpy_s(lpVoid, sizeof(g_hWnd), &g_hWnd, sizeof(g_hWnd));<br />UnmapViewOfFile(hMMFFile);

該記憶體對應檔的名字不要和之前Mutex的名字相同,否則會建立失敗。在程式退出之前要關閉檔案:

CloseHandle(hMMFFile);

在WinMain函數開頭,檢測到有先前執行個體運行之後。從這個記憶體對應檔讀入先前執行個體的主視窗控制代碼:

//擷取先前執行個體主視窗控制代碼<br />HANDLE hMMFFile = OpenFileMapping(FILE_MAP_READ, FALSE, _T("Local//MMFTestJumpList"));<br />if (hMMFFile == NULL) return 0;<br />LPVOID lpVoid = MapViewOfFile(hMMFFile, FILE_MAP_READ, 0, 0, 0);<br />HWND hWndPrev = NULL;<br />memcpy_s(&hWndPrev, sizeof(hWndPrev), lpVoid, sizeof(hWndPrev));<br />UnmapViewOfFile(hMMFFile);<br />CloseHandle(hMMFFile);

得到先前執行個體的視窗控制代碼後,利用WM_COPYDATA訊息,將命令列參數發送到先前執行個體。

 //將命令列參數發送到先前執行個體<br />COPYDATASTRUCT cpdata = {0};<br />cpdata.dwData = 1;<br />cpdata.lpData = szCmdLine;<br />cpdata.cbData = (_tcslen(szCmdLine)+1)*sizeof(TCHAR);<br />SendMessage(hWndPrev, WM_COPYDATA, 0, reinterpret_cast<LPARAM>(&cpdata));

然後加入對WM_COPYDATA訊息的處理:

case WM_COPYDATA://從下一個執行個體傳來命令列參數<br />{<br />COPYDATASTRUCT *pdata = reinterpret_cast<COPYDATASTRUCT *>(lParam);<br />if(pdata->dwData == 1)<br />{<br />LPCTSTR szCmdLine = reinterpret_cast<LPCTSTR>(pdata->lpData);<br />if(szCmdLine != NULL)<br />HandleCmdLine(szCmdLine);<br />}<br />return 0;<br />}<br />break;

在HandleCmdLine中,我們只簡單顯示一條訊息即可:

void HandleCmdLine( LPCTSTR szCmdLine )<br />{<br />if(NULL != StrStr(szCmdLine, _T("/Task1")))<br />{<br />MessageBox(g_hWnd, _T("User Task 1 Clicked"), NULL, MB_OK);<br />}<br />}

驗證一下現在的效果:

因為JumpList即使在程式沒有運行時也是存在的,所以我們在視窗建立之後也要調用一次HandleCmdLine方法,以使第一個程式執行個體可以響應靜止狀態下的使用者點擊。如下面的效果:

此時程式未運行,只是鎖定到工作列,點擊JumpList上的使用者任務,第一個程式執行個體啟動,並響應使用者任務

       為了簡化問題,上面的代碼中省去了很多錯誤處理。在實際的使用中,我將單一實例運行與執行個體間的資料傳遞封裝在了一個輔助的類中,原始碼中將會提供這個類。

       上面代碼需要包含的標頭檔:

#include <ShlObj.h><br />#include <propvarutil.h><br />#include <propkey.h>

      需要串連的庫:shlwapi.lib

 

     本節原始碼下載

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.