问题: 在应用程序中如何激活活动桌面(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 设置活动桌面墙纸。仅用于活动桌面。标准模式时(桌面关闭),使用S ystemParametersInfo。 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来检查一系列的外壳设置,下面列出了有关SHG etSettings使用的详细信息。这些设置或多或少与Windows 9x的资源管理器(参见图四 )中“查看”=〉“文件夹选项”=〉“查看”标签中的选项对应。(Windows 2000有所 不同,它是在“工具”=〉“文件夹选项”=〉“查看”标签中)可惜没用对应的SHSetS ettings函数。 // // 用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 和 IAc tiveDesktop::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_WIN INICHANGE,这两个符号在#define中的值也一样!当IActiveDesktop广播设置修改时, 它将“ShellState”作为段名来传递,因为活动桌面设置被存储在一个注册表键中: /HKCU/Software/Microsoft/Windows/CurrentVersion/Explorer/ShellState 另外,如果你要广播自己修改的全程设置,也可以使用WM_SETTINGCHANGE。广播是应该 使用SendMessageTimeout(HWND_BROADCAST, ...)函数。 图六 TestAD 为了整合所讲的内容,我编写了一个小程序:TestAD(如图六)。当TestAD获得 W M_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::GetDesktopItemOp tions事实上在报告错误的状态信息!请看下面的分析: // TestAD 调用 CActiveDesktop::Enable(TRUE). // CActiveDesktop 调用 IActiveDesktop::SetDesktopItemOptions, 然后将修改应 用到活动桌面(ApplyChanges)。 // ApplyChanges 向最上层窗口广播WM_SETTINGCHANGE 消息。 // CMainFrame获得WM_SETTINGCHANGE,并调用IActiveDesktop::GetDesktopItemOptions 来获得开/关状态——但IActiveDesktop报告的状态仍然是关闭! // 显然IActiveDesktop在广播完成之前没有更新其内部的状态。即GetDesktopItemOp tions报告的是旧的开/关状态。碰到这种情况怎么办呢?我试图自己通过消息处理来修 正这个问题,也就是在主窗口处理完WM_SETTINGCHANGE消息后添加“活动桌面开/关消息 ”。结果在TestAD程序中开/关活动桌面倒是没什么问题了,但当我用桌面上下文菜单的 时候,又发生同样的问题。不用怀疑,肯定是当TestAD处理添加的消息时,Windows仍然 在向下一个最上层窗口广播WM_SETTINGCHANGE消息。 怎么办?难道在显示状态信息前等待半秒钟?真臭。这时如果用SHGetSettings就没 问题啦。实践证明,SHGetSettings报告的是正确的活动桌面开/关状态,即便GetDeskt opItemOptions报告的是相反的状态——真让人高兴!很显然,ApplyChanges更新注册表 是在广播WM_SETTINGCHANGE消息之前及在更新其内部状态之前——这是一件让人哭笑不 得的事情。 现在我们应该可以明确回答前面提出的问题了:用哪个方法来获得活动桌面得开/ 关状态 好呢?好像SHGetSettings最接近正确答案。 最后祝弟兄们编程愉快! 论坛上经常有网友发贴问怎么实现设置桌面墙纸,并且使用的是jpg文件。虽然在下知道可以用API函数SystemParametersInfo可以设置bmp文件为墙纸,也知道可以用IActiveDesktop来设置其他格式的图片文件作为墙纸,但是工作中一直没有使用到该功能,所以也就一直没实现。 1.用SystemParametersInfo设置墙纸 很简单,一句话搞定: SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, "C://xxx.bmp", SPIF_SENDWININICHANGE| SPIF_UPDATEINIFILE); but, 这玩意儿只能用bmp文件,不能用其他文件,MS也不把函数功能扩展一下,BS一下MS。 再but,这玩意儿也不能设置墙纸的样式,比如平铺,拉伸什么的,虽然可通过修改注册表修改,但是据那位网友说,需要重启才能生效什么的,我没有试,因为这篇文章的主要目的是讨论用IActiveDesktop设置墙纸。 2.用IActiveDesktop设置墙纸 习惯上用任何不太熟悉的函数或接口前,查msdn。 通过msdn,了解到: IActiveDesktop是个windows shell interface Header shlobj.h Minimum operating systems Windows 2000, Windows NT 4.0 with Internet Explorer 4.0, Windows 98, Windows 95 with Internet Explorer 4.0 同时,在msdn还有一篇文章 Using the Active Desktop Object 其实,看了这篇文章你就应该学会如何使用Active Desktop Object了。 (1)使用IActiveDesktop需要包含的一些头文件,以及进行初始化 在stdafx.h中 #include <afxwin.h>#include <wininet.h> //添加这个。注意顺序,顺序乱了也编译不过#include <afxext.h> ... 用了com的东西,要进行初始化,不用说了吧。 在CXXXApp::Initinstance中添加AfxOleInit(); 使用IActiveDesktop的cpp中 #include <shlobj.h> (2) 现在开始写实现函数了 还是我的老的测试工程CTest6Dlg..呵呵 BOOL CTest6Dlg::SetWallPaper(CString strPath){ HRESULT hr; IActiveDesktop* pIAD; hr = CoCreateInstance( CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER,IID_IActiveDesktop, (void**)&pIAD); if ( !SUCCEEDED(hr) ) return FALSE; // 设置墙纸前先打开活动桌面开关 COMPONENTSOPT co = {0}; co.dwSize = sizeof(COMPONENTSOPT); co.fEnableComponents = TRUE; co.fActiveDesktop = TRUE; hr = pIAD->SetDesktopItemOptions(&co, 0); if ( !SUCCEEDED(hr) ) return FALSE; //做以下宽字符的转换是因为SetWallpaper的参数是用的LPCWSTR WCHAR wszPath[MAX_PATH]; LPTSTR lpStr = strPath.GetBuffer(strPath.GetLength()); MultiByteToWideChar(CP_ACP, 0, lpStr, -1, wszPath, MAX_PATH); strPath.ReleaseBuffer(); // 设置墙纸 hr = pIAD->SetWallpaper(wszPath, 0); if ( !SUCCEEDED(hr) ) return FALSE; // 设置墙纸风格 WALLPAPEROPT wp = {0}; wp.dwSize = sizeof(WALLPAPEROPT); wp.dwStyle |= WPSTYLE_CENTER; hr = pIAD->SetWallpaperOptions(&wp, 0); if ( !SUCCEEDED(hr) ) return FALSE; // 应用改变 hr = pIAD->ApplyChanges(AD_APPLY_ALL); if ( !SUCCEEDED(hr) ) return FALSE; // 最后释放接口,不要忘了 pIAD->Release(); } 很简单吧。我的测试只做了一个jpg,其他的图片格式没有试,有兴趣的朋友可以帮试试。。。 如何在程序中动态设置墙纸关键字 Wallpaper IActiveDesktop 大家都知道设置WINDOWS桌面墙纸的WIN32 API是SystemParametersInfo, 使用SPI_SETDESKWALLPAPER参数便能设置墙纸: ::SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, "C://ABitmap.bmp", SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE); 但问题并没有解决,因为这个WIN32 API有很大的局限性 1. 无法把jpeg, gif等其他非位图格式的图片设为墙纸。 2. 无法调整墙纸的式样,即无法设置墙纸为居中,平铺或拉伸。(当然我们可以通过编程改变注册表HKEY_CURRENT_USER/Control Panel/Desktop的值来做到这一点)由于在显示属性系统对话框内很容易手动设置这些选项,我们有理由相信,Windows Shell API一定提供了相应的比较高层的函数,接口或类来完成墙纸的设置。事实上,Windows Shell API中确实存在完成这个任务的接口:IActiveDesktop。以下是我所完成的设置墙纸的函数 //strPicFile是图像文件名,支持BMP JPEG GIF等格式 //dwStyle是墙纸的样式 //WPSTYLE_CENTER 居中 0 //WPSTYLE_TILE 平铺 1 //WPSTYLE_STRETCH 拉伸 2 //WPSTYLE_MAX 3 //返回值是TRUE时墙纸设置成功,返回FALSE时失败 BOOL SetWallpaper(CString &strPicFile, DWORD dwStyle) { HRESULT hr; IActiveDesktop* pIAD; //创建接口的实例 hr = CoCreateInstance ( CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER, IID_IActiveDesktop, (void**) &pIAD ); if(!SUCCEEDED(hr)) return FALSE; //将文件名改为宽字符串,这是IActiveDesktop::SetWallpaper的要求 WCHAR wszWallpaper [MAX_PATH]; LPTSTR lpStr = strPicFile.GetBuffer(strPicFile.GetLength() ); MultiByteToWideChar(CP_ACP, 0, lpStr, -1, wszWallpaper, MAX_PATH); strPicFile.ReleaseBuffer(); //设置墙纸 hr = pIAD->SetWallpaper(wszWallpaper, 0); if(!SUCCEEDED(hr)) return FALSE; //设置墙纸的样式 WALLPAPEROPT wpo; wpo.dwSize = sizeof(wpo); wpo.dwStyle = dwStyle; hr = pIAD->SetWallpaperOptions(&wpo, 0); if(!SUCCEEDED(hr)) return FALSE; //应用墙纸的设置 hr = pIAD->ApplyChanges(AD_APPLY_ALL); if(!SUCCEEDED(hr)) return FALSE; //读取墙纸的文件名并打印在debug窗口内 hr = pIAD->GetWallpaper(wszWallpaper, MAX_PATH, 0); CString strFile = wszWallpaper; TRACE(strFile); //如果不用位图的话,这里有你意想不到的发现 //释放接口的实例 pIAD->Release(); return TRUE; } 在MFC程序中应用此函数时,须注意以下三点。 1.在函数所在文件中加上声明IActiveDesktop的头文件 #include 2.在StdAfx.h中插入#include 位置不能搞错,否则IActiveDesktop将找不到定义 #include // MFC core and standard components #include // NOTE: corrects compilation errors w/IActiveDesktop!! #include // MFC extensions 3.由于此函数引用了COM组件,所以必须在C**App::OnInitInstance()中插入以下语句,初始化COM组件。 AfxOleInit(); 补充: 只有在安装了IE4.0及以上版本时才能应用IActiveDesktop接口,只有在打开active desktop(活动桌面)的情况下才能应用SetWallpaper函数,下面是启用或关闭active desktop的函数: //bEnable是TRUE时启用active desktop, 是FALSE时关闭 //返回值是TRUE时启用或关闭active desktop成功,返回FALSE时失败 BOOL EnableActiveDesktop(BOOL bEnable) { HRESULT hr; IActiveDesktop* pIAD; //创建接口的实例 hr = CoCreateInstance ( CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER, IID_IActiveDesktop, (void**) &pIAD ); if(!SUCCEEDED(hr)) return FALSE; COMPONENTSOPT comp; comp.dwSize = sizeof(comp); comp.fEnableComponents = bEnable; //启用或关闭active desktop comp.fActiveDesktop = bEnable; hr = pIAD->SetDesktopItemOptions(&comp, 0); if(!SUCCEEDED(hr)) return FALSE; //释放接口的实例 pIAD->Release; return TRUE; }