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

高级 UxTheme 包装器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (39投票s)

2007年4月30日

CPOL

14分钟阅读

viewsIcon

202460

downloadIcon

17665

如何使用和理解用于通过 C# 包装器 (uxtheme.dll) 绘制的自定义控件的视觉样式

下载次数

使用自定义控件(按钮、复选框、单选按钮、进度条等)绘制的示例应用程序,这些控件是通过包装器绘制的。

Screenshot - uxthemeSample.jpg

引言

我加入 CodeProject 已经很久了。我学到了很多,是时候分享我的知识了。

我想这也不是你第一次阅读关于 uxtheme 包装器的文章了。和我一样,你可能已经在 Google 上搜索了大量关于 uxtheme(也称为 Visual Styles 或 Windows themes)的信息和 C# 示例,但找不到符合你需求的内容。

然而,CodeProject 上有很多优秀的相關文章,例如 Don Kackman 的 "A Managed C++ Wrapper Around the Windows XP Theme API",或者 David Zhao 的 "Add XP Visual Style Support to OWNERDRAW Controls" 和 Mathew Hall 的 "Themed Windows XP style Explorer Bar",它们描述了视觉样式并提供了有用的技巧。但是,在我看来,它们并非为通用的 C# 使用而设计,并且没有提供一种简单的方法在我们的应用程序中使用视觉样式。

我想有一种方法来枚举和切换计算机上可用的主题,所以我决定自己做一个 C# 包装器。

本文

那么,它是如何工作的呢?

我将本文分为三个部分:

  • 简要介绍 uxtheme,
  • 视觉样式格式,你将从中了解 .theme 和 .msstyles 文件隐藏的内容,
  • 以及包装器设计,我将在此描述如何使用它。

背景需求

以下是我希望在包装器中找到的功能:

  • Windows XP 主题支持 - 即使在 XP 之前的版本上也能支持,
  • 一个简单通用的 C# 包装器,
  • 枚举计算机上可用的/已安装的主题,
  • 分析和解密 .msstyles 文件,以便按我想要的方式使用它们,
  • 使用主题数据切换自定义组件的外观和感觉,
  • 提供一种在自定义控件之间共享主题数据的方法。

特点

您将在本示例中找到什么

  • 主题支持,即使对于 XP 之前的版本(正常),
  • 一个“即用型”C# uxtheme 包装器(包含大量注释),
  • 一种获取当前使用的主题信息的方法,
  • 一种枚举你计算机上视觉样式的方法,
  • 一种获取视觉样式所有信息的方法,
  • 一种保存 .msstyles 文件中嵌入的 .ini 文件的方法,
  • 一种保存 .msstyles 文件中嵌入的位图的方法,
  • 一个特定的 PE 文件读取器,将帮助你提取主题数据,
  • 自定义控件的示例,以及它们各自的渲染器实现。

你还将找到自定义设计器/编辑器,如果你想学习如何创建自己的设计时支持。

一、视觉样式和 UxTheme.dll

我不会解释 uxtheme.dll 是如何工作的,因为本文篇幅有限(而且我也不想重写 msdn)。

有趣的是如何编写 C# 包装器。原理很简单:找到你想要处理的 dll(或模块),查找其函数的签名,然后以 C# 的等价形式编写它们。

uxtheme 函数的签名定义在 uxtheme.h 文件中,枚举/常量定义在 tmschema.h 文件中。如果你使用 VS 2003,这些文件位于 "C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Include" 目录中。

uxtheme.h 中,你会找到类似这样的内容:

    //-----------------------------------------------------------------------
    //  DrawThemeText()     - draws the text using the theme-specified
    //                        color and font for the "iPartId" and
    //                        "iStateId".
    //
    //  hTheme              - theme data handle
    //  hdc                 - HDC to draw into
    //  iPartId             - part number to draw
    //  iStateId            - state number (of the part) to draw
    //  pszText             - actual text to draw
    //  dwCharCount         - number of chars to draw (-1 for all)
    //  dwTextFlags         - same as DrawText() "uFormat" param
    //  dwTextFlags2        - additional drawing options
    //  pRect               - defines the size/location of the part
    //-----------------------------------------------------------------------
    THEMEAPI DrawThemeText(HTHEME hTheme, HDC hdc, int iPartId,
        int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags,
        DWORD dwTextFlags2, const RECT *pRect);

下一步是编写 C# 等价方法。例如,DrawThemeText() 函数可以翻译为:

    
/// <summary>
    /// Draws text using the color and font defined by the visual style.
    /// </summary>
    /// <param name="hTheme">Handle to a window's specified 
    ///     theme data.</param>
    /// <param name="hdc">Handle to a device context (HDC) used for 
    ///     drawing the theme-defined background image.</param>
    /// <param name="iPartId">Value that specifies the part to draw.</param>
    /// <param name="iStateId">Value that specifies the state of the 
    ///     part to draw.</param>
    /// <param name="pszText">Pointer to a string that contains the text 
    ///     to draw.</param>
    /// <param name="iCharCount">Value that contains the number of 
    ///     characters to draw. If the parameter is set to -1, all 
    ///     the characters in the string are drawn.</param>
    /// <param name="dwTextFlags">A bitwise combination 
    ///     of <see cref="TextFormatFlags"/> values to specify the text 
    ///     formatting.</param>
    /// <param name="dwTextFlags2">Not used. Set to 0.</param>
    /// <param name="pRect">Pointer to a RECT structure that contains 
    ///     the rectangle, in logical coordinates, in which the text 
    ///     is to be drawn.</param>
    [DllImport("UxTheme.dll", CharSet=CharSet.Unicode, SetLastError=true)]
    public static extern System.Int32 DrawThemeText(
        IntPtr hTheme,
        IntPtr hdc,
        UInt32 iPartId,
        UInt32 iStateId,
        String pszText,
        Int32 iCharCount,
        UInt32 dwTextFlags,
        UInt32 dwTextFlags2,
        ref RECT pRect
        );

翻译函数时,你需要使用 DllImport 属性,并将要导入的模块的名称(或绝对路径)作为第一个参数。

DllImport 属性有许多其他可选参数(请参阅 msdn 上的描述),但你应该始终将 SetLastError 设置为 true。此参数指示被调用方调用 SetLastError 函数,并告知运行时封送拆收器保留最后一条 win32 错误信息的副本(当你不知道函数调用是否成功时,这非常有用)。

要获取最后一条 win32 错误,你可以使用以下代码示例:

    //using System.Runtime.InteropServices;
    //using System.ComponentModel;

    //...

    // call an imported function using SetLastError=true before 
    // using this piece of code
    int errorCode = Marshal.GetLastWin32Error();
    Console.WriteLine("The last win32 error code was: "+errorCode);

    // depends on the called function's error codes
    if(errorCode < 0) throw new Win32Exception(errorCode);

下表提供了 c++ 类型与 C# 之间的快速等价对照:

C++ C#
BOOL System.Booleanbool
BYTE byte
CSize System.Drawing.SizeSystem.Drawing.SizeF
CString, LPCWSTR stringSystem.String
DWORD System.UInt32uint
HBITMAP System.IntPtrSystem.Drawing.Bitmap
HBRUSH System.IntPtr
HDC System.IntPtrSystem.Drawing.Graphics
HPEN System.IntPtr
LONG System.Int32int
WORD System.UInt16ushort

大多数其他 c++ 类型可以转换为 System.IntPtr,但结构体需要用 C# 定义。

二、视觉样式理念

主题可以定义为按“颜色方案”和“大小方案”分组的用户界面属性。然后,通过其颜色和大小方案来检索主题。

这些属性分布在三个文件中:

  • `.theme` 文件:你将在其中找到对 `.msstyles` 文件的引用。
  • `.msstyles` 文件:这是一个老式的 PE 文件(例如,像 DLL 文件一样),嵌入了 .ini 文件和位图。
  • `shellstyle.dll` 文件:它扩展了主题的属性以用于 Explorer(例如,explorer 条位图等)。

主题文件夹树

基本上,可用的主题位于 "%windir%\Resources\Themes" 目录中,如下所示:

Screenshot - themeTree.jpg

每个 .theme 文件都有自己的目录,以及一个同名的 .msstyles 文件(例如,**panther.theme** 在名为 **panther** 的目录中包含其 .msstyles 文件)。

你还会在主题目录中找到一个名为 **shell** 的文件夹,其中包含每个主题方案的文件夹。

最后,方案目录包含一个 shellstyle.dll。你也可以在此目录中找到由 shellstyle.dll 引用的位图(如壁纸)或其他资源。

.theme 文件

`.theme` 文件是一个初始化文件,包含定义许多信息的节和键/值对。以下示例显示了 .theme 文件的内容。

    [Theme]

    ; Recycle Bin
    [CLSID\{645FF040-5081-101B-9F08-00AA002F954E}\DefaultIcon]
    full=%SystemRoot%\SYSTEM32\shell32.dll,32
    empty=%SystemRoot%\SYSTEM32\shell32.dll,31

    [Control Panel\Desktop]
    Wallpaper=%WinDir%Resources\Themes\Panther\Wallpaper\Aqua_Blue.jpg
    TileWallpaper=0
    WallpaperStyle=2
    Pattern=
    ScreenSaveActive=0

    [boot]
    SCRNSAVE.EXE=%WinDir%system32\logon.scr

    [VisualStyles]
    Path=%ResourceDir%\Themes\Panther\Panther.msstyles
    ColorStyle=NormalColor
    Size=NormalSize

    [MasterThemeSelector]
    MTSM=DABJDKT
    ThemeColorBPP=4

最有趣的部分是 [VisualStyles]。你将在其中找到三个重要提示:指向 .msstyles 文件的相对路径(Path)、主题使用的颜色方案(ColorStyle)和大小行为(Size)。

注意: 如果你的环境变量中没有定义 %ResourceDir%,你可以使用下面的示例代码。

    System.Collections.IDictionary vars = 
        System.Environment.GetEnvironmentVariables();

    // extract the "Path" value
    System.Text.StringBuilder val = 
        new System.Text.StringBuilder(MAX_PATH); // MAX_PATH = 255
    Kernel32.GetPrivateProfileString(sectionName, keyName, "", val, 
        MAX_PATH, iniFile); // MAX_PATH = 255

    // remove comments
    String path = val.ToString();
    if(path.IndexOf(";") != -1) path = path.Substring(0, 
        result.IndexOf(";"));

    path = path.Replace("%WinDir%", @"%windir%\");
    path = path.Replace(@"\\", @"\");
    path = path.Replace("%ResourceDir%", @"%windir%\Resources");
    path = path.Replace("%windir%", Convert.ToString(vars["windir"]));

.msstyles 文件

`.msstyles` 文件基本上是一个 PE 文件(例如,一个 DLL)。我们寻找的是资源部分,位于 TEXTFILE 和 BITMAP 资源目录之下。

注意: 你可以使用像 ResEdit 这样的软件来探索你的 .msstyles 文件。

Screenshot - resourceTree.png

最重要的文件是 THEME_INI。它为我们提供了:

  • 主题的文档属性(例如,Author、DisplayName 等),
  • 主题的颜色方案,
  • 主题的大小方案,
  • 用于提取位图的前缀名称,
  • 包含当前颜色方案和大小主题数据的 .ini 文件的名称。

如果我们使用

    [VisualStyles]
    Path=%ResourceDir%\Themes\Panther\Panther.msstyles
    ColorStyle=NormalColor
    Size=NormalSize    

`.theme` 文件中指定的,我们将会在名为 [File.]["color scheme"]["theme name"] 的节中找到我们的参考提示,例如:

    [File.Normalpanther]
    ColorSchemes = panther
    Sizes = NormalSize    

在我们的示例中,包含主题数据的 .ini 文件名为 NORMALPANTHER_INI,并且前缀值在 ColorSchemes 键中设置(位图的名称将以 PANTHER 开头)。

主题数据初始化文件

这些文件包含每个类和视觉样式的各个部分的信息。

在阅读了许多这类文件后,节的规则似乎是:

  • 标准节的格式为 [ClassName].[ClassPart][(ClassPartState)]
  • 类节定义了默认的 UI 值。
  • 带有状态的类节定义了该状态下的 UI 值。
  • 特殊节的格式为 [ClassName]::[ClassName].[ClassPart][(ClassPartState)]
  • [ClassPart] 和 [(ClassPartState)] 是可选的。
  • 节名称(以及键名称)不区分大小写。

那么,如果你想要复选框数据,你需要在 [Button.Checkbox][button.checkbox] 节中查找。

shellstyle.dll

Mathew Hall 的文章描述了该文件的内部结构。如果你有兴趣了解它,请查看他在 Code Project 上的文章 "Themed Windows XP style Explorer Bar"。

三、包装器

下图描述了包装器的设计:

Screenshot - uxthemeOM.gif

简要说明:

  • UxTheme 类是通用包装器。它被 VisualStyleInformationVisualStyleRenderer 使用,以获取属性值或使用当前主题的数据绘制特定控件。
  • 主要类是 VisualStyleInformationVisualStyleRendererVisualStyleFile
    • VisualStyleInformation 提供对当前主题信息的访问,例如主题的作者或版权。
    • VisualStyleRenderer 用于绘制控件。它调用 UxTheme 包装器函数来获取当前视觉样式的数据,并调用 VisualStyleFile 来处理其他主题。
    • VisualStyleFile 处理主题数据。你可以使用它来提取位图,或者仅仅是为了获取组件属性。
  • MemoryIniFile 用于将嵌入在 .msstyles 主题文件中的 .ini 文件映射到内存中。
  • PEFile 用于读取 .msstyles 文件,并访问其资源部分。

我不会列出每个类提供的所有方法,因为这不属于本文的主题。我将重点介绍 VisualStyleFile 及其关联的类/结构。

VisualStyleFile

这个类是最重要的:它的作用是提供一个主题的对象表示。它也是一个可以被你的自定义控件共享的组件。

它通过 .theme 文件检索视觉样式的信息,例如,它检索主题的 .mssstyles,映射使用的 .ini 文件,然后将主题的属性映射到八个结构中:

  • VisualStyleDocumention,提供在视觉样式文件中指定的文档的基本信息;
  • VisualStyleMetrics,提供在视觉样式文件中指定的颜色、字体、大小的基本信息;
    • VisualStyleMetricColors,用于视觉样式中定义的系统颜色,例如 ActiveCaption
    • VisualStyleMetricFonts,用于视觉样式中定义的系统字体,例如 CaptionFont
    • VisualStyleMetricSizes,用于视觉样式中定义的系统大小,例如 CaptionBarHeight
  • VisualStyleProperties,映射给定组件(例如,类+部分+状态)的属性;
  • VisualStyleScheme,映射给定颜色方案的属性;
  • VisualStyleSize,映射给定大小方案的属性。

所以,如果你想访问一个主题的原始信息/数据,你只需要创建一个新的 VisualStyleFile 并按如下方式获取你想要的内容:

    // create a new VisualStyleFile
    String themeFile = @"C:\WINDOWS\Resources\Themes\Luna.theme";
    using(VisualStyleFile theme = new VisualStyleFile(themeFile))
    {
        // get the documentation informations
        VisualStyleDocumention doc = theme.Documentation;
        Console.WriteLine(doc.Author + " - " + doc.Copyright);

        // get the color/size scheme
        Console.WriteLine("Color scheme: "+theme.ThemeSchemeName);
        Console.WriteLine("Size scheme: "+theme.ThemeSizeName);

        // get the properties of a component
        VisualStyleProperties buttonProps = theme.GetElementProperties(
            "BUTTON", (uint)ButtonPart.PushButton);
        Console.WriteLine(
            "Button is transparent : "+buttonProps.Transparent);
        Console.WriteLine("Background image: "+buttonProps.ImageFile);

        // etc...
    }

使用代码

我知道,这有点繁琐。但好消息是,你读到的所有内容都已在本示例中实现。我将这一部分作为 FAQ 来构建,也许你会找到你想知道的。

如何枚举已安装的主题文件

String[] themes = VisualStyleInformation.GetThemeFiles();

foreach(String theme in themes)
{
    Console.WriteLine(theme);
}

如何获取主题信息

如果你想要当前视觉样式的信息:

// Documentation properties
Console.WriteLine(
    "Current theme file: "+VisualStyleInformation.CurrentThemeFileName);
Console.WriteLine(
    "Application themed? "+VisualStyleInformation.IsApplicationThemed);
Console.WriteLine("Current theme author: "+VisualStyleInformation.Author);
Console.WriteLine("Current theme company: "+VisualStyleInformation.Company);
// etc...

// Theme raw properties for a "Button"
VisualStyleRenderer renderer = VisualStyleRenderer("BUTTON",
    (uint)ButtonPart.PushButton, (uint)PushButtonState.Normal);
// bool properties
bool isButtonTransparent = renderer.GetBoolean(BooleanProperty.Transparent);
bool isBacgroundFilled = renderer.GetBoolean(BooleanProperty.BackgroundFill);

// Color properties
Color borderColor = renderer.GetColor(ColorProperty.BorderColor);
Color fillColor = renderer.GetColor(ColorProperty.FillColor);
Color textColor = renderer.GetColor(ColorProperty.TextColor);

// String properties
String backgroundImage = GetFilename(FilenameProperty.ImageFile);
String glyph = GetFilename(FilenameProperty.GlyphImageFile);// for combo or
                                                           // caption button

// etc...

如果你想要特定主题(非当前主题)的属性:

String themeFile = @"C:\WINDOWS\Resources\Themes\Luna.theme";
using(VisualStyleFile theme = new VisualStyleFile(themeFile))
{
    // get the documentation informations
    VisualStyleDocumention doc = theme.Documentation;
    Console.WriteLine(doc.Author + " - " + doc.Copyright);

    // get the color/size scheme
    Console.WriteLine("Color scheme: "+theme.ThemeSchemeName);
    Console.WriteLine("Size scheme: "+theme.ThemeSizeName);

    // get the properties of a component
    VisualStyleProperties buttonProps = theme.GetElementProperties("BUTTON",
       (uint)ButtonPart.PushButton);
    Console.WriteLine("Button is transparent : "+buttonProps.Transparent);
    Console.WriteLine("Background image: "+buttonProps.ImageFile);

    // etc...
}

注意: 即使是当前主题,我也更倾向于第二种解决方案。

如何提取主题的 .ini 文件

// save the .ini files of a specific theme
String themeFile = @"C:\WINDOWS\Resources\Themes\Luna.theme";
String savePath = Environment.CurrentDirectory + @"\inifiles\";
using(VisualStyleFile theme = new VisualStyleFile(themeFile))
{
    theme.SaveIniFiles(savePath);
}

如何获取主题的位图

// gets an identified bitmap
String themeFile = @"C:\WINDOWS\Resources\Themes\Luna.theme";
String savePath = Environment.CurrentDirectory;

using(VisualStyleFile theme = new VisualStyleFile(themeFile))
{
    // gets the properties
    VisualStyleProperties buttonProps = theme.GetElementProperties("BUTTON",
        (uint)ButtonPart.PushButton);

    // save the bitmap to disk
    using(Bitmap bmp = theme.GetBitmap(buttonProps.ImageFile))
    {
        bmp.Save(savePath+@"\buttonBitmaps.bmp");
    }
}

如何与你的自定义控件共享一个主题

如何使用(非当前)主题的数据与你的自定义控件?

你只需通过设计器将 VisualStyleFile 添加到你的窗体,并更新/设置其 ThemeFile 属性。

Screenshot - visualstylefile_step1.png

Screenshot - visualstylefile_step2.png

然后,将你的自定义控件的 VisualStyleFile 属性设置为新的 VisualStyleFile

Screenshot - visualstylefile_step3.png

如何创建你自己的自定义渲染器

实现取决于你:它取决于你想绘制什么(例如,一个列表视图的标题、一个按钮、一个滚动条、一个标题栏等)。技巧是清楚地将绘制行为与组件逻辑分开。我的观点是,创建你的自定义控件作为“bean”,它计算正确的值(边界、大小等),并将它们作为参数传递给你的渲染器方法。

让我们做一个 15 分钟的教程:为窗口的标题按钮创建一个渲染器。

  1. 第一步是查看 uxtheme 的文件,其中包含窗口按钮的列表及其可能的各种状态:你将找到 WindowPartWindowButtonState 枚举。

WindowPart 包含很多值,所以最好的方法是只使用窗口按钮的值,创建一个像这样的枚举:

public enum WindowButtonType : int
{
    CloseButton = (int)WindowPart.CloseButton,
    MaxButton = (int)WindowPart.MaxButton,
    MinButton = (int)WindowPart.MinButton,
    HelpButton = (int)WindowPart.HelpButton,
    RestoreButton = (int)WindowPart.RestoreButton,
    SysButton = (int)WindowPart.SysButton
};
  1. 创建一个基本的渲染器,复制其中一个示例渲染器(例如 RadioButtonRenderer),然后删除不必要的方法。
/// <summary>
/// Provides methods for drawing a WindowButton control (eg. Help, Close, 
/// Minize, etc.).
/// </summary>
public sealed class WindowButtonRenderer
{
    /// <summary>
    /// Gets a value indicating whether the WindowButtonRenderer class 
    /// can be used to draw a window button control with visual styles.
    /// </summary>
    public static bool IsSupported
    {
        get
        {
            return VisualStyleInformation.IsApplicationThemed;
        }
    }

    private WindowButtonRenderer(){ }

    // ...
}
  1. 在其中编写简单的将被你的组件调用的方法(DrawButton 方法),并记住你的渲染器将使用一个 Graphics 对象,并且需要你的组件边界和类型。
/// <summary>
/// Provides methods for drawing a WindowButton control (eg. Help, Close, 
/// Minize, etc.).
/// </summary>
public sealed class WindowButtonRenderer
{
    /// <summary>
    /// Gets a value indicating whether the WindowButtonRenderer class 
    /// can be used to draw a window button control with visual styles.
    /// </summary>
    public static bool IsSupported
    {
        get
        {
            return VisualStyleInformation.IsApplicationThemed;
        }
    }

    private WindowButtonRenderer(){ }

    #region Methods

    #region Misc
    /// <summary>
    /// Gets a VisualStyleRenderer for the specified button state.
    /// </summary>
    /// <param name="button">The button type.</param>
    /// <param name="state">The button state.</param>
    private static VisualStyleRenderer GetButtonRenderer(
        WindowButtonType button, WindowButtonState state)
    {
        switch(state)
        {
            case WindowButtonState.Normal:
                if(button == WindowButtonType.CloseButton)        
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.CloseButton.Normal);
                else if(button == WindowButtonType.MaxButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MaxButton.Normal);
                else if(button == WindowButtonType.MinButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MinButton.Normal);
                else if(button == WindowButtonType.HelpButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.HelpButton.Normal);
                else return new VisualStyleRenderer(
                    VisualStyleElement.Window.RestoreButton.Normal);
            case WindowButtonState.Hot:
                if(button == WindowButtonType.CloseButton)        
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.CloseButton.Hot);
                else if(button == WindowButtonType.MaxButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MaxButton.Hot);
                else if(button == WindowButtonType.MinButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MinButton.Hot);
                else if(button == WindowButtonType.HelpButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.HelpButton.Hot);
                else return new VisualStyleRenderer(
                    VisualStyleElement.Window.RestoreButton.Hot);
            case WindowButtonState.Pushed:
                if(button == WindowButtonType.CloseButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.CloseButton.Pressed);
                else if(button == WindowButtonType.MaxButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MaxButton.Pressed);
                else if(button == WindowButtonType.MinButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MinButton.Pressed);
                else if(button == WindowButtonType.HelpButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.HelpButton.Pressed);
                else return new VisualStyleRenderer(
                    VisualStyleElement.Window.RestoreButton.Pressed);
            case WindowButtonState.Disabled:
            default:
                if(button == WindowButtonType.CloseButton)   
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.CloseButton.Disabled);
                else if(button == WindowButtonType.MaxButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MaxButton.Disabled);
                else if(button == WindowButtonType.MinButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MinButton.Disabled);
                else if(button == WindowButtonType.HelpButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.HelpButton.Disabled);
                else return new VisualStyleRenderer(
                    VisualStyleElement.Window.RestoreButton.Disabled);
        }
    }
    /// <summary>
    /// Gets a <see cref="VisualStyleRenderer"> for the specified 
    /// button state.
    /// </summary>
    /// <param name="style">The visual style file to use.</param>
    /// <param name="button">The button type.</param>
    /// <param name="state">The button state.</param>
    private static VisualStyleRenderer GetButtonRenderer(
        VisualStyleFile style, WindowButtonType button, 
        WindowButtonState state)
    {
        if(button == WindowButtonType.CloseButton)        
            return new VisualStyleRenderer(
                VisualStyleElement.Window.CloseButton.GetElement(
                style, state));
        else if(button == WindowButtonType.MaxButton)    
            return new VisualStyleRenderer(
                VisualStyleElement.Window.MaxButton.GetElement(
                style, state));
        else if(button == WindowButtonType.MinButton)    
            return new VisualStyleRenderer(
                VisualStyleElement.Window.MinButton.GetElement(
                style, state));
        else if(button == WindowButtonType.HelpButton)    
            return new VisualStyleRenderer(
                VisualStyleElement.Window.HelpButton.GetElement(
                style, state));
        else return new VisualStyleRenderer(
            VisualStyleElement.Window.RestoreButton.GetElement(
            style, state));
    }
    #endregion

    #region Drawing
    public static void DrawButton(Graphics g, Rectangle bounds, 
        WindowButtonType button, WindowButtonState state)
    {
        if(!IsSupported) throw new InvalidOperationException();

        VisualStyleRenderer renderer = GetButtonRenderer(button, state);
        if(renderer != null) renderer.DrawBackground(g, bounds);
    }

    /// <summary>
    /// Draws a window Close button control in the specified state 
    /// and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawCloseButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.CloseButton, state);
    }

    /// <summary>
    /// Draws a window Help button control in the specified state and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    /// specifies the visual state of the button.</param>
    public static void DrawHelpButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.HelpButton, state);
    }

    /// <summary>
    /// Draws a window Minize button control in the specified state 
    /// and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawMinizeButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.MinButton, state);
    }
    /// <summary>
    /// Draws a window Maximize button control in the specified state 
    /// and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawMaximizeButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.MaxButton, state);
    }

    /// <summary>
    /// Draws a window Restore button control in the specified state 
    /// and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawRestoreButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.RestoreButton, state);
    }
    #endregion
    #endregion
}
</see>
  1. 为你的渲染器添加 VisualStyleFile 支持。
/// <summary>
/// Provides methods for drawing a WindowButton control (eg. Help, Close, 
/// Minize, etc.).
/// </summary>
public sealed class WindowButtonRenderer
{
    /// <summary>
    /// Gets a value indicating whether the WindowButtonRenderer class 
    /// can be used to draw a window button control with visual styles.
    /// </summary>
    public static bool IsSupported
    {
        get
        {
            return VisualStyleInformation.IsApplicationThemed;
        }
    }

    private WindowButtonRenderer(){ }

    #region Methods

    #region Misc
    /// <summary>
    /// Gets a VisualStyleRenderer for the specified button state.
    /// </summary>
    /// <param name="button">The button type.</param>
    /// <param name="state">The button state.</param>
    private static VisualStyleRenderer GetButtonRenderer(
        WindowButtonType button, WindowButtonState state)
    {
        switch(state)
        {
            case WindowButtonState.Normal:
                if(button == WindowButtonType.CloseButton)        
                    return new VisualStyleRenderer(
                       VisualStyleElement.Window.CloseButton.Normal);
                else if(button == WindowButtonType.MaxButton)    
                    return new VisualStyleRenderer(
                       VisualStyleElement.Window.MaxButton.Normal);
                else if(button == WindowButtonType.MinButton)    
                    return new VisualStyleRenderer(
                       VisualStyleElement.Window.MinButton.Normal);
                else if(button == WindowButtonType.HelpButton)    
                    return new VisualStyleRenderer(
                       VisualStyleElement.Window.HelpButton.Normal);
                else return new VisualStyleRenderer(
                       VisualStyleElement.Window.RestoreButton.Normal);
            case WindowButtonState.Hot:
                if(button == WindowButtonType.CloseButton)
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.CloseButton.Hot);
                else if(button == WindowButtonType.MaxButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MaxButton.Hot);
                else if(button == WindowButtonType.MinButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MinButton.Hot);
                else if(button == WindowButtonType.HelpButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.HelpButton.Hot);
                else return new VisualStyleRenderer(
                    VisualStyleElement.Window.RestoreButton.Hot);
            case WindowButtonState.Pushed:
                if(button == WindowButtonType.CloseButton)        
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.CloseButton.Pressed);
                else if(button == WindowButtonType.MaxButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MaxButton.Pressed);
                else if(button == WindowButtonType.MinButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MinButton.Pressed);
                else if(button == WindowButtonType.HelpButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.HelpButton.Pressed);
                else return new VisualStyleRenderer(
                    VisualStyleElement.Window.RestoreButton.Pressed);
            case WindowButtonState.Disabled:
            default:
                if(button == WindowButtonType.CloseButton)        
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.CloseButton.Disabled);
                else if(button == WindowButtonType.MaxButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MaxButton.Disabled);
                else if(button == WindowButtonType.MinButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.MinButton.Disabled);
                else if(button == WindowButtonType.HelpButton)    
                    return new VisualStyleRenderer(
                        VisualStyleElement.Window.HelpButton.Disabled);
                else return new VisualStyleRenderer(
                    VisualStyleElement.Window.RestoreButton.Disabled);
        }
    }
    /// <summary>
    /// Gets a <see cref="VisualStyleRenderer"> for the specified 
    /// button state.
    /// </summary>
    /// <param name="style">The visual style file to use.</param>
    /// <param name="button">The button type.</param>
    /// <param name="state">The button state.</param>
    private static VisualStyleRenderer GetButtonRenderer(
        VisualStyleFile style, WindowButtonType button, 
        WindowButtonState state)
    {
        if(button == WindowButtonType.CloseButton)        
            return new VisualStyleRenderer(
                VisualStyleElement.Window.CloseButton.GetElement(
                style, state));
        else if(button == WindowButtonType.MaxButton)    
            return new VisualStyleRenderer(
                VisualStyleElement.Window.MaxButton.GetElement(
                style, state));
        else if(button == WindowButtonType.MinButton)    
            return new VisualStyleRenderer(
                VisualStyleElement.Window.MinButton.GetElement(style,state));
        else if(button == WindowButtonType.HelpButton)    
             return new VisualStyleRenderer(
               VisualStyleElement.Window.HelpButton.GetElement(style,state));
        else return new VisualStyleRenderer(
            VisualStyleElement.Window.RestoreButton.GetElement(style,state));
    }
    #endregion

    #region Drawing
    public static void DrawButton(Graphics g, Rectangle bounds, 
        WindowButtonType button, WindowButtonState state)
    {
        if(!IsSupported) throw new InvalidOperationException();

        VisualStyleRenderer renderer = GetButtonRenderer(button, state);
        if(renderer != null) renderer.DrawBackground(g, bounds);
    }

    public static void DrawButton(VisualStyleFile style, Graphics g, 
        Rectangle bounds, WindowButtonType button, WindowButtonState state)
    {
        if(!IsSupported) throw new InvalidOperationException();

        VisualStyleRenderer renderer = GetButtonRenderer(style, button, 
            state);
        if(renderer != null) renderer.DrawBackground(g, bounds);
    }

    /// <summary>
    /// Draws a window Close button control in the specified state 
    /// and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawCloseButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.CloseButton, state);
    }
    /// <summary>
    /// Draws a window Close button control in the specified state 
    /// and bounds.
    /// </summary>
    /// <param name="style">The visual style file to use.</param>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ////                    specifies the visual state of the button.</param>
    public static void DrawCloseButton(VisualStyleFile style, Graphics g, 
        Rectangle bounds, WindowButtonState state)
    {
        DrawButton(style, g, bounds, WindowButtonType.CloseButton, state);
    }


    /// <summary>
    /// Draws a window Help button control in the specified state and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawHelpButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.HelpButton, state);
    }
    /// <summary>
    /// Draws a window Help button control in the specified state and bounds.
    /// </summary>
    /// <param name="style">The visual style file to use.</param>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawHelpButton(VisualStyleFile style, Graphics g, 
        Rectangle bounds, WindowButtonState state)
    {
        DrawButton(style, g, bounds, WindowButtonType.HelpButton, state);
    }


    /// <summary>
    /// Draws a window Minize button control in the specified 
    /// state and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawMinizeButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.MinButton, state);
    }
    /// <summary>
    /// Draws a window Minize button control in the specified state 
    /// and bounds.
    /// </summary>
    /// <param name="style">The visual style file to use.</param>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawMinizeButton(VisualStyleFile style, Graphics g, 
        Rectangle bounds, WindowButtonState state)
    {
        DrawButton(style, g, bounds, WindowButtonType.MinButton, state);
    }


    /// <summary>
    /// Draws a window Maximize button control in the specified state 
    /// and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawMaximizeButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.MaxButton, state);
    }
    /// <summary>
    /// Draws a window Maximize button control in the specified state and 
    /// bounds.
    /// </summary>
    /// <param name="style">The visual style file to use.</param>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawMaximizeButton(VisualStyleFile style, Graphics g, 
        Rectangle bounds, WindowButtonState state)
    {
        DrawButton(style, g, bounds, WindowButtonType.MaxButton, state);
    }

    /// <summary>
    /// Draws a window Restore button control in the specified 
    /// state and bounds.
    /// </summary>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawRestoreButton(Graphics g, Rectangle bounds, 
        WindowButtonState state)
    {
        DrawButton(g, bounds, WindowButtonType.RestoreButton, state);
    }
    /// <summary>
    /// Draws a window Restore button control in the specified 
    //// state and bounds.
    /// </summary>
    /// <param name="style">The visual style file to use.</param>
    /// <param name="g">The Graphics used to draw the button.</param>
    /// <param name="bounds">The button bounds.</param>
    /// <param name="state">One of the WindowButtonState values that 
    ///                     specifies the visual state of the button.</param>
    public static void DrawRestoreButton(VisualStyleFile style, Graphics g, 
        Rectangle bounds, WindowButtonState state)
    {
        DrawButton(style, g, bounds, WindowButtonType.RestoreButton, state);
    }
    #endregion
    #endregion
}
</see>
  1. 创建你的 WindowButton 组件,就像渲染器一样(例如,复制 CustomRadioButton 组件,然后删除不必要的字段和方法)。
    6 - 清理后,添加你的字段,如 WindowButtonStateWindowButtonType,并在 OnPaint() 事件中用你全新的 WindowButtonRenderer 替换渲染器。
    7 - 添加你自己的组件行为,最后,你将得到类似下面的示例代码:
public class WindowButton : System.Windows.Forms.Control, 
    IVisualStyleSwitchable
{
    /// <summary>Event fired when a control's property changes.</summary>
    [Category("Action"), Description(
        "Occurs when a control's property changes."),]
    public event EventHandler PropertyChanged = null;

    #region Fields
    private System.ComponentModel.IContainer components = null;

    private WindowButtonType type = WindowButtonType.CloseButton;
    private WindowButtonState state = WindowButtonState.Normal;
    private Color backColor;
    private Rectangle realBounds = Rectangle.Empty;
    private Size realSize = Size.Empty;
    private VisualStyleFile file = null;

    #endregion

    #region Accessors

    #region Runtime
    /// <summary>
    /// Gets or sets the button state.
    /// </summary>
    private WindowButtonState State
    {
        get
        {
            return this.state;
        }
        set
        {
            if(this.state == value) return;
            this.state = value;

            OnPropertyChanged();
        }
    }
    /// <summary>
    /// Get the control real size.
    /// </summary>
    [Browsable(false),]
    [DesignerSerializationVisibility(
        DesignerSerializationVisibility.Hidden),]
    private Size RealSize
    {
        get
        {
            if(this.realSize==Size.Empty) realSize = Size;
            return this.realSize;
        }
        set
        {
            this.realSize = value;
        }
    }
    /// <summary>
    /// Get the control bound's rectangle.
    /// </summary>
    [Browsable(false),]
    [DesignerSerializationVisibility(
        DesignerSerializationVisibility.Hidden),]
    public new Rectangle ClientRectangle
    {
        get
        {
            if(this.realBounds.Width != RealSize.Width || 
                this.realBounds.Height != RealSize.Height)
            {
                this.realBounds = new Rectangle(0, 0, RealSize.Width, 
                    RealSize.Height);
            }

            return this.realBounds;
        }
    }
    #endregion

    #region Appearance
    /// <summary>
    /// Gets or sets the visual style to use.
    /// </summary>
    [Browsable(true), Category("Appearance"),]
    [DefaultValue(null),]
    public Devcorp.Controls.VisualStyles.VisualStyleFile VisualStyle
    {
        get
        {
            return this.file;
        }
        set
        {
            if(this.file == value) return;

            if(this.file != null) this.file.ThemeFileChanged -= 
                new EventHandler(file_ThemeFileChanged);
            this.file = value;
            if(this.file != null) this.file.ThemeFileChanged += 
                new EventHandler(file_ThemeFileChanged);

            OnPropertyChanged();
        }
    }
    /// <summary>
    /// Gets or sets the visual style to use.
    /// </summary>
    [Browsable(true), Category("Appearance"),]
    [DefaultValue(typeof(WindowButtonType),"CloseButton"),]
    public WindowButtonType Type
    {
        get
        {
            return this.type;
        }
        set
        {
            if(this.type == value) return;
            this.type = value;

            OnPropertyChanged();
        }
    }

    /// <summary>
    /// Gets or sets the background color.
    /// </summary>
    [Browsable(true), Category("Appearance"),]
    [DefaultValue(typeof(Color), "Control"),]
    public new Color BackColor
    {
        get
        {
            return this.backColor;
        }
        set
        {
            if(this.backColor == value) return;

            this.backColor = value;
            base.BackColor = value;

            OnPropertyChanged();
        }
    }

    #endregion
    #endregion

    #region Constructor(s)
    /// <summary>
    /// Default constructor.
    /// </summary>
    public WindowButton()
    {
        SetStyle(ControlStyles.UserPaint, true);
        SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        SetStyle(ControlStyles.DoubleBuffer, true);
        SetStyle(ControlStyles.ResizeRedraw, true);
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        SetStyle(ControlStyles.Selectable, true);

        InitializeComponent();
    }
    /// <summary>
    /// Default constructor.
    /// </summary>
    public WindowButton(System.ComponentModel.IContainer container) : this()
    {
        container.Add(this);
    }

    #endregion
    #region Methods

    #region VS generated code
    /// <summary>Clean up any resources being used.</summary>
    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null)
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        //
        // WindowButton
        //
        this.Size = new System.Drawing.Size(20, 20);

    }

    #endregion
    #region Drawing
    /// <summary>
    /// Handles Onpaint event.
    /// </summary>
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        if(!Disposing && !Parent.Disposing)
        {
            if(this.file != null && this.file.StyleFile!=String.Empty)
            {
                WindowButtonRenderer.DrawButton(this.file, e.Graphics, 
                   ClientRectangle, this.type, this.state);
            }
            else
            {
                WindowButtonRenderer.DrawButton(e.Graphics, 
                    ClientRectangle, this.type, this.state);
            }
        }
    }

    #endregion

    #region Events
    private void file_ThemeFileChanged(object sender, EventArgs e)
    {
        Invalidate();
    }
    /// <summary>
    /// Fires the PropertyChange event.
    /// </summary>
    protected virtual void OnPropertyChanged()
    {
        if(PropertyChanged!=null) PropertyChanged(this, EventArgs.Empty);

        Invalidate();
    }
    /// <summary>
    /// Handles WM messages to set the right button states.
    /// </summary>
    /// <param name="m"></param>
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        if(m.HWnd == Handle)
        {
            switch(m.Msg)
            {
                case (int)Messages.WM_NCCALCSIZE:
                    if(m.WParam==IntPtr.Zero || m.WParam==new IntPtr(1))
                    {
                        NCCALCSIZE_PARAMS csp = (
                            NCCALCSIZE_PARAMS)Marshal.PtrToStructure(
                            m.LParam, typeof(NCCALCSIZE_PARAMS));

                      RealSize = new Size((csp.rgrc1.Right-csp.rgrc1.Left),
                            (csp.rgrc1.Bottom-csp.rgrc1.Top));
                        Marshal.StructureToPtr(csp, m.LParam, false );
                    }
                    break;
                case (int) Messages.WM_CREATE:
                    this.state = (Enabled)? WindowButtonState.Normal: 
                        WindowButtonState.Disabled;
                    break;
                case (int)Messages.WM_LBUTTONDBLCLK:
                case (int)Messages.WM_LBUTTONDOWN:
                    if(Enabled)
                    {
                        if(!Focused) Focus();
                        State = WindowButtonState.Pushed;
                    }
                    break;
                case (int)Messages.WM_LBUTTONUP:
                    if(Enabled)
                    {
                        State = WindowButtonState.Hot;
                    }
                    break;
                case (int)Messages.WM_MOUSEHOVER:
                case (int)Messages.WM_MOUSEMOVE:
                    if(Enabled)
                    {
                        if(this.state != WindowButtonState.Pushed) State = 
                            WindowButtonState.Hot;
                    }
                    break;
                case (int)Messages.WM_MOUSELEAVE:
                case (int)Messages.WM_KILLFOCUS:
                    if(Enabled)
                    {
                        State = WindowButtonState.Normal;
                    }
                    break;
                case (int)Messages.WM_SETFOCUS:
                case (int)Messages.WM_ENABLE:
                    if(Enabled)
                    {
                        State = WindowButtonState.Normal;
                    }
                    else
                    {
                        State = WindowButtonState.Disabled;
                    }
                    break;
            }
        }
    }
    #endregion
    #endregion
}

恭喜!你的自定义 WindowButton 准备就绪!(哦!你应该先把它添加到你的窗体上)

注意: 我在示例中包含了另外四个渲染器(ButtonRendererCheckboxRendererRadioButtonRendererProgressBarRenderer)。你可以使用它们来创建自己的渲染器(或者问我是否已经实现了你需要的东西)。

已知问题

  • 我并没有为所有的视觉样式部分创建渲染器,所以你在自己的实现过程中可能会发现错误(或缺少功能支持)。
  • VisualStyleFile 中使用当前活动的 <$> 主题数据可能会导致应用程序崩溃:不要将这类 VisualStyleFile 与你的自定义控件关联。
  • 在项目中使用了过多的 VisualStyleFile 会导致加载时窗体闪烁(我正在研究一种在所有主题数据都已加载完成后触发事件的方法)。
  • 在项目中使用了过多的 VisualStyleFile 会导致内存占用过高(当然,因为我们是在托管环境中)。
  • 渲染器的实现相当麻烦:你应该复制/修改示例渲染器来创建你自己的。

如果你发现错误或不一致之处,请不要犹豫告诉我。我会修复它并更新这篇文章。

关注点

也许第一个有趣的点是包装器本身。最复杂的部分是位图的绘制和拉伸操作。你可以查看 VisualStyleRenderer 类中的 DrawBackground() 方法以及 VisualStyleHelper 类中的 StretchBitmap() 方法。

实现一个 PE 文件读取器是一项艰巨的任务。如果你想自己构建一个 PE 文件读取器,可以阅读 PECOFF 格式规范,或者你可以使用/扩展示例中的 PEFile 类。另一个想法是,你可以扩展 PEFile 类来创建一个像 ResEdit 这样的资源编辑器。

在我进行大量重构和性能测试时,我实现了一个快速的 .ini 文件内存映射器。目标是避免处理备份文件(嵌入在 .msstyles 中的 .ini 文件),这涉及到大量的 I/O(并使用 win32 内核函数)。你可以在 MemoryIniFileMemoryIniSection 类中找到它。

另一个有趣的地方是包装器的设计。对于那些仍然使用 .NET 1.1 并将要使用 .NET 2.0(或 3.0)的人来说,这将相当容易,因为他们会发现完全相同的类(但功能较少)。将他们的项目迁移将很简单,只需将命名空间 "Devcorp.Controls.VisualStyles" 的引用重命名为 "System.Windows.Forms.VisualStyles",并复制缺失的类。

历史

  • 2007年5月13日 - 版本 0.85
    • 移除了 MemoryIniHelper(冗余重复的方法)。
    • VisualStyleRenderer.DrawBackground() 中添加了填充背景(非位图)支持。
    • 添加了完整的视觉样式属性继承(参见 VisualStyleProperties)。
    • 添加了 GroupBoxRenderer
    • 添加了 ComboBoxRenderer
    • 添加了 TextBoxRenderer
    • 一些错误修复和少量优化。
  • 2007年5月9日 - 仅修正文章中的拼写错误。
  • 2007年5月6日 - 版本 0.82
    • 修复了在绘制拉伸位图时丢失 1px 宽度/高度的问题。
    • 修复了 GetBackgroundContentRectangle() 方法中的 VisualStyleFile 支持。
    • DrawEdge() 方法中添加了部分 VisualStyleFile 支持。
    • 添加了 ExplorerBarRenderer 渲染器(及其示例)。
    • 添加了 IEBarRenderer 渲染器(及其示例)。
    • 一些错误修复。
  • 2007年5月1日 - 版本 0.81
    • 修复了 MemoryIniFile 中的解析错误:一些包含键/值和注释的行未被正确处理。
    • 修复了带有图标的组件的绘制不一致问题。
    • 修复了使用超过三个 VisualStyleFile 时的高内存占用问题。
    • 添加了对主题大小规则的真正支持。
    • 在示例中添加了 WindowButtonWindowButtonRenderer
    • 一些错误修复。
  • 2007年4月29日 - 版本 0.8(发布于 The Code Project)
    • 添加了 .ini 文件内存映射器。
    • 添加了对嵌入式主题 .ini 文件的保存支持。
    • 将独立的渲染器作为示例添加。
    • 进行了大量设计重构和优化。
    • 一些错误修复。
  • 2005年11月20日 - 初始项目发布。
© . All rights reserved.