在Windows操作系统中,控制面板提供了一种方便的方式来管理和配置系统设置。有时,开发者可能需要为特定的应用程序或硬件设备添加自定义的设置页面。这可以通过编写属性页扩展来实现。本文将介绍如何为控制面板小工具编写自定义属性页扩展,包括接口实现、注册和调试步骤。
属性页扩展是一种特殊的动态链接库(DLL),它允许开发者向控制面板小工具添加或替换页面。这些扩展通过实现特定的COM接口来与控制面板交互。
在开始编写属性页扩展之前,需要熟悉以下概念:
如果需要复习这些概念,可以参考相关的教程。
定制控制面板小工具应该谨慎进行。如果正在编写自定义驱动程序(例如,类似于Microsoft的IntelliPoint或IntelliType),那么进行定制是合理的。但是,如果只是有一个简单的壁纸切换程序,那么最好编写自己的配置应用程序,而不是增加到已经拥挤的显示控制面板中。
与常规属性页扩展一样,控制面板扩展实现两个接口:
方法调用的顺序与常规属性页扩展略有不同。顺序如下:
最大的区别是ReplacePage()方法,在常规属性页扩展中未使用。某些控制面板小工具允许扩展替换属性页中的页面。可以在cplext.h文件中找到确切的页面和小工具,但在这里总结了它们:
如上所述,IShellExtInit::Initialize()被调用,但由于在控制面板小工具中没有选择,参数是无意义的。如果有任何一次性初始化要做,Initialize()是一个很好的地方。
显示小工具允许扩展替换一个页面(背景),所以IShellPropSheetExt::ReplacePage()方法被调用一次。如果扩展不想替换页面,它只需返回S_OK。如果扩展确实想要替换页面,它创建一个新的属性页,小工具会将其显示在内置的背景页面的位置。
替换页面的过程与添加页面几乎相同。扩展填充了一个PROPSHEETPAGE结构,创建了一个页面,并调用lpfnReplaceWith函数来替换背景页面。示例页面上没有控件;目的只是为了展示如何在显示小工具中获取页面。假设能够为页面编码一个对话框过程;如果需要帮助,请查看CodeProject上的其他属性页文章。
可以定制的控制面板小工具在HKEY_LOCAL_MACHINE中有自己的注册表区域。不幸的是,用于扩展主显示属性页的注册表键在9x和2000上是不同的,所以不能使用RGS脚本注册扩展。在Windows 9x上,它是HKLM\Software\Microsoft\Windows\CurrentVersion\Controls Folder\Display\shellex\PropertySheetHandlers,而在Windows 2000上它是HKLM\Software\Microsoft\Windows\CurrentVersion\Controls Folder\Desk\shellex\PropertySheetHandlers。在DllRegisterServer()中为扩展在PropertySheetHandlers下创建一个新的键,并在DllUnregisterServer()中移除该键。
一旦知道了要使用什么调试设置,就可以在Visual C调试器中轻松地调试控制面板扩展。Microsoft知识库文章Q166168和Q135068有一个很好的描述如何做到这一点,但会在这里做一个快速总结。
在项目设置中,转到调试选项卡,将可执行文件设置为"rundll32.exe"。程序参数应该是"shell32.dll,Control_RunDLL",后面跟着包含小工具的CPL文件的完整路径。(注意"Control_RunDLL"部分是区分大小写的。)并不总是很明显要使用哪个CPL文件,所以这里有一些常见的:
"@1"部分指示在main.cpl中运行哪个小工具。(CPL文件可以包含多个小工具;添加@n运行小工具编号n,其中n是一个从0开始的计数。)
以下是ReplacePage()方法的示例代码。首先验证uPageID是期望的值。
#include <cplext.h>
STDMETHODIMP CDisplayCplExt::ReplacePage ( UINT uPageID,
LPFNADDPROPSHEETPAGE lpfnReplaceWith,
LPARAM lParam )
{
if (CPLPAGE_DISPLAY_BACKGROUND != uPageID)
return S_OK;
PROPSHEETPAGE psp;
HPROPSHEETPAGE hPage;
ZeroMemory ( &psp, sizeof(PROPSHEETPAGE) );
psp.dwSize = sizeof(PROPSHEETPAGE);
psp.dwFlags = PSP_USEREFPARENT | PSP_DEFAULT | PSP_USECALLBACK;
psp.hInstance = _Module.GetResourceInstance();
psp.pszTemplate = MAKEINTRESOURCE(IDD_REPLACEPAGE);
psp.pfnDlgProc = ReplacementPageDlgProc;
psp.pfnCallback = ReplacementPageCallbackProc;
psp.pcRefParent = (UINT*) &_Module.m_nLockCnt;
hPage = CreatePropertySheetPage ( &psp );
if (NULL != hPage)
{
if (!lpfnReplaceWith ( hPage, lParam ))
{
DestroyPropertySheetPage ( hPage );
}
}
return S_OK;
}