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

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2012 年 4 月 13 日

CPOL

4分钟阅读

viewsIcon

67544

downloadIcon

3838

Nick Thissen 在 VBForums 的文章的 C# 版本,封装成一个类库,你可以直接添加到你的项目中。

Click to enlarge image
图 1. 此渲染器实现效果的图示,与 Visual Studio 的对比截图。

引言

我正在开发一个类似 IDE 的软件项目,并一直在寻找用 C# 编写的 ToolStripRenderer 类,以便帮助我使我的 MenuStripContextMenuStripToolStrip 控件看起来更像 Visual Studio 的样式。

我没能找到 C# 版本,但谷歌搜索找到了 VBForums 上 Nick Thissen 的这篇精彩的 文章。不幸的是,它是用 VB.NET 写的。我的项目需要一个 C# 版本。

而且,我很懒,不想从工具箱中添加标准的 MenuStripContextMenuStripToolStrip 控件,然后手动进入代码为每个控件设置 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#。

控件

现在是你可以直接拖放到窗体上的控件,用以替代 ToolStripMenuStripToolStripContainerContextMenuStrip

这些控件编写起来非常简单。我们只需在类库项目中添加更多类,这些类都继承自上面列出的控件类。每个新类只实现构造函数,将每个控件的 Renderer 属性设置为 VS2008MenuRendererVS2008ToolStripRenderer 的新对象。

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. VS2008MenuStripVS2008ContextMenuStrip 控件的代码。

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 库的项目。

VS2008ToolStrip/VisualStudioToolStrip_formDesign.jpg
图 2. 向已渲染成 Visual Studio 风格的新窗体添加控件!

历史

  • 2010年4月1日:文章撰写并发布
© . All rights reserved.