Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Custom VisualStudio 2008-style MenuStrip and ToolStrip Renderer in C#

0.00/5 (No votes)
1 Apr 2010 1  
An adaptation of Nick Thissen's article on VBForums translated to C# and bottled up into a Class Library you can just drop into your projects.
Click to enlarge image
Figure 1. Illustration of what this Renderer implementation does, with a comparison screenshot of Visual Studio.

Introduction

I was developing an IDE-like software project and was looking around for a ToolStripRenderer class written in C# which would aid me in making my MenuStrip, ContextMenuStrip, and ToolStrip controls look more like Visual Studio's variety.

I had no such luck in finding versions written in C# but a Google search did come across this excellent article on VBForums by Nick Thissen. Unfortunately, it was done in VB.NET. For my project, I needed a C# version.

Plus, I was being lazy and didn't want to add the standard MenuStrip, ContextMenuStrip, and ToolStrip controls from the Toolbox and then have to go in by hand into the code and set the Renderer property on each. As you increase the number of such controls in your project, going into some file somewhere and tweaking code gets cumbersome. Why not save trouble and have the Designer do it, by adding custom controls from a library and just dropping them onto the form, renderer and all?

This is how this article adapts, and hopefully improves upon, Mr. Thissen's article.

Background

This article does the same thing, by translating the VB.NET code provided by Mr. Thissen into the equivalent C#. We will guide you step by step through adding this capability to your own programs.

Using the Code

My first step in hopefully improving upon Mr. Thissen's work was to first start by creating a new Class Library project. I named the Class Library project the VS2008StripRenderingLibrary and added to it the following classes.

The C# Version of a Module

Mr. Thissen starts his article out having us build a Module in VB which holds global constants for colors, etc. Since we do not have access to Modules in C#, the best we can do is a Class full of a bunch of static members. The first class to add is a class to hold Color values. The source code is thus:

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));
            }
        }
    }
}

Listing 1. The clsolor class, which stores Color constants and also provides a static function for rendering rounded rectangles. This is exactly as Nick Thissen's code; I just used Find and Replace to make it into valid C#. Notice also, instead of using a VB Module, I instead had to make a class with static variables. C# does not support Modules.

Code to Render Menus

The next class to add was a class I called VS2008MenuRenderer, which is defined as follows:

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;
                        }
                    }
                }
            }
        }
    }
}

Listing 2. Code for the VS2008MenuStripRenderer class, which is in charge of making MenuStrips look like Visual Studio 2008.

Code to Render a ToolStrip

The code for the VS2008ToolStripRenderer class is shown below. Just like the VS2008MenuStripRenderer code, this code is also adapted straight from what Nick Thissen has written, except that I used Find and Replace to turn his VB.NET code into valid C# code.

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);
                    }
                }
            }
        }
    }
}

Listing 3. Code for the VS2008ToolStripRenderer class, which is again just adapted straight from Nick Thissen's VB.NET code, translating it into valid C#.

Controls

Now for the controls you can use to just drop onto your form in place of ToolStrip, MenuStrip, ToolStripContainer, or ContextMenuStrip.

The controls are too easy to write. All we do is add yet more classes to our Class Library project, derived from the control classes listed above. Each new class only implements the constructor, setting each control's Renderer property to a new object of either VS2008MenuRenderer or VS2008ToolStripRenderer.

The VS2008MenuStrip and VS2008ContextMenuStrip Controls

Code for the VS2008MenuStrip control is as follows:

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();
        }
    }
}

Listing 4. Code for the VS2008MenuStrip and VS2008ContextMenuStrip controls.

The VS2008ToolStrip Control

Code for the VS2008ToolStrip control is as given below:

using System.Windows.Forms;

namespace VS2008StripRenderingLibrary {
    public class VS2008ToolStrip : ToolStrip {
        public VS2008ToolStrip() {
            this.Renderer = new VS2008ToolStripRenderer();
        }
    }
}

Listing 5. Code for the VS2008ToolStrip control.

Finally, we will want a ToolStripContainer control to use for when we want to use a ToolStripContainer and want to have the rendering be consistent.

The VS2008ToolStripContainer Control

The VS2008ToolStripContainer control is written as given below:

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);
            }
        }
    }
}

Listing 6. Code for the VS2008ToolStripContainer control.

Points of Interest

Adding The Controls to A New Project

The benefits of using the VS2008StripRenderingLibrary can be seen in the demo project included with this article. You can save yourself a lot of coding by letting the Designer do the work for you.

The figure below illustrates creating a project with a new, blank Form and a reference to the VS2008StripRenderingLibrary library:

VS2008ToolStrip/VisualStudioToolStrip_formDesign.jpg
Figure 2. Adding controls to a new form already rendered like Visual Studio!

History

  • 1st April, 2010: Article written and posted

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here