Windows資源管理器相關資訊獲取
原文連結
翻譯參考
有的時候,軟體開發是創造新的東西,不過更常見的是把現有的東西組合到一起。今天的難題就屬於後一種。
給定一個視窗控制代碼,你可以判定:(1)是否是一個資源管理器視窗,如果是,那麼(2)它正在顯示哪個資料夾,而且(3)當前焦點在哪一項上。
這其實不是一件難事。你只需把許多小碎片拼湊起來就可以。
一切從 ShellWindows 物件開始,它代表所有開啟的外殼視窗。你可以使用 Item 屬性遍歷它們。用 C++ 來做這事看起來有點笨拙,因為 ShellWindows 物件就是為像 JScript 或者 Visual Basic 這樣的指令碼語言的使用所設計的。
IShellWindows *psw;
if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
IID_IShellWindows, (void**)&psw))) {
VARIANT v;
V_VT(&v) = VT_I4;
IDispatch *pdisp;
BOOL fFound = FALSE;
for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
V_I4(&v)++) {
...
pdisp->Release();
}
psw->Release();
}
對於每一個元素,我們可以詢問其視窗控制代碼以判斷是否是我們需要的。
IWebBrowserApp *pwba;
if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
HWND hwndWBA;
if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
hwndWBA == hwndFind) {
fFound = TRUE ;
...
}
pwba->Release();
}
不錯,現在我們已經通過各元素的 IWebBrowserApp 介面找到了目標,接下來需要得到頂級外殼瀏覽器物件(top shell browser)。這可以通過查詢 SID_STopLevelBrowser 服務並請求 IShellBrowser 介面來完成。
IServiceProvider *psp;
if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
IShellBrowser *psb;
if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
IID_IShellBrowser, (void**)&psb))) {
...
psb->Release();
}
psp->Release();
}
我們再使用 QueryActiveShellView 方法從 IShellBrowser 介面請求當前的外殼檢視(shell view)。
IShellView *psv;
if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
...
psv->Release();
}
當然,我們真正想要的其實是 IFolderView 介面,它是一個自動化物件,囊括了所有的好東西。
IFolderView *pfv;
if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
(void**)&pfv))) {
...
pfv->Release();
}
挺好。想從視圖裡得到些什麼呢,正被瀏覽的 IShellFolder 的位置怎麼樣?做這件事情,我們需要使用 IPersistFolder2::GetCurFolder。GetFolder 方法可以使我們訪問到外殼資料夾(shell folder),我們要從那裡請求 IPersistFolder2。(大部分情況下你需要 IShellFolder 介面,因為大多數的酷玩意兒都在那兒。)
IPersistFolder2 *ppf2;
if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
(void**)&ppf2))) {
LPITEMIDLIST pidlFolder;
if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
...
CoTaskMemFree(pidlFolder);
}
ppf2->Release();
}
我們把 pidl 轉換為用於顯示的路徑。
if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
lstrcpyn(g_szPath, TEXT("<not a directory>"), MAX_PATH);
}
...
用我們已經得到的這些可以幹什麼呢?哦,對了,來看一下當前的焦點在什麼上吧。
int iFocus;
if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
...
}
要把聚焦項的名字顯示出來,我們需要該項的 pidl 和 IShellFolder。(看看,我告訴過你好東西就在 IShellFolder 那兒)。項可以通過 Item 方法得到。
LPITEMIDLIST pidlItem;
if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
...
CoTaskMemFree(pidlItem);
}
(如果想得到選中項的列表,我們可以使用 Items 方法,需要傳入 SVGIO_SELECTION。)
得到 pidl 之後,還需要 IShellFolder:
IShellFolder *psf;
if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
(void**)&psf))) {
...
psf->Release();
}
然後我們用這兩樣東西以及 GetDisplayNameOf 方法的輔助來獲取項的顯示名字。
STRRET str;
if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
SHGDN_INFOLDER,
&str))) {
...
}
我們可以使用輔助函式 StrRetToBuf 把那個乖僻的 STRRET 結構轉換為令人生厭的字串緩衝區。(怪異的 STRRET 結構的歷史容以後再說。)
StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);
好,下面我們把所有這些都組合起來。程式碼看起來相當醜陋,因為我把所有東西都放到了一個大函式裡而沒有把它們分離為子函式。在“現實生活”中,我會把它們分為小的輔助函式,那樣更便於管理。
開啟 scratch(譯者注:原作者的一個測試用程式原型) 程式,新增一下函式:
#include <shlobj.h>
#include <exdisp.h>
TCHAR g_szPath[MAX_PATH];
TCHAR g_szItem[MAX_PATH];
void CALLBACK RecalcText(HWND hwnd, UINT, UINT_PTR, DWORD)
{
HWND hwndFind = GetForegroundWindow();
g_szPath[0] = TEXT('\0');
g_szItem[0] = TEXT('\0');
IShellWindows *psw;
if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
IID_IShellWindows, (void**)&psw))) {
VARIANT v;
V_VT(&v) = VT_I4;
IDispatch *pdisp;
BOOL fFound = FALSE;
for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
V_I4(&v)++) {
IWebBrowserApp *pwba;
if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
HWND hwndWBA;
if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
hwndWBA == hwndFind) {
fFound = TRUE;
IServiceProvider *psp;
if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
IShellBrowser *psb;
if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
IID_IShellBrowser, (void**)&psb))) {
IShellView *psv;
if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
IFolderView *pfv;
if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
(void**)&pfv))) {
IPersistFolder2 *ppf2;
if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
(void**)&ppf2))) {
LPITEMIDLIST pidlFolder;
if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
lstrcpyn(g_szPath, TEXT("<not a directory>"), MAX_PATH);
}
int iFocus;
if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
LPITEMIDLIST pidlItem;
if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
IShellFolder *psf;
if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
(void**)&psf))) {
STRRET str;
if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
SHGDN_INFOLDER,
&str))) {
StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);
}
psf->Release();
}
CoTaskMemFree(pidlItem);
}
}
CoTaskMemFree(pidlFolder);
}
ppf2->Release();
}
pfv->Release();
}
psv->Release();
}
psb->Release();
}
psp->Release();
}
}
pwba->Release();
}
pdisp->Release();
}
psw->Release();
}
InvalidateRect(hwnd, NULL, TRUE);
}
剩下的就是定期呼叫此函式並列印結果。
BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
SetTimer(hwnd, 1, 1000, RecalcText);
return TRUE;
}
void PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
TextOut(pps->hdc, 0, 0, g_szPath, lstrlen(g_szPath));
TextOut(pps->hdc, 0, 20, g_szItem, lstrlen(g_szItem));
}
現在已經準備就緒了。執行程式,把它放到一邊,然後啟動資源管理器並觀察程式,會發現它可以跟蹤你所在的資料夾和焦點所在的項。
我希望我已經證實了我的觀點:通常,你所需的東西的所有片斷已經存在了,你只不過是需要指出怎樣把它們拼在一塊。注意,每一小片都不大。只是你必須能意識到可以用一種有趣的方法把它們放到一起。