C# 中自定义 VisualStudio 2008 风格的 MenuStrip 和 ToolStrip 渲染器





5.00/5 (6投票s)
Nick Thissen 在 VBForums 的文章的 C# 版本,封装成一个类库,你可以直接添加到你的项目中。

引言
我正在开发一个类似 IDE 的软件项目,并一直在寻找用 C# 编写的 ToolStripRenderer
类,以便帮助我使我的 MenuStrip
、ContextMenuStrip
和 ToolStrip
控件看起来更像 Visual Studio 的样式。
我没能找到 C# 版本,但谷歌搜索找到了 VBForums 上 Nick Thissen 的这篇精彩的 文章。不幸的是,它是用 VB.NET 写的。我的项目需要一个 C# 版本。
而且,我很懒,不想从工具箱中添加标准的 MenuStrip
、ContextMenuStrip
和 ToolStrip
控件,然后手动进入代码为每个控件设置 Renderer
属性。随着你的项目中此类控件数量的增加,进入某个文件并调整代码会变得很麻烦。为什么不省点力气,让设计器来完成这项工作,通过从类库中添加自定义控件,并将它们直接拖放到窗体上,渲染器也一并集成?
这就是本文改编,并希望能改进 Thissen 先生文章的方式。
背景
本文将完成同样的事情,将 Thissen 先生提供的 VB.NET 代码翻译成等效的 C# 代码。我们将一步一步地指导你将此功能添加到你自己的程序中。
Using the Code
我希望能改进 Thissen 先生工作的第一个步骤是创建一个新的类库项目。我将类库项目命名为 VS2008StripRenderingLibrary
,并向其中添加了以下类。
VB 模块的 C# 版本
Thissen 先生在他的文章开头让我们构建一个 VB 模块,其中包含全局常量(如颜色等)。由于 C# 中没有 Modules
,我们最好的方法是创建一个包含大量 static
成员的类。第一个要添加的类是用于存储 Color
值的类。源代码如下:
using System.Drawing;
using System;
namespace VS2008StripRenderingLibrary {
public class clsColor {
public static Color clrHorBG_GrayBlue = Color.FromArgb(255, 233, 236, 250);
public static Color clrHorBG_White = Color.FromArgb(255, 244, 247, 252);
public static Color clrSubmenuBG = Color.FromArgb(255, 240, 240, 240);
public static Color clrImageMarginBlue = Color.FromArgb(255, 212, 216, 230);
public static Color clrImageMarginWhite = Color.FromArgb(255, 244, 247, 252);
public static Color clrImageMarginLine = Color.FromArgb(255, 160, 160, 180);
public static Color clrSelectedBG_Blue = Color.FromArgb(255, 186, 228, 246);
public static Color clrSelectedBG_Header_Blue =
Color.FromArgb(255, 146, 202, 230);
public static Color clrSelectedBG_White = Color.FromArgb(255, 241, 248, 251);
public static Color clrSelectedBG_Border = Color.FromArgb(255, 150, 217, 249);
public static Color clrSelectedBG_Drop_Blue = Color.FromArgb(255, 139, 195, 225);
public static Color clrSelectedBG_Drop_Border = Color.FromArgb(255, 48, 127, 177);
public static Color clrMenuBorder = Color.FromArgb(255, 160, 160, 160);
public static Color clrCheckBG = Color.FromArgb(255, 206, 237, 250);
public static Color clrVerBG_GrayBlue = Color.FromArgb(255, 196, 203, 219);
public static Color clrVerBG_White = Color.FromArgb(255, 250, 250, 253);
public static Color clrVerBG_Shadow = Color.FromArgb(255, 181, 190, 206);
public static Color clrToolstripBtnGrad_Blue = Color.FromArgb(255, 129, 192, 224);
public static Color clrToolstripBtnGrad_White =
Color.FromArgb(255, 237, 248, 253);
public static Color clrToolstripBtn_Border = Color.FromArgb(255, 41, 153, 255);
public static Color clrToolstripBtnGrad_Blue_Pressed =
Color.FromArgb(255, 124, 177, 204);
public static Color clrToolstripBtnGrad_White_Pressed =
Color.FromArgb(255, 228, 245, 252);
public static void DrawRoundedRectangle(Graphics g, int x , int y ,
int width, int height, int m_diameter , Color color ) {
using (Pen pen = new Pen(color)) {
//Dim g As Graphics
var BaseRect = new RectangleF(x, y, width, height);
var ArcRect = new RectangleF(BaseRect.Location,
new SizeF(m_diameter, m_diameter));
//top left Arc
g.DrawArc(pen, ArcRect, 180, 90);
g.DrawLine(pen, x + Convert.ToInt32(m_diameter / 2),
y, x + width - Convert.ToInt32(m_diameter / 2), y);
// top right arc
ArcRect.X = BaseRect.Right - m_diameter;
g.DrawArc(pen, ArcRect, 270, 90);
g.DrawLine(pen, x + width, y + Convert.ToInt32(m_diameter / 2),
x + width, y + height - Convert.ToInt32(m_diameter / 2));
// bottom right arc
ArcRect.Y = BaseRect.Bottom - m_diameter;
g.DrawArc(pen, ArcRect, 0, 90);
g.DrawLine(pen, x + Convert.ToInt32(m_diameter / 2),
y + height, x + width - Convert.ToInt32(m_diameter / 2), y + height);
// bottom left arc
ArcRect.X = BaseRect.Left;
g.DrawArc(pen, ArcRect, 90, 90);
g.DrawLine(pen, x, y + Convert.ToInt32(m_diameter / 2),
x, y + height - Convert.ToInt32(m_diameter / 2));
}
}
}
}
列表 1. clsolor
类,它存储 Color
常量,并提供一个用于渲染圆角矩形的静态函数。这与 Nick Thissen 的代码完全相同;我只是使用了查找和替换将其转换为有效的 C#。另外请注意,我没有使用 VB 的 Module
,而是创建了一个带有静态变量的类。C# 不支持 Module
。
渲染菜单的代码
接下来要添加的类是我称之为 VS2008MenuRenderer
的类,定义如下:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace VS2008StripRenderingLibrary {
public class VS2008MenuRenderer : ToolStripRenderer {
// Make sure the textcolor is black
protected override void InitializeItem(ToolStripItem item) {
base.InitializeItem(item);
item.ForeColor = Color.Black;
}
protected override void Initialize(ToolStrip toolStrip) {
base.Initialize(toolStrip);
toolStrip.ForeColor = Color.Black;
}
// Render horizontal background gradient
protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) {
base.OnRenderToolStripBackground(e);
LinearGradientBrush b = new LinearGradientBrush(e.AffectedBounds,
clsColor.clrHorBG_GrayBlue, clsColor.clrHorBG_White,
LinearGradientMode.Horizontal);
e.Graphics.FillRectangle(b, e.AffectedBounds);
}
// Render image margin and gray ItemBackground
protected override void OnRenderImageMargin(ToolStripRenderEventArgs e) {
base.OnRenderImageMargin(e);
// Draw ImageMargin background gradient
LinearGradientBrush b = new LinearGradientBrush
(e.AffectedBounds, clsColor.clrImageMarginWhite,
clsColor.clrImageMarginBlue, LinearGradientMode.Horizontal);
// Shadow at the right of image margin
var DarkLine = new SolidBrush(clsColor.clrImageMarginLine);
var WhiteLine = new SolidBrush(Color.White);
var rect = new Rectangle(e.AffectedBounds.Width,
2, 1, e.AffectedBounds.Height);
var rect2 = new Rectangle(e.AffectedBounds.Width + 1,
2, 1, e.AffectedBounds.Height);
// Gray background
var SubmenuBGbrush = new SolidBrush(clsColor.clrSubmenuBG);
var rect3 = new Rectangle(0, 0, e.ToolStrip.Width, e.ToolStrip.Height);
// Border
var borderPen = new Pen(clsColor.clrMenuBorder);
var rect4 = new Rectangle
(0, 1, e.ToolStrip.Width - 1, e.ToolStrip.Height - 2);
e.Graphics.FillRectangle(SubmenuBGbrush, rect3);
e.Graphics.FillRectangle(b, e.AffectedBounds);
e.Graphics.FillRectangle(DarkLine, rect);
e.Graphics.FillRectangle(WhiteLine, rect2);
e.Graphics.DrawRectangle(borderPen, rect4);
}
// Render checkmark
protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) {
base.OnRenderItemCheck(e);
if (e.Item.Selected) {
var rect = new Rectangle(3, 1, 20, 20);
var rect2 = new Rectangle(4, 2, 18, 18);
SolidBrush b = new SolidBrush(clsColor.clrToolstripBtn_Border);
SolidBrush b2 = new SolidBrush(clsColor.clrCheckBG);
e.Graphics.FillRectangle(b, rect);
e.Graphics.FillRectangle(b2, rect2);
e.Graphics.DrawImage(e.Image, new Point(5, 3));
} else {
var rect = new Rectangle(3, 1, 20, 20);
var rect2 = new Rectangle(4, 2, 18, 18);
SolidBrush b = new SolidBrush(clsColor.clrSelectedBG_Drop_Border);
SolidBrush b2 = new SolidBrush(clsColor.clrCheckBG);
e.Graphics.FillRectangle(b, rect);
e.Graphics.FillRectangle(b2, rect2);
e.Graphics.DrawImage(e.Image, new Point(5, 3));
}
}
// Render separator
protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) {
base.OnRenderSeparator(e);
var DarkLine = new SolidBrush(clsColor.clrImageMarginLine);
var WhiteLine = new SolidBrush(Color.White);
var rect = new Rectangle(32, 3, e.Item.Width - 32, 1);
e.Graphics.FillRectangle(DarkLine, rect);
e.Graphics.FillRectangle(WhiteLine, rect);
}
// Render arrow
protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) {
e.ArrowColor = Color.Black;
base.OnRenderArrow(e);
}
// Render MenuItem background: lightblue if selected, darkblue if dropped down
protected override void OnRenderMenuItemBackground
(ToolStripItemRenderEventArgs e) {
base.OnRenderMenuItemBackground(e);
if (e.Item.Enabled) {
if (e.Item.IsOnDropDown == false && e.Item.Selected) {
// If item is MenuHeader and selected: draw darkblue color
var rect = new Rectangle(3, 2, e.Item.Width - 6, e.Item.Height - 4);
using (var b = new LinearGradientBrush
(rect, clsColor.clrSelectedBG_White,
clsColor.clrSelectedBG_Header_Blue,
LinearGradientMode.Vertical)) {
using (var b2 = new SolidBrush(clsColor.clrCheckBG)) {
e.Graphics.FillRectangle(b, rect);
clsColor.DrawRoundedRectangle(e.Graphics, rect.Left - 1,
rect.Top - 1, rect.Width, rect.Height + 1, 4,
clsColor.clrToolstripBtn_Border);
clsColor.DrawRoundedRectangle(e.Graphics, rect.Left - 2,
rect.Top - 2, rect.Width + 2, rect.Height + 3, 4,
Color.White);
e.Item.ForeColor = Color.Black;
}
}
} else if (e.Item.IsOnDropDown && e.Item.Selected) {
// If item is NOT menuheader (but subitem);
// and selected: draw lightblue border
var rect = new Rectangle(4, 2, e.Item.Width - 6, e.Item.Height - 4);
using (var b = new LinearGradientBrush
(rect, clsColor.clrSelectedBG_White,
clsColor.clrSelectedBG_Blue, LinearGradientMode.Vertical)) {
using (var b2 = new SolidBrush(clsColor.clrSelectedBG_Border)) {
e.Graphics.FillRectangle(b, rect);
clsColor.DrawRoundedRectangle(e.Graphics,
rect.Left - 1, rect.Top - 1, rect.Width,
rect.Height + 1, 6, clsColor.clrSelectedBG_Border);
e.Item.ForeColor = Color.Black;
}
}
}
// If item is MenuHeader and menu is dropped down;
// selection rectangle is now darker
if ((e.Item as ToolStripMenuItem).DropDown.Visible &&
e.Item.IsOnDropDown == false) {
// (e.Item as ToolStripMenuItem).OwnerItem == null
var rect = new Rectangle(3, 2, e.Item.Width - 6, e.Item.Height - 4);
using (var b = new LinearGradientBrush
(rect, Color.White, clsColor.clrSelectedBG_Drop_Blue,
LinearGradientMode.Vertical)) {
using (var b2 = new SolidBrush
(clsColor.clrSelectedBG_Drop_Border)) {
e.Graphics.FillRectangle(b, rect);
clsColor.DrawRoundedRectangle(
e.Graphics, rect.Left - 1, rect.Top - 1,
rect.Width, rect.Height + 1,
4, clsColor.clrSelectedBG_Drop_Border);
clsColor.DrawRoundedRectangle(
e.Graphics, rect.Left - 2, rect.Top - 2,
rect.Width + 2, rect.Height + 3, 4,
Color.White);
e.Item.ForeColor = Color.Black;
}
}
}
}
}
}
}
列表 2. VS2008MenuStripRenderer
类的代码,该类负责使 MenuStrip
看起来像 Visual Studio 2008。
渲染 ToolStrip 的代码
VS2008ToolStripRenderer
类的代码如下所示。与 VS2008MenuStripRenderer
代码一样,这段代码也是直接从 Nick Thissen 的作品改编而来,只是我使用了查找和替换将他的 VB.NET 代码转换为有效的 C# 代码。
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace VS2008StripRenderingLibrary {
public class VS2008ToolStripRenderer : ToolStripProfessionalRenderer {
// Render custom background gradient
protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) {
base.OnRenderToolStripBackground(e);
using (var b = new LinearGradientBrush(e.AffectedBounds,
clsColor.clrVerBG_White, clsColor.clrVerBG_GrayBlue,
LinearGradientMode.Vertical)) {
using (var shadow = new SolidBrush(clsColor.clrVerBG_Shadow)) {
var rect = new Rectangle
(0, e.ToolStrip.Height - 2, e.ToolStrip.Width, 1);
e.Graphics.FillRectangle(b, e.AffectedBounds);
e.Graphics.FillRectangle(shadow, rect);
}
}
}
// Render button selected and pressed state
protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) {
base.OnRenderButtonBackground(e);
var rectBorder = new Rectangle(0, 0, e.Item.Width - 1, e.Item.Height - 1);
var rect = new Rectangle(1, 1, e.Item.Width - 2, e.Item.Height - 2);
if (e.Item.Selected == true || (e.Item as ToolStripButton).Checked) {
using (var b = new LinearGradientBrush
(rect, clsColor.clrToolstripBtnGrad_White,
clsColor.clrToolstripBtnGrad_Blue, LinearGradientMode.Vertical)) {
using (var b2 = new SolidBrush(clsColor.clrToolstripBtn_Border)) {
e.Graphics.FillRectangle(b2, rectBorder);
e.Graphics.FillRectangle(b, rect);
}
}
}
if (e.Item.Pressed) {
using (var b = new LinearGradientBrush
(rect, clsColor.clrToolstripBtnGrad_White_Pressed,
clsColor.clrToolstripBtnGrad_Blue_Pressed,
LinearGradientMode.Vertical)) {
using (var b2 = new SolidBrush(clsColor.clrToolstripBtn_Border)) {
e.Graphics.FillRectangle(b2, rectBorder);
e.Graphics.FillRectangle(b, rect);
}
}
}
}
}
}
列表 3. VS2008ToolStripRenderer
类的代码,同样是直接从 Nick Thissen 的 VB.NET 代码改编而来,将其翻译成有效的 C#。
控件
现在是你可以直接拖放到窗体上的控件,用以替代 ToolStrip
、MenuStrip
、ToolStripContainer
或 ContextMenuStrip
。
这些控件编写起来非常简单。我们只需在类库项目中添加更多类,这些类都继承自上面列出的控件类。每个新类只实现构造函数,将每个控件的 Renderer
属性设置为 VS2008MenuRenderer
或 VS2008ToolStripRenderer
的新对象。
VS2008MenuStrip 和 VS2008ContextMenuStrip 控件
VS2008MenuStrip
控件的代码如下:
using System.Windows.Forms;
namespace VS2008StripRenderingLibrary {
public class VS2008MenuStrip : MenuStrip {
public VS2008MenuStrip() {
this.Renderer = new VS2008MenuRenderer();
}
}
public class VS2008ContextMenuStrip : ContextMenuStrip {
public VS2008ContextMenuStrip() {
this.Renderer = new VS2008MenuRenderer();
}
}
}
列表 4. VS2008MenuStrip
和 VS2008ContextMenuStrip
控件的代码。
VS2008ToolStrip 控件
VS2008ToolStrip
控件的代码如下所示:
using System.Windows.Forms;
namespace VS2008StripRenderingLibrary {
public class VS2008ToolStrip : ToolStrip {
public VS2008ToolStrip() {
this.Renderer = new VS2008ToolStripRenderer();
}
}
}
列表 5. VS2008ToolStrip
控件的代码。
最后,当我们需要使用 ToolStripContainer
并希望保持渲染一致性时,我们会需要一个 ToolStripContainer
控件。
VS2008ToolStripContainer 控件
VS2008ToolStripContainer
控件的编写方式如下:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace VS2008StripRenderingLibrary {
public class VS2008ToolStripContainer : ToolStripContainer {
public VS2008ToolStripContainer() {
this.TopToolStripPanel.Paint +=
new PaintEventHandler(TopToolStripPanel_Paint);
this.TopToolStripPanel.SizeChanged +=
new EventHandler(TopToolStripPanel_SizeChanged);
}
void TopToolStripPanel_SizeChanged(object sender, EventArgs e) {
this.Invalidate();
}
void TopToolStripPanel_Paint(object sender, PaintEventArgs e) {
Graphics g = e.Graphics;
var rect = new Rectangle(0, 0, this.Width, this.FindForm().Height);
using (LinearGradientBrush b = new LinearGradientBrush(
rect, clsColor.clrHorBG_GrayBlue,
clsColor.clrHorBG_White, LinearGradientMode.Horizontal)) {
g.FillRectangle(b, rect);
}
}
}
}
列表 6. VS2008ToolStripContainer
控件的代码。
关注点
将控件添加到新项目
使用 VS2008StripRenderingLibrary
的优势可以在本文附带的演示项目中看到。通过让设计器为您完成工作,您可以节省大量的编码时间。
下图展示了如何创建一个包含一个新空白窗体并已引用 VS2008StripRenderingLibrary
库的项目。

历史
- 2010年4月1日:文章撰写并发布