65.9K
CodeProject 正在变化。 阅读更多。
Home

向控制面板小程序添加自定义页面

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2000年11月2日

viewsIcon

164433

downloadIcon

1394

本文介绍了如何通过编写属性表处理程序来向控制面板小程序添加自己的页面。

引言

在写完我的属性表外壳扩展教程后,有些人问我如何自定义控制面板小程序中的页面。这个过程与编写“普通”属性表扩展非常相似,因此我写了这篇文章来总结如何做到这一点。

我将这篇文章归类为中级水平,因为您应该已经熟悉属性表扩展、涉及的COM接口及其方法,以及如何使用纯SDK调用编写属性页。如果您需要复习,请参阅我的教程

另外请记住,自定义控制面板小程序不应轻易进行。如果您正在编写自定义驱动程序软件(例如Microsoft的IntelliPoint或IntelliType之类的东西),那么这是完全合理的。但是,如果您只是有一个切换壁纸的小程序,那么最好编写自己的配置应用程序,而不是添加到已经拥挤的“显示”控制面板中。

本文的示例项目是一个简单的扩展,它向“显示”小程序添加了两个自定义页面。

扩展接口

与普通属性表扩展一样,控制面板扩展实现了两个接口:IShellExtInitIShellPropSheetExt。方法调用的顺序与普通属性表扩展略有不同。顺序如下:

  • 首先调用IShellExtInit::Initialize(),但由于没有要选择的文件,所以pidlFolderlpdobj参数都为NULL。您可以在Initialize()中执行任何一次性初始化。
  • 如果小程序允许替换页面,则IShellPropSheetExt::ReplacePage()会为每个可替换的页面调用一次。您可以在每次ReplacePage()调用中替换一个页面;我将在下面解释。
  • IShellPropSheetExt::AddPages()会被调用一次。您可以在AddPages()中添加任意数量的页面。

最大的区别在于ReplacePage()方法,它在普通属性表扩展中未使用。某些控制面板小程序允许扩展替换属性表中的页面。您可以在cplext.h文件中找到确切的页面和小程序,但我已在此处进行了总结:

  • 显示小程序:您可以替换“背景”页面。
  • 键盘小程序:您可以替换“速度”页面。
  • 鼠标小程序:您可以替换“按钮”和“移动”页面。

初始化接口

如前所述,会调用IShellExtInit::Initialize(),但由于控制面板小程序中没有选择,因此参数无意义。如果您有任何一次性初始化要做,Initialize()是一个不错的地方。

属性表接口

“显示”小程序允许扩展替换一个页面(“背景”),因此会调用一次IShellPropSheetExt::ReplacePage()方法。如果扩展程序不想替换页面,则只需返回S_OK。如果扩展程序确实想替换页面,它将创建一个新的属性页,小程序将在其中替换内置的“背景”页面。

ReplacePage()的原型是:

HRESULT IShellPropSheetExt::ReplacePage (
    UINT uPageID,
    LPFNADDPROPSHEETPAGE lpfnReplaceWith,
    LPARAM lParam );

参数如下:

uPageID
一个来自cplext.h的常量,指示小程序正在查询哪个页面。例如,“显示”小程序调用ReplacePage()时,uPageID设置为CPLPAGE_DISPLAY_BACKGROUND,表示扩展程序可以替换“背景”页面。在允许替换多个页面的小程序中(例如鼠标小程序),会为每个页面调用一次ReplacePage()
lpfnReplaceWith, lParam
lpfnReplaceWith是一个函数指针,扩展程序会调用它来实际替换页面。lParam是一个对shell有意义的值,它会被传递给lpfnReplaceWith函数。

替换页面的过程与添加页面的过程几乎相同。我们的扩展程序填充一个PROPSHEETPAGE结构,创建一个页面,然后调用lpfnReplaceWith函数来替换“背景”页面。示例页面上没有任何控件;目的是仅用于演示如何获得“显示”小程序中的页面。我假设您能够为页面编写对话框过程;如果您对此需要帮助,请查看CodeProject上的其他属性表文章。

以下是我们ReplacePage()方法的代码。我们首先验证uPageID是我们期望的值。

#include <cplext.h>

STDMETHODIMP CDisplayCplExt::ReplacePage ( UINT uPageID, 
                                           LPFNADDPROPSHEETPAGE lpfnReplaceWith,
                                           LPARAM lParam )
{
    // Check that we're being called with the page ID we expect.
    if ( CPLPAGE_DISPLAY_BACKGROUND != uPageID )
        return S_OK;

然后,我们填充一个PROPSHEETPAGE结构。这里的代码引用了对话框过程和回调函数,稍后会讲到。

PROPSHEETPAGE  psp;
HPROPSHEETPAGE hPage;

    // Set up the PROPSHEETPAGE struct.
    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;

然后,我们创建一个页面并将其传递给lpfnReplaceWith函数。

    // Create the page & get a handle.
    hPage = CreatePropertySheetPage ( &psp );

    if ( NULL != hPage )
        {
        // Call the shell's callback function, so it adds the page to
        // the property sheet.
        if ( !lpfnReplaceWith ( hPage, lParam ))
            {
            DestroyPropertySheetPage ( hPage );
            }
        }

    return S_OK;
}

有两个附加函数:ReplacementPageDlgProcReplacementPageCallbackProc。您可以查看示例项目中的代码;它们只是骨架,因为页面上没有控件。

请注意,shell版本4.71及更高版本包含一个具有“背景”页面替换功能的显示扩展。因此,如果您使用上述代码而不禁用shell自己的扩展,您仍然会看到一个“背景”选项卡,如下图所示:

 [Our page with the shell's page - 13K]

扩展程序还可以从其AddPages()方法向属性表中添加任意数量的页面。这与普通属性表扩展中的操作相同,代码几乎与上面的ReplacePage()代码相同。如果您迫切想查看代码,请查看示例项目。

示例扩展程序在AddPages()中向“显示”小程序添加了一个页面,以下是最终结果:

 [Our replacement page - 8K]

 [Our additional display page - 7K]

注册扩展

可以自定义的控制面板小程序在注册表中有自己的区域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()中删除该键。

我手头没有NT 4系统来检查它使用的注册表项,因此目前注册代码将NT 4视为与Win 2000相同。如果某个好心的读者能帮我在NT 4上检查一下,并告知我代码是否使用了错误的注册表项,我将不胜感激。

调试控制面板扩展

一旦知道要使用哪些调试设置,就可以轻松地在Visual C调试器中调试控制面板扩展。Microsoft知识库文章Q166168Q135068对如何做到这一点有很好的描述,但我在此简要总结一下。

在项目设置中,转到“调试”选项卡,并将可执行文件设置为“rundll32.exe”。程序参数应为“shell32.dll,Control_RunDLL”,后跟包含小程序(CPL文件)的完整路径。(请注意,“Control_RunDLL”部分区分大小写。)要使用哪个CPL文件并不总是显而易见的,以下是一些常见的CPL文件:

小程序

参数(如果您的Windows安装在其他目录中,请更改“C:\windows\system”)

显示

shell32.dll,Control_RunDLL C:\windows\system\desk.cpl

鼠标

shell32.dll,Control_RunDLL C:\windows\system\main.cpl

键盘

shell32.dll,Control_RunDLL C:\windows\system\main.cpl,@1

“@1”部分指示main.cpl中要运行哪个小程序。(CPL文件可以包含多个小程序;添加@n将运行第n个小程序,其中n是一个从0开始的计数。)

如果情况仍然有些不清楚,这里有一张截图演示了设置:

 [Debug settings - 7K]

当您开始调试时,rundll32将调用shell中的Control_RunDLL函数,该函数又会启动控制面板小程序。要结束调试,只需关闭小程序窗口即可。

© . All rights reserved.