![]() |
Desktop Development »
Menus »
Menus and Toolbars
Intermediate
MozBarBy Patrik BohmanA flexible toolbar very much like the toolbar in FireFox Options dialog. |
C#, .NET, Win2K, WinXP, Visual Studio, GDI+, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||

I started this project when I needed something like the toolbar in FireFox's Options dialog in one of my other projects. This is where the name came from. I looked around but couldn't find any control that had what I needed so I decided to write my own.
Another benefit of writing this control was that it gave me the opportunity to play around with some of the techniques used in control creation, i.e., Designers, TypeConverters and TypeEditors. I will not go into code details (that's what the source code is for) but I will try to mention a little about the techniques used.
I took a lot of hints from Matthew Hall's Themed Taskbar for general design and use of collections, and I also borrowed John O' Byrne's excellent Imagelistpopup control, modified it slightly and used it in my image dropdown editor.
Like any other .NET control, for use in the IDE, you should add the MozBar control to a Toolbox panel. This can be accomplished by right-clicking on a Toolbox tab and selecting "Add/Remove items...", browsing to the MozBar assembly, and selecting it. This will add all the MozBar controls to the Toolbox so they can be dragged/dropped to a Windows Form or control.
There are two primary controls in the MozBar assembly:
MozPane
MozItem A MozItem is the control that defines a toolbar item. A MozItem must be contained by a MozPane. A MozPane is a container for a collection of MozItem controls, and provides automatic placement and relocation as they are added or removed.
Once an instance of MozPane or MozItem is added to the form, select it and view its properties.
The MozPane acts as a container for all the MozItems that the MozBar will contain.
There are several properties that define the appearance and behavior of the MozBar:
Style: Sets the orientation/layout of the MozBar, can be set to Vertical or Horizontal.
ImageList: ImageList that contains the images used by the items. If this is not set, no images can be selected for the items.
ItemColors: The colors used for an item's various states.
ItemBorderStyles: The border styles used for an item's various states.
Padding: Property that specifies the spacing between items and border, both vertically and horizontally.
Items: Collection containing the items within the MozBar.
Toggle: Determines if it should be possible to toggle the selected state of the items. If this is set to false the only way to deselect an item is to select another.
SelectedItems: Returns the number of selected items.
MaxSelectedItems: The max. number of simultaneous selected items, this only has effect if Toggle is set to true. If Toggle is false, MaxSelectedItems is always 1.
SelectButton: The mouse button used for selections.
Theme: Indicates whether the control should use theme colors. If enabled and visual themes are not supported, system colors will be used. The most useful events are:
ItemClick: Indicates an item has been clicked.
ItemDoubleClick: Indicates an item has been double clicked.
ItemGotFocus: Indicates that an item has got focus.
ItemLostFocus: Indicates that an item has lost focus.
ItemSelected: Indicates that an item has been selected.
ItemDeselected: Indicates that an item has been deselected, i.e., toggled. This event can only be raised if Toggle is true. All eventArgss include the MozItem responsible for the event. To check which item caused the event, use the Tag property.
private void mozPane1_ItemSelected(object sender, Pabo.MozBar.MozItemEventArgs e)
{
// Check the tag..
switch(e.MozItem.Tag)
{
case "Save":
{
break;
}
case "Load":
{
break;
}
}
}
The ItemClick event uses the ItemClickEventArgs which in addition to the responsible MozItem also includes the Button property which contains the button used.
To select an item, use the methods SelectItem(int index) or SelectItem(string tag), you have the option of using either the index or the tag to identify the item.
A MozItem is the actual toolbar item and it can be added to MozPane by simply dragging it onto the MozPane from the toolbox or by using the Controls property in the MozPane control.
A MozItem can have one of three possible states:
Normal
Selected
Focus Each of the states can have a different image, borderstyle and color for background and border. The images are set through the item's Images property; colors and borderstyles are set with the ItemColors and ItemBorderStyles properties in the MozPane control.
An item can also have different styles, that are set using the ItemStyle property:
Picture
TextAndPicture
Text
Divider When set to Divider, the item will show as a divider (similar to the ones used in menus) either horizontally or vertically depending on the Style of the MozPane. If TextAndPicture is used, the Text can be aligned using the TextAlign property, possible positions are Left, Top, Right or Bottom.
A nice way to organize related properties in a control is to group them together in a nested structure, common examples of this in controls are Size and Font properties. In MozBar, this technique is used with several properties, among them the Padding property in MozPane.

To accomplish this, we need to do two things. First, we need a class (PaddingCollection) that contains the properties we want grouped together, and second, we need a TypeConverter (PaddingCollectionTypeConverter) to display the properties properly.
[TypeConverter(typeof(PaddingCollectionTypeConverter))]
public class PaddingCollection
{
private MozPane m_pane;
private int m_horizontal;
private int m_vertical;
public PaddingCollection(MozPane pane)
{
// set the control to which the collection belong
m_pane = pane;
// Default values
m_horizontal = 2;
m_vertical = 2;
}
[RefreshProperties(System.ComponentModel.RefreshProperties.All)]
[Description("Horizontal padding.")]
public int Horizontal
{
get
{
return m_horizontal;
}
set
{
m_horizontal = value;
if (m_pane!=null)
{
// padding has changed , force DoLayout
m_pane.DoLayout();
m_pane.Invalidate();
if (m_pane.PaddingChanged!=null)
m_pane.PaddingChanged(this,new EventArgs());
}
}
}
[RefreshProperties(System.ComponentModel.RefreshProperties.All)]
[Description("Vertical padding.")]
public int Vertical
{
get
{
return m_vertical;
}
set
{
m_vertical = value;
if (m_pane!=null)
{
m_pane.DoLayout();
m_pane.Invalidate();
if (m_pane.PaddingChanged!=null)
m_pane.PaddingChanged(this,new EventArgs());
}
}
}
}
The class is pretty straightforward but the important things are the attributes. The TypeConverter attribute is used to assign the type converter to the class.
[TypeConverter(typeof(PaddingCollectionTypeConverter))]
public class PaddingCollection
{
}
The RefreshProperties attribute is used to force a refresh of properties when the property to which the attribute is assigned is changed.
[RefreshProperties(System.ComponentModel.RefreshProperties.All)]
public int Horizontal
{
}
Since this is a nested property, the type converter should inherit from ExpandableObjectConverter and we need to override four methods. In CanConvertTo and CanConvertFrom, we need to make sure we can convert to and from strings. The actual conversion is done in the ConvertTo and ConvertFrom functions.
public class PaddingCollectionTypeConverter : ExpandableObjectConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
if(sourceType == typeof(string))
return true;
return base.CanConvertFrom (context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context,
Type destinationType)
{
if(destinationType == typeof(string))
return true;
return base.CanConvertTo (context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext
context, System.Globalization.CultureInfo culture, object value)
{
if(value.GetType() == typeof(string))
{
// Parse property string
string[] ss = value.ToString().Split(new char[] {';'}, 2);
if (ss.Length==2)
{
// Create new PaddingCollection
PaddingCollection item =
new PaddingCollection((MozPane)context.Instance);
// Set properties
item.Horizontal = int.Parse(ss[0]);
item.Vertical = int.Parse(ss[1]);
return item;
}
}
return base.ConvertFrom (context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value, Type destinationType)
{
if(destinationType == typeof(string) &&
(value is MozPane.PaddingCollection) )
{
// cast value to paddingCollection
PaddingCollection dest = (PaddingCollection)value;
// create property string
return dest.Horizontal.ToString()+"; "+dest.Vertical.ToString();
}
return base.ConvertTo (context, culture, value, destinationType);
}
}
All that's left to do is to create a public property in MozPane that handles a value of type PaddingCollection, and we are all set.
true)>
[Category("Appearance")]
[Description("Padding (Horizontal, Vertical)")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public PaddingCollection Padding
{
get
{
return m_padding;
}
set
{
if (value!=m_padding)
{
if (value != null)
m_padding = value;
DoLayout();
Invalidate();
if (this.PaddingChanged!=null)
this.PaddingChanged(this,new EventArgs());
}
}
}
Another nice thing to provide with controls is custom type editors. Each of the image states in MozItem uses a custom image selector for selecting images from the ImageList assigned in the MozPane.

For this to work, we must create our own type editor (ImageMapEditor) that inherits from System.Drawing.Design.UITypeEditor.
public class ImageMapEditor : System.Drawing.Design.UITypeEditor
{
}
We must override GetEditStyle and return the style we want to use.
public override System.Drawing.Design.UITypeEditorEditStyle
GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
if(context != null && context.Instance != null )
{
return UITypeEditorEditStyle.DropDown ;
}
return base.GetEditStyle (context);
}
We must also override GetPaintValueSupported to be able to paint our own value.
public override bool
GetPaintValueSupported(System.ComponentModel.ITypeDescriptorContext context)
{
return true;
}
To initiate the editing of the property, we override EditValue. If there is an ImageList assigned, we create a new instance of ImageListPanel and then use IWindowsFormsEditorService.DropDownControl to display it in a dropdown. Before displaying the dropdown, we also add an event listener so that we can handle the user input.
public override object
EditValue(System.ComponentModel.ITypeDescriptorContext context,
IServiceProvider provider, object value)
{
wfes = (IWindowsFormsEditorService)
provider.GetService(typeof(IWindowsFormsEditorService));
if((wfes == null) || (context == null))
return null ;
ImageList imageList = GetImageList(context.Instance) ;
if ((imageList == null) || (imageList.Images.Count==0))
return -1 ;
m_imagePanel = new ImageListPanel();
m_imagePanel.BackgroundColor = Color.FromArgb(241,241,241);
m_imagePanel.BackgroundOverColor = Color.FromArgb(102,154,204);
m_imagePanel.HLinesColor = Color.FromArgb(182,189,210);
m_imagePanel.VLinesColor = Color.FromArgb(182,189,210);
m_imagePanel.BorderColor = Color.FromArgb(0,0,0);
m_imagePanel.EnableDragDrop = true;
m_imagePanel.Init(imageList,12,12,6,(int)value);
// add listner for event
m_imagePanel.ItemClick += new ImageListPanelEventHandler(OnItemClicked);
// set m_selectedIndex to -1 in case
// the dropdown is closed without selection
m_selectedIndex = -1;
// show the popup as a drop-down
wfes.DropDownControl(m_imagePanel);
// return the selection (or the original value if none selected)
return (m_selectedIndex != -1) ? m_selectedIndex : (int) value ;
}
Finally, we need to override PaintValue to paint our own value.
public override void PaintValue(System.Drawing.Design.PaintValueEventArgs pe)
{
int imageIndex = -1 ;
// value is the image index
if(pe.Value != null)
{
try
{
imageIndex = (int)Convert.ToUInt16( pe.Value.ToString() ) ;
}
catch
{
}
}
// no instance, or the instance represents an undefined image
if((pe.Context.Instance == null) || (imageIndex < 0))
return ;
// get the image set
ImageList imageList = GetImageList(pe.Context.Instance) ;
// make sure everything is valid
if((imageList == null) || (imageList.Images.Count == 0)
|| (imageIndex >= imageList.Images.Count))
return ;
// Draw the preview image
pe.Graphics.DrawImage(imageList.Images[imageIndex],pe.Bounds);
}
When the custom type editor is done, all we need to do is add the Editor attribute to the properties that should use it.
[TypeConverter(typeof(ImageTypeConverter))]
[Editor(typeof(MozBar.ImageMapEditor),typeof(System.Drawing.Design.UITypeEditor))]
[Description("Image for normal state.")]
public int Normal
{
get
{
return m_imageIndex;
}
set
{
if (value != m_imageIndex)
{
m_imageIndex = value;
if (m_item.ImageChanged!=null)
m_item.ImageChanged(this,
new ImageChangedEventArgs(itemState.Normal));
m_item.Invalidate();
}
}
}
MozBar implements basic theme support. If Theme is enabled and visual themes are supported by the application, it will use colors from the currently active theme for its select and focus state.



Implementing theme support basically involves four steps:
WM_THEMECHANGED event.
To find out if the system supports themes, we need to check which version of ComCtl32.dll is in use. This is done by using its DllGetVersion function. This will return true if visual styles are enabled either by using a manifest or Application.EnableVisualStyles(). It will return false if visual themes have been disabled for the application.
// Setup struct and function call
[StructLayout(LayoutKind.Sequential)]
public struct DLLVERSIONINFO
{
public int cbSize;
public int dwMajorVersion;
public int dwMinorVersion;
public int dwBuildNumber;
public int dwPlatformID;
}
[DllImport("Comctl32.dll", EntryPoint="DllGetVersion", ExactSpelling=true,
PreserveSig=false, CharSet=CharSet.Unicode)]
private static extern int DllGetVersion(ref DLLVERSIONINFO s);
public bool _IsAppThemed()
{
try
{
// Check which version of ComCtl32 thats in use..
DLLVERSIONINFO version = new DLLVERSIONINFO();
version.cbSize = Marshal.SizeOf(typeof(DLLVERSIONINFO));
int ret = DllGetVersion(ref version);
// If MajorVersion > 5 themes are allowed.
if (version.dwMajorVersion >= 6)
return true;
else
return false;
}
catch (Exception)
{
return false;
}
}
Almost all theme related functionality is found in the uxTheme.dll. This is an unmanaged DLL and since Microsoft does not supply any managed wrapper, we need to use DllImport to call the functions we are interested in. The uxTheme API and the constants needed to use it are described in the two header files UxTheme.h and TmSchema.h which are available in the Microsoft Platform SDK, or if you have VS2003 or VS2005 they should also be available in the ..\VC\PlatformSDK\Include directory. These are the functions used by MozBar:
[DllImport("uxTheme.dll", EntryPoint="GetThemeColor", ExactSpelling=true,
PreserveSig=false, CharSet=CharSet.Unicode )]
private extern static void GetThemeColor (System.IntPtr hTheme,
int partID,
int stateID,
int propID,
out int color);
[DllImport( "uxtheme.dll", CharSet=CharSet.Unicode )]
private static extern IntPtr OpenThemeData( IntPtr hwnd, string classes );
[DllImport( "uxtheme.dll", EntryPoint="CloseThemeData", ExactSpelling=true,
PreserveSig=false, CharSet=CharSet.Unicode) ]
private static extern int CloseThemeData( IntPtr hwnd );
To intercept the WM_THEMECHANGED event, we must override WndProc and check the message.
protected override void WndProc(ref Message m)
{
base.WndProc (ref m);
switch (m.Msg)
{
case WM_THEMECHANGED:
{
// Theme has changed , get new colors if Theme = true
if (Theme)
GetThemeColors();
break;
}
}
}
OK, so now we have our functions, we know when the theme has changed and if visual themes are supported. The only thing left to do is to get the theme related info we want and possibly adjust it to fit our needs.
private void GetThemeColors()
{
int EPB_HEADERBACKGROUND = 1;
int EPB_NORMALGROUPBACKGROUND = 5;
int TMT_GRADIENTCOLOR1 = 3810;
int TMT_GRADIENTCOLOR2 = 3811;
Color selectColor = new Color();
Color focusColor = new Color();
Color borderColor = new Color();
bool useSystemColors = false;
// Check if themes are available
if (m_themeManager._IsAppThemed())
{
if (m_theme!=IntPtr.Zero)
m_themeManager._CloseThemeData(m_theme);
// Open themes for "ExplorerBar"
m_theme = m_themeManager._OpenThemeData(this.Handle,"EXPLORERBAR");
if (m_theme!=IntPtr.Zero)
{
// Get Theme colors..
selectColor = m_themeManager._GetThemeColor(m_theme,
EPB_HEADERBACKGROUND,1,TMT_GRADIENTCOLOR2);
focusColor = m_themeManager._GetThemeColor(m_theme,
EPB_NORMALGROUPBACKGROUND,1,TMT_GRADIENTCOLOR1);
borderColor = ControlPaint.Light(selectColor);
selectColor = ControlPaint.LightLight(selectColor);
focusColor = ControlPaint.LightLight(selectColor);
}
}
// apply colors..
ItemColors.SelectedBorder = selectColor;
ItemColors.Divider = borderColor;
this.BorderColor = borderColor;
ItemColors.SelectedBackground = selectColor;
ItemColors.FocusBackground = focusColor;
ItemColors.FocusBorder = selectColor;
Invalidate();
}
MozPane will not draw correctly, hopefully this will be fixed in an update.
ImageList, it suffers from the infamous ImageList bug that destroys the alpha channel for 32 bit images when you add images to the ImageList in design mode. Workarounds for this could be to replace the ImageList with a control like the ImageSet by Tom Guinter (available here) or to add the images at run time. This is hopefully fixed in Visual Studio 2005 so it might not be worth the hassle. [DefaultValue].
ImageList was disposed at runtime or removed in design mode.
Theme property).
AutoScroll property.
VerticalScroll and HorizontalScroll).
MozItem.DoubleClick event.
Font property to MozPane.
OnFontChanged event handler to MozItem.
SelectButton property to MozPane.
I hope this control can be of use to you. I'm sure there is plenty of room for improvements and added functionality, so if you have any ideas or suggestions please post a comment.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 29 Sep 2005 Editor: Smitha Vijayan |
Copyright 2005 by Patrik Bohman Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |