Table of Contents
- Introduction
- Background
- BaseRenderer
- BaseRenderer Inheritance
- Themed Controls
- Using BaseRenderer Inherited Classes in Your Application
- Conclusion
- Notes
- History
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:
- Allow customization by editing properties that range from changing the color of a specific control to changing the appearance of a specific control
- 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:
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)
{
if (e.Item.IsOnDropDown)
{
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 ToolStrip
s. 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 ToolStrip
s 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 ToolStripSeparator
s or ToolStripControlHost
s. So, to fix this problem, I loop through all of the ToolStripItem
s 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 ToolStrip
s and ToolStripEnhanced
controls instantly. The component class that I have created allows you to switch from all of the Renderer
s 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 Renderer
s 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.
- 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. - 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. - 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
. - 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).
- 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.
- 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
ToolStripButton
s. - 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 TabPage
s, remove TabPage
s, 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
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.