Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

GroupBox with an icon: the ImageGroupBox control

0.00/5 (No votes)
15 Mar 2006 1  
This article presents an extended GroupBox that displays an icon in the header.

Sample Image - ImageGroupBox.jpg

Introduction

To enhance the design of our applications, most of us use images or icons. With .NET, it's very easy to do, because most of the controls provided by Microsoft already have an Image or Icon property. Unfortunately, it is not the case of the GroupBox control. In fact, with the standard GroupBox control, it is not possible to display an icon in the header section.

Background

I was very surprised to see first that the GroupBox has no Icon nor Image property, and next that no code was available on this topic on the Internet. Of course, great controls like The Grouper did what I was looking for (and much, much more). But I wanted something more simple. So I started to write my own control, thinking that it would be easy ... Here it is.

How does it work?

Of course, the GroupBox must be overridden, and the only property added to the base GroupBox is the Icon property, defined like this:

private Icon m_Icon = null;

[Description("Icon before the text"), 
    AmbientValue((string)null), 
    Category("Appearance"),Localizable(true)]
public Icon Icon {
    get { return m_Icon; }
    set { if(m_Icon != value) { m_Icon = value; 
          this.Invalidate(false); } }
}

For working with visual styles, it's better to use the VisualStyleRenderer class:

private VisualStyleRenderer m_Renderer = null;

Next, the OnPaint method is overridden too. It allows custom painting, exactly what we need to introduce the drawing of the icon. The basic idea of this code is simple, as you can see:

protected override void OnPaint(PaintEventArgs e) {
    // This override only draws the control

    // if the icon is not null. Otherwise,

    // the base method is called

    if(m_Icon != null && (Application.RenderWithVisualStyles 
       && (base.Width >= 10)) && (base.Height >= 10)) {
        // Draw the entire control

        this.DrawGroupBox(e.Graphics);
    } else base.OnPaint(e);
}

The main code lies in the DrawGroupBox method. This method draws the entire control, with the help of three methods: DrawIcon, DrawText, and DrawBackground.

private void DrawGroupBox(Graphics grfx) {
    // Set the enabled state of the control

    GroupBoxState state = base.Enabled ? 
        GroupBoxState.Normal : GroupBoxState.Disabled;

    // The rectangle that bounds the control

    Rectangle bounds = new Rectangle(0,0,base.Width,base.Height);
    // Set the rectangle to display the Text

    Size txtsize = TextRenderer.MeasureText(grfx,text, 
                   font,new Size(bounds.Width-14,bounds.Height));
    // The optimized height of the header

    int headerheight = Math.Max(m_Icon.Height,txtsize.Height);
    // Define the rectangle for the icon

    Rectangle iconrect = new Rectangle(9, 
              (headerheight - m_Icon.Height) / 2, 
              m_Icon.Width,m_Icon.Height);
    // Define the rectangle of the text

    Rectangle textrect = new Rectangle(new 
              Point(iconrect.Right,(headerheight - 
              txtsize.Height) / 2),txtsize);
    // Define the rectangle that defines the inner container

    Rectangle displayrect = bounds; displayrect.Y += 
              headerheight / 2; displayrect.Height 
              -= headerheight / 2;

    // Draw the icon

    DrawIcon(grfx,m_Icon,iconrect,state);
    // Draw the text

    DrawText(grfx,this.Text,this.Font,textrect, 
             m_Renderer.GetColor(ColorProperty.TextColor), 
             this.BackColor,txtflags);
    // Draw the background

    DrawBackground(grfx,displayrect,textrect,m_Icon.Width);
    // Clean up

    grfx.Dispose();

Then, the three distinct objects are painted in the following functions:

  • the icon: it is done with the Graphics.DrawIcon method for the Enabled state, and the Control.ControlPaint.DrawImageDisabled method for the Disabled state:
    // Draw the icon m_Icon in the iconrect Rectangle
    
    private void DrawIcon(Graphics grfx,Icon icon, 
                 Rectangle rc,GroupBoxState state) {
        if(state == GroupBoxState.Disabled) {
            using(Image image = m_Icon.ToBitmap()) {
                // Draw the disabled icon
    
                ControlPaint.DrawImageDisabled(grfx,image, 
                              rc.Left,rc.Top,Color.Empty);
            }
        } else {
        // Draw the enabled icon
    
        grfx.DrawIcon(icon,rc);
        }
    }
  • the text: this can be done with the Graphics.DrawString method, but for respect of the base properties (like RightToLeft), it is better to use the TextRenderer.DrawText method:
    // Draw the text with the Graphics object grfx,
    
    // in the bounds Rectangle
    
    private void DrawText(Graphics grfx,string text, 
            Font font,Rectangle bounds, 
            Color txtcolor,Color backcolor) {
        TextRenderer.DrawText(grfx,text,font, 
                  bounds,txtcolor,backcolor);
    }
  • the rounded rectangle: it's easy to do with the VisualStyleRenderer class. This class encapsulates the visual styles handling, and prevents writing big amounts of lines of code. Because the upper border of the rounded rectangle must not overlay the header of the control, only three parts are drawn, gathered in such a manner that they seem to form a rectangle:
    private void DrawBackground(Graphics grfx, 
                Rectangle bounds,Rectangle headerrect, int iconwidth) {
        // Initializing the bounds of each part
    
        Rectangle leftrect = bounds; leftrect.Width = 7;
        Rectangle middlerect = bounds; middlerect.Width = 
                Math.Max(0,headerrect.Width + iconwidth);
        Rectangle rightrect = bounds;
        if((txtflags & TextFormatFlags.Right) == TextFormatFlags.Right) {
            leftrect.X = bounds.Right - 7;
            middlerect.X = leftrect.Left - middlerect.Width;
            rightrect.Width = middlerect.X - bounds.X;
        } else {
            middlerect.X = leftrect.Right;
            rightrect.X = middlerect.Right;
            rightrect.Width = bounds.Right - rightrect.X;
        }
        middlerect.Y = headerrect.Bottom;
        middlerect.Height -= headerrect.Bottom - bounds.Top;
    
        // Draw the left part
    
        m_Renderer.DrawBackground(grfx,bounds,leftrect);
        // Draw the middle part
    
        m_Renderer.DrawBackground(grfx,bounds,middlerect);
        // Draw the right part
    
        m_Renderer.DrawBackground(grfx,bounds,rightrect);
    }

I recommend you to see the entire code (downloadable at the top of this page) for all the details, particularly for the TextFormatFlags, omitted here for simplicity.

This approach is not the only possible, of course. After I wrote the ImageGroupBox control, I saw the XP Style Collapsible GroupBox that uses the GroupBoxRenderer.DrawGroupBox method.

Points of Interest

As you could see, overriding the OnPaint method involves redrawing the entire control. And it's not a so little work when the visual styles must be respected! But in fact, the tools based on the VisualStyleRenderer and TextRenderer classes, help. That's what I thought interesting in this approach. It's a way of using underground classes to product nice results, without writing too many lines of code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here