Skip to main content
Email Password   helpLost your password?

MozBar

Introduction

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.

Using the control

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:

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.

MozPane

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:

The most useful events are:

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.

MozItem

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:

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:

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.

Nested properties

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.

Padding property

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

Custom image selector

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.

Padding property

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

Theme support

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.

LunaSilverOlive

Implementing theme support basically involves four steps:

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

}

Known problems/issues

History

Conclusion

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.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generalweird bug Pin
cinamon
20:09 25 Dec '07  
GeneralRe: weird bug Pin
shawn32
14:00 27 May '08  
GeneralRe: weird bug Pin
shawn32
4:42 28 May '08  
GeneralRe: weird bug Pin
MrWolfy
11:04 28 Jun '08  
QuestionCompact Framework Pin
vital.cruvinel
5:54 31 Jul '07  
GeneralRemoving the border Pin
gimmedacode
14:52 27 Apr '07  
NewsBorder Draw Problem - Solution [modified] Pin
Julian Ott
10:37 14 Oct '06  
Generalnice control Pin
picazo
12:19 11 Apr '06  
Generalhow to make mozPanel not select any item? Pin
nonotoday
17:12 15 Feb '06  
GeneralSome suggestions Pin
deecke
5:25 9 Jan '06  
GeneralRe: Some suggestions... Sorry! Pin
deecke
15:09 9 Jan '06  
GeneralRe: Some suggestions Pin
Patrik Bohman
1:53 17 Jan '06  
GeneralRe: Some suggestions Pin
bugmenot123
13:34 7 Feb '06  
GeneralRe: Some suggestions Pin
Patrik Bohman
10:03 8 Feb '06  
GeneralVersion 1.5.1 is up. Pin
Patrik Bohman
10:59 29 Sep '05  
GeneralVersion 1.5 is up Pin
Patrik Bohman
3:38 19 Aug '05  
Generalbig bug Pin
Unruled Boy
17:49 15 Aug '05  
GeneralRe: big bug Pin
Patrik Bohman
10:19 16 Aug '05  
Generalwhere is the sample code? Pin
Unruled Boy
17:02 15 Aug '05  
GeneralRe: where is the sample code? Pin
Patrik Bohman
10:52 16 Aug '05  
Generallicense? Pin
Unruled Boy
17:01 15 Aug '05  
GeneralRe: license? Pin
Patrik Bohman
11:02 16 Aug '05  
GeneralMozItem on Toolbox Pin
yoeru
21:38 13 Jul '05  
GeneralImageMapEditor class Pin
yoeru
19:49 13 Jul '05  
GeneralRe: ImageMapEditor class Pin
Patrik Bohman
21:14 13 Jul '05  


Last Updated 29 Sep 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009