問題:
在應用程序中如何激活活動桌面(Active Desktop)?一般情況下用戶可以在桌面單擊右鍵,選擇“活動桌面”=〉“按Web頁查看”來打開/關閉活動桌面特性。有沒有什麼函數可以程序中調用來實現對活動桌面的操作?另外,如何斷定用戶激活或取消活動桌面?
解答:
在回答這個問題之前,讓我給你一個重要警告。那就是如果你打算開關活動桌面特性,請保證經過了用戶的許可!最好使用大字體清晰地顯示:“你真的想要激活活動桌面嗎?”要是沒有這樣的提示,對用戶不免有些粗魯。有些用戶並不想要什麼程序來決定是否啟動活動桌面。如果用戶真要是喜歡Web特性而不想失去活動桌面。那他們也會容忍由此而帶來的性能下降。
好吧,這麼多嚴厲的警告。現在假設你有充足的理由打開或關閉活動桌面。也許你在寫一個新的外殼。為了激活或取消活動桌面,你需要使用IActiveDesktop,這是個活動桌面的COM接口。下面列出的是這個接口的方法列表:
//IActiveDesktop 接口方法表
方法
功能和用途
AddDesktopItem 添加一個桌面項。 AddDesktopItemWithUI 使用某個用戶界面添加一個桌面項到活動桌面。 AddUrl 添加與指定的URL關聯的桌面項。 ApplyChange 執行對活動桌面的修改。要使修改生效必須調用這個函數。用於激活或取消活動桌面。 GenerateDesktopItemHtml 產生包含給定桌面項的通用HTML頁面。 GetDesktopItem 獲得指定的桌面項。 GetDesktopItemByID 獲得與給定ID匹配的桌面項。 GetDesktopItemBySource 用源URL獲得某個桌面項。 GetDesktopItemCount 或的桌面項計數。 GetDesktopItemOptions 檢查活動桌面是否打開或關閉。SHGetSettings 性能更佳。用於激活或取消活動桌面。 GetPattern 獲取當前使用的式樣。 GetWallpaper 獲取當前使用的牆紙。僅用於活動桌面。標准模式時(桌面關閉),使用SystemParametersInfo。 GetWallpaperOptions 獲得牆紙選項。僅用於活動桌面。標准模式時(桌面關閉),使用SystemParametersInfo。 ModifyDesktopItem 修改桌面項。 RemoveDesktopItem 從桌面刪除指定的桌面項。 SetDesktopItemOptions 打開或關閉活動桌面。 SetPattern 設置活動桌面式樣。 SetWallpaper 設置活動桌面牆紙。僅用於活動桌面。標准模式時(桌面關閉),使用SystemParametersInfo。 SetWallpaperOptions 設置牆紙選項。僅用於活動桌面。標准模式時(桌面關閉),使用SystemParametersInfo。//用IActiveDesktop可以添加和刪除桌面項(HTML頁面,圖像,URLs或者ActiveX 控件),設置和獲取牆紙(僅用於活動桌面,在標准模式時要用SystemParametersInfo函數)及其它有用功能。你可以用來打開或關閉活動桌面的函數是SetDesktopItemOptions。但首先要考慮——如何獲得IActiveDesktop接口?用通常使用COM的方法創建一個實例:
//IActiveDesktop* pAD;
HRESULT hr = ::CoCreateInstance(
CLSID_ActiveDesktop,
NULL, // 不支持聚合,也就是說沒有外部Unknown
CLSCTX_INPROC_SERVER,
IID_IActiveDesktop,
(void**)&pAD);
//不要忘了在啟動代碼中調用CoInitialize,如MFC應用的InitInstance函數。一旦你有了ActiveDesktop指針,便可以調用它的方法。
// 激活活動桌面
COMPONENTSOPT opt;
opt.dwSize = sizeof(opt);
opt.fActiveDesktop =
opt.fEnableComponents = TRUE;
HRESULT hr = pAD->SetDesktopItemOptions(&opt,0);
//現在活動桌面應該被激活,但真是這樣嗎?當你第一次運行時,什麼事情也沒發生。怎麼回事呢?經過檢查,我發現之所以設置沒有起作用是因為有個小細節在文檔中沒有說明——將設置應用到活動桌面:
//pAD->ApplyChanges(AD_APPLY_REFRESH);
//用完接口之後不要忘了釋放(Release)它!(當然,你不應該使用原始的接口指針,應該用ATL智能指針——希望你正在使用它們)為了檢查活動桌面是否打開或關閉,有一個對應的Get函數——GetDesktopItemOptions,它使用相同的COMPONENTSOPT結構。還有一個外殼函數做同樣的事情:
// 活動桌面打開或關閉了嗎?
SHELLFLAGSTATE shfs;
SHGetSettings(&shfs,SSF_DESKTOPHTML);
BOOL bADEnabled = shfs.fDesktopHTML;
//不需要COM,CoCreateInstance,IActiveDesktop,或任何有關COM接口的東西。只要調用這個函數。你可以用SHGetSettings來檢查一系列的外殼設置,下面列出了有關SHGetSettings使用的詳細信息。這些設置或多或少與Windows 9x的資源管理器(參見圖四)中“查看”=〉“文件夾選項”=〉“查看”標簽中的選項對應。(Windows 2000有所不同,它是在“工具”=〉“文件夾選項”=〉“查看”標簽中)可惜沒用對應的SHSetSettings函數。
//// 用SHGetSettings獲得信息
// SHGetSettings 獲得當前外殼設置
VOID SHGetSettings(
LPSHELLFLAGSTATE lpsfs, // 下列結構的地址
DWORD dwMask // 獲取哪個信息(參見下面內容)
);
// SHGetSettings 填充下面的位域結構. 這些標志與Explorer的“查看”=〉“文件夾選項”=〉“查看”標簽中的選項對應
typedef struct {
BOOL fShowAllObjects : 1; // 顯示所有文件 (隱藏的或系統的)
BOOL fShowExtensions : 1; // 顯示文件擴展名 (如 .txt)
BOOL fNoConfirmRecycle : 1; // 刪除時不確認
BOOL fShowSysFiles : 1; // 顯示文件的系統屬性
BOOL fShowCompColor : 1;
BOOL fDoubleClickInWebView : 1; // 顧名思義
BOOL fDesktopHTML : 1; // 已打開活動桌面
BOOL fWin95Classic : 1; // 已打開Windows 95 "傳統"視圖
BOOL fDontPrettyPath : 1;
BOOL fShowAttribCol : 1;
BOOL fMapNetDrvBtn : 1; // 顯示網絡驅動器按鈕
BOOL fShowInfoTip : 1; // 顯示彈出式描述
BOOL fHideIcons : 1; //在活動桌面模式中隱藏圖標
UINT fRestFlags : 3;
} SHELLFLAGSTATE;
// 這些標志被用於獲取上面的這些域;如調用時使用:
dwMask =
// (SSF_DESKTOPHTML | SSF_WIN95CLASSIC) 來獲取 fDesktopHTML 和
// fWin95Classic。
#define SSF_SHOWALLOBJECTS 0x00000001
#define SSF_SHOWEXTENSIONS 0x00000002
#define SSF_SHOWCOMPCOLOR 0x00000008
#define SSF_SHOWSYSFILES 0x00000020
#define SSF_DOUBLECLICKINWEBVIEW 0x00000080
#define SSF_SHOWATTRIBCOL 0x00000100
#define SSF_DESKTOPHTML 0x00000200
#define SSF_WIN95CLASSIC 0x00000400
#define SSF_DONTPRETTYPATH 0x00000800
#define SSF_SHOWINFOTIP 0x00002000
#define SSF_MAPNETDRVBUTTON 0x00001000
#define SSF_NOCONFIRMRECYCLE 0x00008000
#define SSF_HIDEICONS 0x00004000
圖四 “查看”/“工具” 菜單 =〉“文件夾選項”=〉“查看”
現在我們知道由兩種方法來檢查是否活動桌面是否激活——SHGetSettings 和 IActiveDesktop::GetDesktopItemOptions,哪個方法好呢?這很重要嗎?為了回答這個問題,讓我們來探討問題中的第二部分:如何斷定用戶激活或取消活動桌面,不論是從桌面菜單或者是從屬性對話框(如圖五)?
圖五 選擇活動桌面
當用戶打開或關閉活動桌面時,Windows廣播WM_SETTINGCHANGE消息給所有最上層窗口,消息值分別為:wParam = 0 和 lParam = "ShellState"。所以為了捕獲這個事件,必須處理WM_SETTINGCHANGE消息。
// 最上層框架窗口!
void CMainFrame::OnSettingChange(UINT
uFlags, LPCTSTR pszSection)
{
if (lpszSection &&
_tcscmp(pszSection,_T("ShellState"))==0) {
// do what you want
}
CFrameWnd::OnSettingChange(uFlags,
pszSection);
}
//WM_SETTINGCHANGE是個Windows的常用消息,當程序修改了SystemParametersInfo設置,則Windows就會廣播此消息。但WM_SETTINGCHANGE也比較多地用在其它情形。
一般情況下,wParam/uFlags時0,lParam/pszSection是WIN.INI段名或被修改部分的注冊表鍵(只是最終的鍵,而不是整個串)。事實上,WM_SETTINGCHANGE常被叫做WM_WININICHANGE,這兩個符號在#define中的值也一樣!當IActiveDesktop廣播設置修改時,它將“ShellState”作為段名來傳遞,因為活動桌面設置被存儲在一個注冊表鍵中:
\HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ShellState
另外,如果你要廣播自己修改的全程設置,也可以使用WM_SETTINGCHANGE。廣播是應該使用SendMessageTimeout(HWND_BROADCAST, ...)函數。
圖六 TestAD
為了整合所講的內容,我編寫了一個小程序:TestAD(如圖六)。當TestAD獲得 WM_SETTINGCHANGE時,便顯示一條消息。利用我創建的一個類(CActiveDesktop)來獲得並設置活動桌面的狀態。為了使用這個類,你只要編寫如下代碼:
//CActiveDesktop ad;
if (!ad.IsEnabled())
ad.Enable(TRUE);
//CActiveDesktop隱藏了所有與COM有關的瑣事。它使用ATL智能指針來保證接口處理的正確性和整體處理的自動化。如果你現在不使用CComQIPtr,那麼趕快學會使用它,對於它的正確使用能使你獲得健壯的,無錯的COM代碼,它非常有用。CActiveDesktop並沒用封裝所有的IActiveDesktop特性,只是封裝了我編寫TestAD所需要的功能。如果我什麼時候想要編寫一個Windows外殼時(我當然不會),再添加缺少的方法。但決定權在於你自己。CActiveDesktop非常簡單,所以有關細節就請你參考源代碼吧。
在實現CActiveDesktop和TestAD時,我遇到了一些意想不到的事情。首先是我在前面已經提到的在修改設置後要將它“應用”到(ApplyChanges)活動桌面的問題。其次是我發現了IActiveDesktop的同步bug問題。當我開始實現TestAD時,IActiveDesktop好像老是報告的錯誤狀態。也就是說活動桌面真正打開的時候,它報告的是關閉,反之亦然。我以為是我的代碼有問題,但當我細究後發現IActiveDesktop::GetDesktopItemOptions事實上在報告錯誤的狀態信息!請看下面的分析:
//TestAD 調用 CActiveDesktop::Enable(TRUE).
//CActiveDesktop 調用 IActiveDesktop::SetDesktopItemOptions, 然後將修改應用到活動桌面(ApplyChanges)。
//ApplyChanges 向最上層窗口廣播WM_SETTINGCHANGE 消息。
//CMainFrame獲得WM_SETTINGCHANGE,並調用IActiveDesktop::GetDesktopItemOptions來獲得開/關狀態——但IActiveDesktop報告的狀態仍然是關閉!
//顯然IActiveDesktop在廣播完成之前沒有更新其內部的狀態。即GetDesktopItemOptions報告的是舊的開/關狀態。碰到這種情況怎麼辦呢?我試圖自己通過消息處理來修正這個問題,也就是在主窗口處理完WM_SETTINGCHANGE消息後添加“活動桌面開/關消息”。結果在TestAD程序中開/關活動桌面倒是沒什麼問題了,但當我用桌面上下文菜單的時候,又發生同樣的問題。不用懷疑,肯定是當TestAD處理添加的消息時,Windows仍然在向下一個最上層窗口廣播WM_SETTINGCHANGE消息。
怎麼辦?難道在顯示狀態信息前等待半秒鐘?真臭。這時如果用SHGetSettings就沒問題啦。實踐證明,SHGetSettings報告的是正確的活動桌面開/關狀態,即便GetDesktopItemOptions報告的是相反的狀態——真讓人高興!很顯然,ApplyChanges更新注冊表是在廣播WM_SETTINGCHANGE消息之前及在更新其內部狀態之前——這是一件讓人哭笑不得的事情。
現在我們應該可以明確回答前面提出的問題了:用哪個方法來獲得活動桌面得開/關狀態
好呢?好像SHGetSettings最接近正確答案。
本文配套源碼