![]() |
Desktop Development »
Menus »
Toolbars
Beginner
License: The GNU General Public License (GPL)
ToolStrip Custom RenderersBy Thomas StockwellAn article on creating custom renderers for a ToolStrip. |
C# 2.0, Windows, .NET 2.0VS2005, Architect, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||

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.
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.
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:
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:
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.
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.
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.
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.
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));
}
}
}
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.
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.
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.
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.
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.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.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.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.EnhancedRadioButton and EnhancedCheckbox by adding text color changing with ToolStripManager.ToolStripButtons.ToolStripPanel.ThemedTabControl
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.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.ThemedTabControl (not ToolStripEnhancedTabControl).VisualStyle enabled TabPages, remove TabPages, and switch whether the ThemedTabControl is themed or not.BaseRenderer properties and variables. There are more #region fields to make moving between variable and property groups easier.ToolStripGrip painting to include styles: dotted, double-line, line.ToolStripEnhanced controls have not changed.EnhancedComboBox now is ThemedComboBox.BitmapBar drawing when ToolStripDropDown owned by a ToolStripOverflowButton was drawn.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.ToolStipDropDown controls when the Renderer for the ToolStripManager changes from each BaseRenderer.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.EnhancedComboBox, EnhancedFontComboBox, EnhancedColorComboBox, EnhancedProgressBar, EnhancedRadioButton, and the EnhancedCheckBox have been added to support the events of their control.| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 25 Apr 2006 Editor: Smitha Vijayan |
Copyright 2006 by Thomas Stockwell Everything else Copyright © CodeProject, 1999-2009 Web17 | Advertise on the Code Project |