Introduction
Ever since Microsoft started using a nice GUI style that shows brief information at the top of setup forms, I wanted to have one of my own. The screen shot shown above display a sample of such a control. I first created this control as an OCX with VB6 and used it in several projects. VB6 version required heavy use of Win32 API functions. However, to learn C# syntax, I decided to create the same control, I call it InfoBarLite
, with this new language. After starting to code, I found out that life was much better with C#.
The control has some very nice features to make things easier:
- The control can have both a background image and a separate image.
- The image can be aligned in 9 different ways. Additionally, the image can be positioned by using x and y offset properties.
- Two different text properties are provided each of which can have multiple lines separated by
Environment.NewLine
character.
- Each text property has its own
Font
, ForeColor
, OffsetX
, and OffsetY
properties.
- Background color of the control can be either solid or gradient.
- The control can have 9 different border styles for all sides or for each side.
- It can be used either as a header bar or as a side bar.
Details
The InfoBarLite
control is derived from System.Windows.Forms.UserControl
as shown below:
public class InfoBarLite : System.Windows.Forms.UserControl
{
...
}
The most interesting part of the control is OnPaint
event as this is the usual case for owner-drawn controls. First, the background information is drawn. This information is defined by BackStyle
, BorderSide
, and BoderStyle
properties. One point I want to stress out is that the behavior of the control. if the BackStyle
is Gradient
, the background image is not drawn. This means that BackStyle
property has precedence over BackgroundImage
property. In contrast, if the BackStyle
is Solid
, the BackColor
property is ignored. This behavior is by design. Of course, it can easily be changed by modifying the overridden OnPaint
event. Now, here is the OnPaint
method:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Rectangle r = this.ClientRectangle;
switch( this.BackStyle )
{
case BackStyle.Gradient:
LinearGradientBrush gb = new LinearGradientBrush(r,
this.GradientStartColor,
this.GradientEndColor,
this.GradientMode);
if(this.BorderSide == Border3DSide.All)
{
ControlPaint.DrawBorder3D(pe.Graphics, r,
this.BorderStyle, this.BorderSide);
r.Inflate(-2, -2);
pe.Graphics.FillRectangle(gb, r);
}
else
{
pe.Graphics.FillRectangle(gb, r);
ControlPaint.DrawBorder3D(pe.Graphics, r,
this.BorderStyle, this.BorderSide);
}
gb.Dispose();
break;
case BackStyle.Solid:
if(this.BorderSide == Border3DSide.All)
{
ControlPaint.DrawBorder3D(pe.Graphics, r,
this.BorderStyle, this.BorderSide);
r.Inflate(-2, -2);
SolidBrush sb = new SolidBrush(this.BackColor);
pe.Graphics.FillRectangle(sb, r);
sb.Dispose();
}
else
{
ControlPaint.DrawBorder3D(pe.Graphics, r,
this.BorderStyle, this.BorderSide);
}
break;
default:
ControlPaint.DrawBorder3D(pe.Graphics, r,
this.BorderStyle, this.BorderSide);
break;
}
StringFormat fmt = new StringFormat();
fmt.FormatFlags = StringFormatFlags.MeasureTrailingSpaces |
StringFormatFlags.DisplayFormatControl |
StringFormatFlags.FitBlackBox;
fmt.Trimming = StringTrimming.EllipsisCharacter;
RectangleF rImage = new RectangleF(0,0,0,0);
RectangleF rText1 = new RectangleF(0,0,0,0);
RectangleF rText2 = new RectangleF(0,0,0,0);
SizeF sizeText1 = pe.Graphics.MeasureString(this.Text1,
this.Text1Font, ClientRectangle.Size, fmt);
SizeF sizeText2 = pe.Graphics.MeasureString(this.Text2,
this.Text2Font, ClientRectangle.Size, fmt);
this.getRects(ref rImage, ref rText1,
sizeText1, ref rText2, sizeText2);
if(this.Enabled)
{
int iImageWidth = 0;
if( this.m_Image != null )
{
pe.Graphics.DrawImage(this.m_Image, rImage);
iImageWidth = this.m_Image.Width;
}
SolidBrush sb = new SolidBrush(this.Text1ForeColor);
pe.Graphics.DrawString(this.Text1,
this.Text1Font, sb, rText1, fmt);
sb = new SolidBrush(this.Text2ForeColor);
pe.Graphics.DrawString(this.Text2, this.Text2Font,
sb, rText2, fmt);
sb.Dispose();
}
else
{
if( this.m_Image != null )
ControlPaint.DrawImageDisabled(pe.Graphics,
this.m_Image, (int) rImage.X, (int) rImage.Y,
SystemColors.ControlLight);
ControlPaint.DrawStringDisabled(pe.Graphics,
this.Text1, this.Text1Font,
SystemColors.ControlLight,
rText1,
fmt);
ControlPaint.DrawStringDisabled(pe.Graphics,
this.Text2, this.Text2Font,
SystemColors.ControlLight,
rText2,
fmt);
}
}
Usage
It should be very easy to use this control. Just drop it in a form, set the desired properties, and that's all. A sample which is very similar to Nero's screen is shown below:
Figure 3 - A sample demo screen showing the control as a side bar and as a header bar.
Know issues/limitations
There are some issues that need to be fixed and/or added.
- When the control is used as a sidebar, text properties are not drawn rotated (vertical), but this is by design.
- The
Font
property inherited from UserControl
class should not be visible to the user. Currently, I don't know how to do it; therefore, any suggestions will be appreciated.
- When the
Enabled
property is changed through the property window (in design mode), this change is not reflected to the control. On the other hand, if this property is changed via code, the control acts as expected. Once again, I require suggestions on this issue.
- The last limitation is that the control is not globalization aware. In VS IDE, if you set the
Form
's Localizable
property to True
, the IDE automatically adds code to retrieve the correct text of native controls (comes with VS.NET) that will reflect the CurrentCulture
via resource manager. I guess this is the easiest way to localize an application. Currently, the InfoBarLite
doesn't cooperate with the IDE to accomplish this task. If someone knows how to do it and sends the solution, I can update the control to benefit everybody.
Conclusion
Hopefully, the InfoBarLite
control will make my fellow developers' life easier as it did mine. It will also allow you to add a very nice user interface feature that will help you to increase end-user experience. If you look at Figure 1, this control will increase the understandability of the forms we create for users, as they will now have more descriptive information about what is needed from him/her. Traditionally, this kind of information is displayed in a StatusBar
control. However, most of the users are not happy, at least my users, because they are being forced to read such small letters. The InfoBarLite
control attempts to solve aforementioned usability problems. It can display two different styles of text and an image. The image might describe the task required to be achieved better than text as most people tend not to read descriptions. It's suggested to be used in dialog boxes, but, of course, it's up to you.