Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C#
Article

ToolStrip Custom Renderers

Rate me:
Please Sign up or sign in to vote.
4.32/5 (23 votes)
25 Apr 2006GPL311 min read 184.4K   4.3K   76   19
An article on creating custom renderers for a ToolStrip.

Sample image

Table of Contents

  1. Introduction
  2. Background
  3. BaseRenderer
  4. BaseRenderer Inheritance
  5. Themed Controls
  6. Using BaseRenderer Inherited Classes in Your Application
  7. Conclusion
  8. Notes
  9. History

Introduction

A new introduction to the .NET framework is the ToolStrip control. This control allows a programmer to make amazing user interfaces for their applications by giving applications docking menus, toolbars, and many other features that were not implemented in the .NET Framework 1.1. This article will focus on the use of the ToolStripManager to dynamically change the appearance of a ToolStrip control in a matter of seconds.

Background

I wrote this article and code to further explain and assist in the encapsulation of a ToolStripRenderer control. As well as telling you how you can encapsulate a ToolStripRenderer control, I will explain an abstract 'BaseRenderer' ToolStripRenderer control that I have included in the code for this article. This 'BaseRenderer' encapsulates all the paint routines of a ToolStripRenderer control, and separates the painting into easier to manage, individually overridable paint events.

This is a further enhancement of my first article to The Code Project, entitled Transparent Menu and XP System Title Buttons. I say this is a further enhancement on that article because I have also implemented [optional] transparent menus in the form of ToolStripDropDown controls.

BaseRenderer

The BaseRenderer class, as stated previously, is an abstract inherited ToolStripRenderer control. This allows all of the paint routines for the ToolStripRenderer control to be customized or overridden. The idea of this control is simple:

  1. Allow customization by editing properties that range from changing the color of a specific control to changing the appearance of a specific control
  2. Allow individual portions of a control to be painted in many overridable functions, to allow easier customization

First of all, what I did to this class was I used Visual Studio .NET 2005's enhanced intellisense to override all of the methods for the painting and initialization procedures. The following is an example of what all of the functions looked liked, please note that I included all of the IDE generated base methods:

C#
protected override void OnRenderItemBackground(ToolStripItemRenderEventArgs e) 
{ 
      base.OnRenderItemBackground(e); 
}

Second, as you can see below, for the OnRenderMenuItemBackground method, I have wrote the logic for how a ToolStripMenuItem should be drawn to the screen. For this method, I have determined whether the item is on a ToolStripDropDown control or not, and drawing will change accordingly. Also, keep in mind that each method with the DrawMenu prefix used in the snippet breaks down the drawing procedures for this particular method.

C#
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
{
     //Menus
     if (e.Item.IsOnDropDown)
     {
         //Transparency code has been removed from this snippet 
         //Menu Transparency will be spoken about later in the article

          DrawMenuDropDownItemBackground(e);
          DrawMenuDropDownItemBitmapBar(e);
          if (e.Item.Selected == true && e.Item.Enabled)
             DrawMenuDropDownItemHighlight(e);
      }
      else
      {
           ToolStripMenuItem tsm = (ToolStripMenuItem)e.Item;
           MenuDropDownDirection md = MenuDropDownDirection.top;
           if (tsm.DropDownDirection == ToolStripDropDownDirection.AboveLeft || 
               tsm.DropDownDirection == ToolStripDropDownDirection.AboveRight)
              md = MenuDropDownDirection.bottom;
           else if (tsm.DropDownDirection == ToolStripDropDownDirection.BelowLeft || 
                    tsm.DropDownDirection == ToolStripDropDownDirection.BelowRight)
              md = MenuDropDownDirection.top;
           else if (tsm.DropDownDirection == ToolStripDropDownDirection.Right)
              md = MenuDropDownDirection.left;
           else if (tsm.DropDownDirection == ToolStripDropDownDirection.Left)
              md = MenuDropDownDirection.right;
           if(e.Item.Enabled && (e.Item.Selected || e.Item.Pressed))
                    DrawMenuBarItemHighlight(e,md);
      }
}

Third, I wrote the actual drawing routines. While writing these drawing routines, I kept in mind that all of these drawing routines will be overridable, so any inherited classes of the BaseRenderer could easily customize a specific drawing routine. With that in mind, I will show you the drawing routine for the DrawMenuDropDownItemHighlight method.

C#
public virtual void DrawMenuDropDownItemHighlight(ToolStripItemRenderEventArgs e)
{
   Rectangle HRect = new Rectangle();

   switch (mddHighlightType)
      {
        case MenuHighlightType.NoColumn:
            HRect = new Rectangle(mddColumnWidth, 1, 
                    (int)e.Graphics.VisibleClipBounds.Width - 
                    mddColumnWidth-1, (int)e.Graphics.VisibleClipBounds.Height - 2);
            break;
        case MenuHighlightType.WithColumn:
            HRect = new Rectangle(1, 1, (int)e.Graphics.VisibleClipBounds.Width-3, 
                   (int)e.Graphics.VisibleClipBounds.Height-2);
            break;
       }
   LinearGradientBrush lgb = new LinearGradientBrush(HRect, mddHColor1, 
                                                     mddHColor2, mddColorAngle);
   e.Graphics.FillRectangle(lgb, HRect);
   e.Graphics.DrawRectangle(new Pen(mddHBorderColor), HRect);
}

With this example, you can see that HRect is a rectangle variable that contains the bounds of the ToolStripItem that is to be rendered in this method. HRect is altered depending on the BaseRender variable, mddHighlightType, which is an enum with the values of either NoColumn or WithColumn. Depending on which value the variable is set to, the highlight for the control will or will not include the highlight over the BitmapBar.

Now that I have explained how I have structured the basics of this ToolStripRenderer class, I will explain the variables and properties for the control. For the variables of this class, I have set guidelines for how they are to be named. First is the prefix of each variable. For each drawing segment of code, the variables used have a specific prefix. For example, the prefix of 'mdd' is short for menudropdown, 'm' is short for menu, 'pb' is short for progress bar, 'cb' is short for combobox, and 'uni' is short for universal or what is used to paint the odds and ends of the ToolStrips. In telling you how the variables are named, I hope to make the code easier to read and understand. Properties for the BaseRenderer are pretty straightforward, and for every variable in the BaseRenderer class, there is a property to alter the variable.

BaseRenderer Inheritance

With the BaseRenderer ToolStripRenderer class, I have included six classes that inherit the BaseRenderer class. A majority of the classes that begin with the prefix 'Color' are basically inheriting the BaseRenderer class and then editing its properties to develop a theme. The other two classes, one called Blood and the other called CustomPicture, are classes that further customize the menus by overriding paint events. The following example shows an overridden DrawUniversalBackground method. The two Bitmap variables, 'bloodvert' and 'bloodhor', are two different JPG files, with one that is more wide than tall, and the other more tall than wide, both with dripping blood on the sides. These two bitmaps will be drawn on the ToolStrips depending on the ToolStrip dimensions. The code below the drawing of the bitmaps is a fix to a problem that I was having for a while. The problem was that the BitmapBar on the left side of the menu was not painting on ToolStripSeparators or ToolStripControlHosts. So, to fix this problem, I loop through all of the ToolStripItems on a ToolStrip, and if I find a control of type ToolStripSeparator or ToolStripControlHost, I manually draw the BitmapBar on the left side of the menu.

Disclaimer: I included the Blood class not to gross out any of the readers of this article, but in creating this control, I thought it would look cool. Second, the Blood class is a perfect example of overriding paint events in the BaseRenderer.

C#
public override void DrawUniversalBackground(ToolStripRenderEventArgs e)
{
   Bitmap use;
   if (e.AffectedBounds.Width < e.AffectedBounds.Height)
       use = bloodvert;
   else
       use = bloodhor;
   if(use==bloodhor)
       e.Graphics.DrawImage(use, 0, 0, Misc.Iff(use.Width < e.AffectedBounds.Width,  
                            e.AffectedBounds.Width,use.Width), 
                            Misc.Iff(use.Height < e.AffectedBounds.Height, 
                            use.Height, e.AffectedBounds.Height));
   else
       e.Graphics.DrawImage(use, 0, 0, Misc.Iff(use.Width < e.AffectedBounds.Width, 
                            use.Width , e.AffectedBounds.Width), 
                            Misc.Iff(use.Height < e.AffectedBounds.Height, 
                            use.Height, e.AffectedBounds.Height));
   if (e.ToolStrip.IsDropDown)
     {
       foreach(ToolStripItem tsi in e.ToolStrip.Items)
           {
             if (tsi.GetType() == typeof(ToolStripSeparator)||
                 tsi.GetType().BaseType == typeof(ToolStripControlHost))
               base.DrawMenuDropDownItemBitmapBar(new 
                       ToolStripItemRenderEventArgs(e.Graphics, tsi));
           }
     }
}

Themed Controls

The code attached to this article contains many 'themed' controls. These are controls that are owner drawn, and expose their drawing events to the BaseRenderer. These are unique controls in that whenever the protected WndProc function is called and the control's logic says that the control should be painted, an event is fired. Depending on the event that is fired, the themed control will attach a method to the event to custom-paint the control. These functions that paint the themed controls are in the BaseRenderer class, and are prefixed with 'Steal'. These paint methods, as with the rest of the methods in the BaseRenderer, are virtual, allowing the methods to be overridden with custom paint routines depending on the inherited BaseRenderer.

Along with the actual themed controls, I have included a few ToolStripControlHost controls that encapsulate all the themed controls individually. This will allow all of the themed controls to be put into a ToolStrip control. These controls are prefixed with 'ToolStripEnhanced'. To add one of the ToolStripEnhanced controls to your application, all you need to do is include the namespace for this project in to your application's references. This will allow the Visual Studio .NET designer to add the ToolStripEnhanced controls to the available controls that can be put on to a ToolStrip in the Windows Forms Designer.

Using BaseRenderer Inherited Classes in Your Application

To make using these controls ever more easier, I have created a component class that encapsulates all the BaseRenderer inherited classes and gives you an easy way to change the Renderer used for all ToolStrips and ToolStripEnhanced controls instantly. The component class that I have created allows you to switch from all of the Renderers available including: ToolStripProfessionalRenderer, ToolStripSystemRenderer, and the inherited BaseRenderer classes. To show you how this control changes the Renderer during both design-time and runtime, I should better show you the property for CustomRenderer.

C#
public Renderer CustomRender
{
   get { return rndr;}
   set 
       { 
          rndr=value;
          switch (rndr)
          {
              case Renderer.ColorDarkness:
                  ToolStripManager.Renderer = new ColorDarkness();
                  break;
              case Renderer.ColorHalloween:
                  ToolStripManager.Renderer = new ColorHalloween();
                  break;
              case Renderer.ColorIce:
                  ToolStripManager.Renderer = new ColorIce();
                  break;
              case Renderer.CustomPicture:
                  if (img != null)
                     ToolStripManager.Renderer = new CustomPicture(img);
                  else
                  {
                     if(this.DesignMode)
                         MessageBox.Show("Custom Image must be specified before" + 
                                          " you can choose CustomPicture", 
                                          "Error", MessageBoxButtons.OK, 
                                          MessageBoxIcon.Error);
                         CustomRender = Renderer.ColorDarkness;
                   }
                   break;
               case Renderer.Blood:
                   ToolStripManager.Renderer = new Blood();
                   break;
               case Renderer.Professional:
                   ToolStripManager.Renderer = 
                     (ToolStripRenderer)new ToolStripProfessionalRenderer();
                   break;
               case Renderer.System:
                   ToolStripManager.Renderer = 
                     (ToolStripRenderer)new ToolStripSystemRenderer();
                   break;
               case Renderer.ColorBlood:
                   ToolStripManager.Renderer = new ColorBlood();
                   break;
                }
       if (ToolStripManager.Renderer.GetType().BaseType == typeof(BaseRenderer))
       {
           BaseRenderer br = (BaseRenderer)ToolStripManager.Renderer;
           br.Menu_Transparency = transparency;
       }
   }
}

As you can see, for this property, I have a normal get statement, but I have a more complicated set statement. In the switch statement, you can see that I am using an enum called 'Renderer' which has a list of all of the Renderers that I have created or are built into the System.Windows.Forms namespace. As you can see, for the CustomPicture inherited BaseRenderer, I check to make sure that a bitmap is set for the CustomPicture property of the component; if there is no bitmap specified, then the CustomRenderer property is set to the default ColorDarkness.

Another piece of code at the bottom of this property sets the transparency for the drop down menus of a BaseRenderer. I will not go into detail, but basically, what I do to accomplish the transparent menus is I get the handle of the dropdown menu, then I use SetWindowLong along with transparency settings to alter the behavior of the dropdown menu window.

Conclusion

Now, I know this is a lot of information to go through, but I do hope this explanation is enough to help you, the readers, to further customize the ToolStrip controls.

Notes

  1. An important note that I would like to mention is that when you include a CustomRenderer control in your application, you can change the ToolStripManager.Renderer property while you are designing a form in the IDE. This allows you to see what the user of the program will see.
  2. I have configured the CustomRenderer.CustomRenderer property so that you can edit a BaseRenderer inherited class' properties. This allows you to change the color property of a particular theme, or change an image, and so on. However, if you plan on developing a completely new theme, I would not suggest this method. To allow a new theme to be used over and over again, I would rather create a new class that inherits the BaseRenderer and edit the properties through that class.
  3. When you include the CustomRenderer control in your application, you will now also see a few new controls in the ToolStripItems menu for a ToolStrip. The controls include the ToolStripEnhancedComboBox and ToolStripEnhancedProgressBar. These controls can act exactly like the System.Windows.Forms counterparts if you set their IsRendered property to false. IsRendered property refers to if BaseRenderer class should draw those controls. The System.Windows.Forms counterparts of these controls will not be rendered by a BaseRenderer.
  4. Any copyright notices on the code needs to remain on the code where-ever and when-ever it is used. All copyright notices that are included in the code are copyright the author of this article. This code is under the GPL (General Public License).
  5. The attached project is the entire folder and contents that I am using in editing this project for myself. I do hope there are no problems in building this project as there were in my first article.

History

  • February 6, 2006 - Added/Revised/Fixed
    • Added the IThemed interface which is added to all 'Themed...' controls. This interface makes sure that all controls with the Themed prefix have the ability to not be rendered by the ToolStripManager.
    • Added theme support for the EnhancedRadioButton and EnhancedCheckbox by adding text color changing with ToolStripManager.
    • Added painting for overflow button.
    • Added rollover effects for ToolStripButtons.
    • Added paint events for the ToolStripPanel.
    • Added ThemedTabControl
      • Tab pages are serialized with the initialization code of the form.
      • As of right now, only the tab portion of the tab page is themed.
      • When using the ToolStripEnhancedTabControl, please note that you have to programmatically alter the tab pages. You will not be able to add controls to the ToolStripEnhancedTabControl through the Windows Forms Designer.
        • Please note: The ToolStripEnhancedTabControl is quite large and it may not be proper to put it into a ToolStrip. This may clutter the GUI of your application, making your application not user-friendly.
      • Smart Tags have been added to the ThemedTabControl (not ToolStripEnhancedTabControl).
        • Allows you to dynamically add VisualStyle enabled TabPages, remove TabPages, and switch whether the ThemedTabControl is themed or not.
    • Revised layout of BaseRenderer properties and variables. There are more #region fields to make moving between variable and property groups easier.
    • Revised copyright notice to make it more understandable. The code is now copyright by this article's author, and distributed under the GPL license.
    • Revised ToolStripGrip painting to include styles: dotted, double-line, line.
    • Revised naming of Enhanced controls. ToolStripEnhanced controls have not changed.
      • Renamed the Enhanced... Controls to Themed... controls. E.g., EnhancedComboBox now is ThemedComboBox.
    • Fixed file names that were mixed up (ToolStripEnhancedColorComboBox.cs and TooLStripEnhancedComboBox.cs got mixed up).
    • Fixed BitmapBar drawing when ToolStripDropDown owned by a ToolStripOverflowButton was drawn.
    • Known and not yet fixed problems:
      • In a form that has the ThemedColorCombo, the designer adds unnecessary excess colors in an Items.AddRange method. This may result in thousands of extra lines of code that are not needed and time consuming to delete.
      • There is temporary flickering of ToolStipDropDown controls when the Renderer for the ToolStripManager changes from each BaseRenderer.
  • February 3, 2006 - Edited article
    • Rewrote the section on 'Enhanced ToolStrip Controls' to make it more understandable, and to reflect what the next code update will include. Section is now renamed to 'Themed Controls'.
  • January 26, 2006 - Fixed
    • Restructured the solution for the code so that it better meets The Code Project standards.
  • January 22, 2006 - Revised
    • EnhancedFontComboBox added. Inherited from EnhancedComboBox.
    • EnhancedColorComboBox added. Inherited from EnhancedComboBox.
    • EnhancedComboBox and EnhancedProgressBar painting has been re-handled. Instead of the BaseRenderer adding an event handler for paint routines, the actual EnhancedComboBox or EnhancedProgressBar will actually call the paint routine from the ToolStripManager.Renderer property. This fixes a problem when you try to put either of these controls directly on a form or in a StatusStrip control.
    • EnhancedRadioButton added. No painting is handled by the BaseRenderer class, control has transparent background to support ToolStripManager renderers.
    • EnhancedCheckBox added. No painting is handled by the BaseRenderer class, control has transparent background to support ToolStripManager renderers.
    • Events for EnhancedComboBox, EnhancedFontComboBox, EnhancedColorComboBox, EnhancedProgressBar, EnhancedRadioButton, and the EnhancedCheckBox have been added to support the events of their control.
  • January 21, 2006 - Initial Version

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer University of Michigan-Flint
United States United States
I am currently a Database Administrator & Developer for the International Center at the University of Michigan. My expertise is GUI design with WinForms and WPF.

Comments and Discussions

 
GeneralMy vote of 5 Pin
clauziuz1-Sep-11 14:38
clauziuz1-Sep-11 14:38 
QuestionHow to change a particular property... Pin
secovel12-May-06 5:24
secovel12-May-06 5:24 
AnswerRe: How to change a particular property... Pin
Thomas Stockwell14-May-06 10:37
professionalThomas Stockwell14-May-06 10:37 
GeneralDispose! Pin
Alex Mensky20-Apr-06 3:22
Alex Mensky20-Apr-06 3:22 
GeneralRe: Dispose! Pin
Thomas Stockwell21-Apr-06 12:06
professionalThomas Stockwell21-Apr-06 12:06 
GeneralRe: Dispose! Pin
Thomas Stockwell25-Apr-06 2:01
professionalThomas Stockwell25-Apr-06 2:01 
GeneralRe: Dispose! Pin
Alex Mensky29-Apr-06 20:54
Alex Mensky29-Apr-06 20:54 
GeneralVB.NET Pin
FundamentalDiscord16-Apr-06 14:20
FundamentalDiscord16-Apr-06 14:20 
GeneralRe: VB.NET Pin
Thomas Stockwell17-Apr-06 5:02
professionalThomas Stockwell17-Apr-06 5:02 
GeneralRe: VB.NET Pin
FundamentalDiscord18-Apr-06 4:30
FundamentalDiscord18-Apr-06 4:30 
GeneralRe: VB.NET Pin
Thomas Stockwell22-Apr-06 3:38
professionalThomas Stockwell22-Apr-06 3:38 
General.Net 2.0 ToolStrip Rebar Renderer Pin
Mike Chaliy21-Feb-06 1:52
Mike Chaliy21-Feb-06 1:52 
GeneralSome comments Pin
Alex Mensky23-Jan-06 4:18
Alex Mensky23-Jan-06 4:18 
GeneralRe: Some comments Pin
Thomas Stockwell25-Jan-06 3:36
professionalThomas Stockwell25-Jan-06 3:36 
GeneralRe: Some comments Pin
Thomas Stockwell26-Jan-06 4:58
professionalThomas Stockwell26-Jan-06 4:58 
GeneralHave sympathy on the poor Pin
fwsouthern21-Jan-06 19:02
fwsouthern21-Jan-06 19:02 
GeneralRe: Have sympathy on the poor Pin
Thomas Stockwell22-Jan-06 3:45
professionalThomas Stockwell22-Jan-06 3:45 
Sorry about how this article is formatted. I wrote this article using the template provided on the website and then posted the article directly to the site. I'll see if I can change the formatting so that it is smaller, but if I can't then hopefully it will be fixed when the article is edited by TheCodeProject.

**I have not figured out how to format the article to fix the problem, but I have made a comment about this for when the article is edited. Hopefully they will be able to fix this problem.

Regards,
1tg46

-- modified at 9:59 Sunday 22nd January, 2006
GeneralRe: Have sympathy on the poor Pin
aprenot23-Jan-06 6:39
aprenot23-Jan-06 6:39 
GeneralRe: Have sympathy on the poor Pin
Thomas Stockwell24-Jan-06 2:31
professionalThomas Stockwell24-Jan-06 2:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.