完全主题化的 Windows Vista 控件






4.91/5 (60投票s)
在 Windows Vista 中完全渲染 Windows 控件(带特殊淡入效果)。
引言
本文旨在介绍如何在 Windows Vista 中使用新的 Windows Vista 用户界面控件。项目中包含一些组件(Commandlinks
、ImageButton
、SplitButton
),它们的功能与 Windows Vista 主题控件完全相同。
本文旨在实现 Windows Vista 中大部分的界面效果(例如淡入/淡出效果)。
背景
大多数 Windows Vista 用户可能已经注意到其新的用户界面。每个控件都比 Windows XP 的控件更美观,并且带有漂亮的淡入淡出效果。然而,Microsoft Visual Studio 默认不支持完整的 Vista 主题。尽管 Visual Studio 2005 尝试解决视觉样式问题,但它仍然无法让控件实现 Windows Vista 中所有视觉效果。
本文涵盖以下内容:
- 在一些常见控件(
Button
、Checkbox
、Radiobutton
、Textbox
、Combobox
)上实现淡入淡出效果 - 在
Button
上使用图像并保留淡入淡出效果 - 在
Toolbar
上实现淡入淡出效果 - 在
MainMenu
和ContextMenu
上实现 Windows Vista 主题 - 实现
CommandLink
和SplitButton
- 实现不同颜色的
Progressbar
- 在
ListBox
和TreeView
上实现类似资源管理器的界面
它利用了 user32.dll 的 SendMessage()
函数以及 uxtheme.dll 的 setwindowtheme()
函数,以便将某些控件渲染到所需级别。
必备组件
您需要以下内容才能完成本教程
- 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
来解决。然而,这样做意味着图像将不会显示在这些控件上。这个问题可以在下一节中相当容易地解决。
在按钮和其他控件上显示图像
通过将 button
的 FlatStyle
设置为 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
。您将获得
如果您想使用位图/图像而不是图标,您可以使用 Bitmap
的 GetHIcon()
方法来检索 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
仍然支持其他普通按钮所具有的淡入淡出效果。
然而,使用此方法存在一个小问题。如果此 button
的 text
设置为“”,则不会显示图像。这个问题可以通过使用 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
使用相应的主题。为了使主题生效,您需要将 button
的 flatstyle
设置为 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);
}
您将得到
您还可以使用相同的方法在 CommandLink
的 Button
上显示图标/图像
拆分按钮(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)
默认情况下,TreeView
和 ListView
的外观与 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 文档中找不到。当然,我们不得不使用旧版本的控件(如 MainMenu
和 ContextMenu
)才能充分发挥新 Windows Vista 图形用户界面的潜力,这有点不寻常。
注意事项
我尚未在 Visual Studio 2003 上测试上述代码,也尚未在 Windows XP 上运行该应用程序(我“尚未”使其向后兼容)。
演示中一些可能无法在旧版 Windows 上运行的自定义控件包括 CommandLink
、SplitButton
和 ProgressBar
。
它已在 Visual Studio 2005 和 Windows Vista 中进行测试。
参考文献
- Application.EnableVisualStyles
- MSDN - 控件信息(使用左侧导航窗格浏览)
历史
- 版本 1.10:2007年6月17日 - 添加/修改了多个控件
- 版本 1.00:2007年5月20日 - 初始版本