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

完全主题化的 Windows Vista 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (60投票s)

2007年5月20日

Ms-PL

9分钟阅读

viewsIcon

332091

downloadIcon

7783

在 Windows Vista 中完全渲染 Windows 控件(带特殊淡入效果)。

引言

本文旨在介绍如何在 Windows Vista 中使用新的 Windows Vista 用户界面控件。项目中包含一些组件(CommandlinksImageButtonSplitButton),它们的功能与 Windows Vista 主题控件完全相同。

本文旨在实现 Windows Vista 中大部分的界面效果(例如淡入/淡出效果)。

背景

大多数 Windows Vista 用户可能已经注意到其新的用户界面。每个控件都比 Windows XP 的控件更美观,并且带有漂亮的淡入淡出效果。然而,Microsoft Visual Studio 默认不支持完整的 Vista 主题。尽管 Visual Studio 2005 尝试解决视觉样式问题,但它仍然无法让控件实现 Windows Vista 中所有视觉效果。

本文涵盖以下内容:

  • 在一些常见控件(ButtonCheckboxRadiobuttonTextboxCombobox)上实现淡入淡出效果
  • Button 上使用图像并保留淡入淡出效果
  • Toolbar 上实现淡入淡出效果
  • MainMenuContextMenu 上实现 Windows Vista 主题
  • 实现 CommandLinkSplitButton
  • 实现不同颜色的 Progressbar
  • ListBoxTreeView 上实现类似资源管理器的界面

它利用了 user32.dllSendMessage() 函数以及 uxtheme.dllsetwindowtheme() 函数,以便将某些控件渲染到所需级别。

必备组件

您需要以下内容才能完成本教程

  • Windows Vista
  • .NET framework 2.0(Windows Vista 已预装)

其他一些有用的东西(可选)

通用控件

大多数控件已经为 Windows Vista 主题做好了准备。我相信您听说过 EnableVisualStyles(),对吗?通常调用 EnableVisualStyles() 来使支持主题的控件获得用户界面主题。Visual Studio 2005 已经完成了这一点,但 Visual Studio 2003 默认没有启用。如果您想为通用控件启用主题,可以查看 Heath Steward 关于 Windows 窗体的 Windows XP 视觉样式 的文章。

EnableVisualStyles() 影响以下控件:

  • 文本框
  • RichTextBox
  • HScrollBar
  • VScrollBar
  • 进度条
  • TabControl(部分)
  • MainMenu
  • ContextMenu
  • ComboBox
  • DataGrid
  • listBox
  • listView
  • 树视图
  • DateTimePicker
  • MonthCalendar
  • 分割器
  • TrackBar
  • StatusBar
  • ToolBar
  • 树视图
  • listView
  • Button
  • Groupbox(部分)

虽然 Visual Studio 2005 支持控件的视觉主题,但这些控件没有“完整”主题,其中包括淡入淡出效果。这个问题可以通过将 FlatStyle 设置为 System 来解决。然而,这样做意味着图像将不会显示在这些控件上。这个问题可以在下一节中相当容易地解决。

在按钮和其他控件上显示图像

通过将 buttonFlatStyle 设置为 System,会导致 button 和其他一些图像无法显示在这些 button 上。但是,本节旨在通过使用尽可能少的代码来解决这个问题。

如何完成这项工作?我们需要使用 user32.dll 中的 SendMessage() 函数。

首先,我们需要声明该函数(但首先,您需要导入 System.Runtime.InteropServices

//Imports the user32.dll
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage
            (IntPtr hWnd, int msg, int wParam, int lParam);

要设置控件的图像,我们需要使用 BM_SETIMAGE [0x00F7] 消息。目前,我们将使用 icon(在 System.Drawing 中声明)作为图像,因为获取 icon 的句柄很容易。

const int BM_SETIMAGE = 0x00F7;
private Icon buttonicon;
//Sets the button to use an icon
void SetImage()
{
    IntPtr iconHandle = IntPtr.Zero;

    // Get the icon's handle.
    if (this.buttonicon != null)
    {
        iconHandle = this.Icon.Handle;
    }

    //Sets the icon
    SendMessage(this.button1.Handle, BM_SETIMAGE, 1, (int)iconHandle);
}

这段代码的作用是获取您想要在按钮(名为“button1”)上显示的 icon 的句柄,然后向 Windows 发送一条消息,将 button 设置为使用该 icon

您可以通过设置“buttonicon”来设置您想要的 icon。您可以使用以下代码进行设置

buttonicon = new Icon(SystemIcons.Exclamation, 40, 40);
SetImage();

这会使用系统的感叹号 icon。您将获得

如果您想使用位图/图像而不是图标,您可以使用 BitmapGetHIcon() 方法来检索 bitmap 的句柄,然后使用该句柄来设置 button 的图像。

IntPtr iconhandle = IntPtr.Zero;
Bitmap image = new Bitmap("C:\images\an_image.png");
iconhandle = image_.GetHicon();
SendMessage(this.Handle, VistaConstants.BM_SETIMAGE, 1, (int)iconhandle);

button 仍然支持其他普通按钮所具有的淡入淡出效果。

然而,使用此方法存在一个小问题。如果此 buttontext 设置为“”,则不会显示图像。这个问题可以通过使用 createParams() 函数并将 BS_ICON 主题分配给 button 来解决。

//CreateParams property
protected override CreateParams CreateParams
{
    get
    {
        CreateParams cParams = base.CreateParams;
        if (this.ShowIconOnly == true)
        {
            //Render button without text
            cParams.Style |= 0x00000040; // BS_ICON value
        }
    return cParams;
    }
}

此方法最好通过继承自 button 的类来完成。

命令链接(CommandLinks)

CommandLinks 实际上只是按钮,只是分配了不同的主题。它们的主题及其值(取自 Windows Vista SDK 中的 CommCtrl.h)如下:

  • BS_COMMANDLINK [0x0000000E]

第一步是创建一个继承自 button 的类。然后,我们可以重写 CreateParams() 函数,使 button 使用相应的主题。为了使主题生效,您需要将 buttonflatstyle 设置为 system。下面是 CommandLink 的类:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices

public class CommandLink : Button
{
    const int BS_COMMANDLINK = 0x0000000E;

    //Set button's flatstyle to system
    public CommandLink()
    {
        this.FlatStyle = FlatStyle.System;
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cParams = base.CreateParams;
            //Set the button to use Commandlink styles
            cParams.Style |= BS_COMMANDLINK;
            return cParams;
        }
    }
}

然后,您可以构建项目并在您的 Windows 窗体中像普通 button 一样使用该组件(因为它继承自普通 button)。CommandLink 看起来像这样:

*鼠标在 CommandLink 上方,但 PrintScreen 无法捕获鼠标。

button 支持与实际控件相同的淡入淡出效果。

但是等等? CommandLink 的“备注”在哪里?我们可以通过向 Windows 发送 BCM_SETNOTE [0x00001609] 消息来实现此功能。我们可以将其整合到一个属性中,以便轻松设置 CommandLink 的备注。

const uint BCM_SETNOTE = 0x00001609;
//Imports the user32.dll
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr SendMessage
        (HandleRef hWnd, UInt32 Msg, IntPtr wParam, string lParam);
//Note property
private string note_ = "";
public string Note
{
    get
    {
        return this.note_;
    }
    set
    {
        this.note_ = value;
        this.SetNote(this.note_);
    }
}

//Sets the button's note
void SetNote(string NoteText)
{
    //Sets the note
    SendMessage(new HandleRef(this, this.Handle), 
                BCM_SETNOTE, IntPtr.Zero, NoteText);
}

您将得到

您还可以使用相同的方法在 CommandLinkButton 上显示图标/图像

拆分按钮(SplitButtons)

就像 CommandLink 一样,SplitButton 只是简单的按钮。您可以使用 CreateParams 方法将其样式设置为 SplitButton 的样式

  • BS_SPLITBUTTON [0x0000000C]

但是,即使设置了样式,您如何将菜单(上下文菜单)与 button 关联起来呢?我们可以通过首先关联一个事件来实现这一点,该事件将在用户单击 splitbutton 上的“下拉箭头”时触发。首先,我们将通过重写 wndproc() 方法来监听发送到 button 的所有消息。当单击下拉箭头时,会发送 BCN_SETDROPDOWNSTATE [0x1606] 消息。此消息会多次触发,但只有一条消息是唯一的,其 WPARAMS 的值为“1”。消息过滤最好在另一个类中完成。此示例代码将在下拉箭头被按下时触发一个自定义事件 (DropDown_Clicked)。

protected override void WndProc(ref Message m)
{
    // Listen for operating system messages; 
    // Filter out a lot of messages here
    //The dropdown glyph changes:
    //Mousedown (at dropdown area) => Dropdown Event fired 
    //                => Glyph changes => MouseUp => Glyph Changes
    switch (m.Msg)
    {
        case (VistaControls.VistaConstants.BCM_SETDROPDOWNSTATE):
        //PROBLEM: other buttons also have would encounter this message
        if (m.HWnd == this.Handle)
        {
            ////This message seems to occur when the drop down is clicked; 
            ////Occurs several times, but one of them have the value of 1 
            ////in WParams
            if (m.WParam.ToString() == "1")
            {
                DropDown_Clicked();
            }
        }
    break;
    }

    base.WndProc(ref m);
}

使用这个自定义事件,我们可以在按下按钮的下拉部分时显示一个上下文菜单(完整代码在演示中)。但这并非全部。我们只分配了一个事件,当下拉菜单被按下时会触发。我们需要让下拉菜单的字形在正确的时间改变。我们可以使用一些消息来实现字形的正确绘制。它们是 WM_PAINT [0x0F]、WM_LBUTTONDOWN [0x201]、WM_MOUSELEAVE [0x2A3]、WM_LBUTTONUP [0x202] 和 WM_KILLFOCUS [0x08] 消息。我们可以通过重写 WndProc() 消息来在发送这些消息时运行代码。(代码太混乱,无法在此处放置,但完整代码可以通过 Windows Vista Controls Demo 找到。)

此控件也支持淡入淡出效果。

提示文本框 (Cue TextBoxes)

Windows Vista 中的某些 TextBox 会提示用户应该输入什么内容(例如搜索框)。它通常以灰色文本显示在文本框上,当用户尝试在 textbox 中输入内容时,该文本会消失。此功能称为“提示横幅”。您可以通过使用 EM_SETCUEBANNER [0x1501] 消息在 textbox 上启用此功能。

SendMessage(textbox1.Handle, 0x1500 + 1, IntPtr.Zero, 
                "Please type in something.");

主菜单和上下文菜单

Visual Studio 2005 中的默认 MainMenu(或 MainMenuStrip)和 ContextMenu(或 ContextMenuStrip)不支持完整的 Windows Vista 主题

如果您想在主菜单和上下文菜单上获得 Windows Vista 主题,您需要使用 Visual Studio 2003 版本,或者 Visual Studio 2005 中向后兼容的版本(称为“MainMenu”和“ContextMenu”)。

您可以使用控件的 ContextMenu 属性将 ContextMenu 分配给控件(在 Visual Studio 2005 的 VB.NET 中可能会隐藏)。

工具栏

虽然 Visual Studio 2005 中的默认 toolbar 具有 Windows Vista 外观(如果 Rendermode 设置为 System),但它并不完全具备所有效果。Windows Vista 上的大多数 toolbar 都具有淡入淡出效果。要获得它,您需要使用 Visual Studio 2003 版本或 Visual Studio 2005(及更高版本)中向后兼容的版本,并将其 Appearance 设置为 Flat 以获得淡入淡出效果。

您也可以在 toolbar button 上使用 ImageList。为了让 Toolbar 在 Visual Studio 2003 中正确使用 ImageList,请在 application.EnableVisualStyles() 函数之后调用 applications.DoEvents() 函数(由 Don Kackman 提供的 Application.EnableVisualStyles Bug 修复)。

进度条(Progressbars)

在 Windows Vista 中,progressbar 有不同的样式,最常见的 progressbar 是绿色的。然而,也有红色和黄色版本(还有蓝色版本,称为仪表,但无法访问)。progressbar 颜色似乎对应于特定的 progressbar 状态。您可以使用 PBM_SETSTATE [0x40F] 消息设置这些状态。这些状态是 PBST_NORMAL [0x0001]、PBST_ERROR [0x0002] 和 PBST_PAUSE [0x0003]。

SendMessage(progressbar1.Handle, 0x400 + 16, 0x0001, 0); //Normal; Green
SendMessage(progressbar1.Handle, 0x400 + 16, 0x0002, 0); //Error; Red
SendMessage(progressbar1.Handle, 0x400 + 16, 0x0003, 0); //Pause; Yellow

树视图和列表视图 (TreeViews and ListViews)

默认情况下,TreeViewListView 的外观与 Explorer 不同

然而,通过一些代码,您可以使其看起来像这样

您需要使用 UXTheme.dll 文件中的 SetWindowTheme() 函数。您可以使用以下代码来实现这一点

//Imports the UXTheme DLL
[DllImport("uxtheme", CharSet = CharSet.Unicode)]
public extern static Int32 SetWindowTheme
        (IntPtr hWnd, String textSubAppName, String textSubIdList);

然后,您可以使用以下代码设置相关控件使用这些样式

SetWindowTheme(listView1.Handle, "explorer", null);

其中“listView1”是您的 ListView。您也可以对 TreeView 执行此操作。

但还有更多。在 Windows 资源管理器中,这些 ListView 也具有半透明选择。您可以通过调用 LVS_EX_DOUBLEBUFFER [0x00010000] 扩展样式来实现此目的。

SendMessage(listview1.Handle, 0x1000 + 54, 0x00010000, 0x00010000);

Windows 资源管理器中的 treeview 也具有淡入淡出效果。这可以通过 TVS_EX_FADEINOUTEXPANDOS [0x0040] 扩展样式来实现。

SendMessage(treeview1.Handle, 0x1100 + 44, 0x0040, 0x0040);

treeview 还具有“自动滚动”功能。您可以通过 TVS_EX_AUTOHSCROLL [0x0020] 扩展样式启用此功能。

SendMessage(treeview1.Handle, 0x1100 + 44, 0x0020, 0x0020);

关注点

可能存在一些可用于修改控件主题的消息或函数,这些消息或函数可能在有限的 Windows Vista 文档中找不到。当然,我们不得不使用旧版本的控件(如 MainMenuContextMenu)才能充分发挥新 Windows Vista 图形用户界面的潜力,这有点不寻常。

注意事项

我尚未在 Visual Studio 2003 上测试上述代码,也尚未在 Windows XP 上运行该应用程序(我“尚未”使其向后兼容)。

演示中一些可能无法在旧版 Windows 上运行的自定义控件包括 CommandLinkSplitButtonProgressBar

它已在 Visual Studio 2005 和 Windows Vista 中进行测试。

参考文献

历史

  • 版本 1.10:2007年6月17日 - 添加/修改了多个控件
  • 版本 1.00:2007年5月20日 - 初始版本
© . All rights reserved.