Simple GDI+ Controls
The first type of GDI+ control that might occur to you to use is one that simply wraps one of the GDI+ drawing features you examined in the previous chapter. For example, you might want to provide a simple shape control that renders a closed figure depending on the properties you set. Or, you might want to create a special type of label that paints itself with a textured brush, or a gradient that the developer can configure through the appropriate properties. That's the type of example considered next with the GradientLabel control.
A Gradient Label
The first example presents a special label that allows the developer to add a gradient background by choosing two colors. The developer can also specify the usual properties like Text, Font, and ForeColor, and configure the type of gradient fill through an additional property. The GradientLabel control is a quick and painless way to add a label with a gradient background to a splash screen or wizard in your application without having to rewrite the basic GDI+ code.The GradientLabel class inherits from UserControl and overrides the drawing logic. This isn't necessary—it could simply inherit from the base Control class and incur slightly less of an overhead, but the UserControl approach makes it easy to work with the GradientLabel class in a test project, rather than requiring a separate DLL and client test program.
public class GradientLabel : System.Windows.Forms.UserControl
{
// (Code omitted.)
}
The first step is to create the required properties. In this case, you need to store information about the text to be displayed on the label, the two colors for the gradient, and the type of gradient to be used.
private string text;
private Color colorA = Color.LightBlue;
private Color colorB = Color.Purple;
private LinearGradientMode gradientStyle = LinearGradientMode.ForwardDiagonal;
Each member variable requires a separate property procedure. For brevity's sake, I've left out the optional attributes you could use to configure the category and the corresponding description for each property. Note that each property invalidates the display, ensuring that the gradient and text are repainted as needed.
[Browsable(true),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override string Text
{
get
{
return text;
}
set
{
text = value;
this.Invalidate();
}
}
public Color ColorA
{
get
{
return colorA;
}
set
{
colorA = value;
this.Invalidate();
}
}
public Color ColorB
{
get
{
return colorB;
}
set
{
colorB = value;
this.Invalidate();
}
}
public LinearGradientMode GradientFillStyle
{
get
{
return gradientStyle;
}
set
{
gradientStyle = value;
this.Invalidate();
}
}
Tip
Note that the user control class already provides a Text property. However, this Text property will not appear in the Properties window unless you manually override it and set the Browsable attribute to true. Also, this property will not be serialized (stored in the form designer code when configured at design-time) unless you add the DesignerSerializationAttribute. This is a source of much confusion for beginning control developers. Always remember, if you inherit from a control and have trouble storing an existing property, you may need to override it and modify this attribute!The final step is to add the drawing logic, which is made up of three separate steps:Set the ResizeRedraw property of the control to true so it will be refreshed every time the size changes.
Override the OnPaintBackground() method, add the code to generate the gradient fill, and don't call the base implementation on the method. This way, the blank grey background is not painted before the gradient, and control drawing or refreshing takes place faster and with less flicker.
Override the OnPaint() method and add the code needed to paint the label text with the current font and forecolor.
// Ensure it will be repainted when resized.
private void GradientLabel_Load(object sender,
System.EventArgs e)
{
this.ResizeRedraw = true;
}
protected override void OnPaintBackground(
System.Windows.Forms.PaintEventArgs e)
{
LinearGradientBrush brush = new LinearGradientBrush(e.ClipRectangle, colorA,
colorB, gradientStyle);
// Draw the gradient background.
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
// Draw the label text.
e.Graphics.DrawString(text, this.Font, new SolidBrush(this.ForeColor), 0, 0);
}
Figure 13-1 shows the gradient label sized to fill a form.

Figure 13-1: The GradientLabel
Improving the GradientLabel's Design-Time Support
As it stands, the GradientLabel works seamlessly. You can easily configure the two colors from an automatically provided color picker at design-time, and the results appear immediately in the IDE.However, there are a couple of changes you can make to improve the control. First, consider ColorA, ColorB, and GradientFillStyle properties. These properties are really all parts of the same setting, and together they determine the background fill. If you wrapped these three settings into one class, they would be easier to find and set at design time, and easier to reuse in any other control that might need a gradient fill.Here's how the custom class would look. It uses a special TypeConverter attribute that instructs Visual Studio .NET to expose this object as an expandable set of subproperties in the Properties window.
[TypeConverter(typeof(ExpandableObjectConverter))]
public class GradientFill
{
private Color colorA = Color.LightBlue;
private Color colorB = Color.Purple;
private LinearGradientMode gradientStyle= LinearGradientMode.ForwardDiagonal;
public Color ColorA
{
get
{
return colorA;
}
set
{
colorA = value;
}
}
public Color ColorB
{
get
{
return colorB;
}
set
{
colorB = value;
}
}
[System.ComponentModel.RefreshProperties(RefreshProperties.Repaint)]
public LinearGradientMode GradientFillStyle
{
get
{
return gradientStyle;
}
set
{
gradientStyle = value;
}
}
}
The new GradientLabel control does not define any of these properties. Instead, it now defines a single GradientFill property. Note that this property requires the DesignerSerializationVisibility attribute set to Content. This instructs Visual Studio .NET to serialize all embedded child properties of the GradientFill class. Without it, you'll face the "disappearing configuration" problem all over again.
private GradientFill gradient = new GradientFill();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public GradientFill GradientFill
{
get
{
return gradient;
}
set
{
gradient = value;
this.Invalidate();
}
}
You'll also need to modify a single code statement in the background painting code:
LinearGradientBrush brush = new LinearGradientBrush(e.ClipRectangle,
gradient.ColorA, gradient.ColorB, gradient.GradientFillStyle);
This design also provides an opportunity to get a little fancy by creating a custom thumbnail of the gradient in the Properties window. To add this extra bit of finesse, all you need to do is create a UITypeEditor for the GradientFill class, and override the PaintValue() method. Here's the complete code:
public class GradientFillEditor : UITypeEditor
{
public override bool GetPaintValueSupported(
System.ComponentModel.ITypeDescriptorContext context)
{
return true;
}
public override void PaintValue(
System.Drawing.Design.PaintValueEventArgs e)
{
GradientFill fill = (GradientFill)e.Value;
LinearGradientBrush brush = new LinearGradientBrush(e.Bounds,
fill.ColorA, fill.ColorB, fill.GradientFillStyle);
// Paint the thumbnail.
e.Graphics.FillRectangle(brush, e.Bounds);
}
}
Finally, attach the UITypeEditor to the GradientFill class with an Editor attribute, as you did in Chapter 8:
[TypeConverter(typeof(ExpandableObjectConverter)),
Editor(typeof(GradientFillEditor), typeof(UITypeEditor))]
public class GradientFill
The GradientLabel now retains its effortless design-time support, with the added frill of a thumbnail gradient in the Properties window next to the GradientFill property (see Figure 13-2). You can also reuse the GradientFill and GradientFillEditor to add similar features to countless other custom control projects.

Figure 13-2: Custom thumbnails with the GradientLabel
A Marquee Label
The next example presents another graphical label control—with a twist. This control automatically refreshes its display in response to a timer, scrolling a line of text across the visible area. The code is quite similar to the previous example, except for the fact that it adds double-buffering so that the label can be scrolled smoothly without flicker.The control uses three significant properties: Text; ScrollTimeInterval, which determines how frequently the timer fires; and ScrollPixelAmount, which determines how much the text is scrolled with every timer tick. An additional private member variable, called position, is defined to track how far the text has scrolled. This property is not made available to the client (although it could be if you wanted to allow the text to be set at a specific scroll position).Here's the property procedure code for the MarqueeLabel control:
private string text;
private int scrollAmount = 10;
private int position = 0;
[Browsable(true),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override string Text
{
get
{
return text;
}
set
{
text = value;
this.Invalidate();
}
}
public int ScrollTimeInterval
{
get
{
return tmrScroll.Interval;
}
set
{
tmrScroll.Interval = value;
}
}
[DefaultValue(10)]
public int ScrollPixelAmount
{
get
{
return scrollAmount;
}
set
{
scrollAmount = value;
}
}
When the control is instantiated, it checks the current mode. In design mode, it disables the timer. The text still appears on the control, but it is not automatically scrolled (which would be a potentially distracting and CPU-wasting approach).
private void MarqueeLabel_Load(object sender, System.EventArgs e)
{
this.ResizeRedraw = true;
if (!this.DesignMode)
{
tmrScroll.Enabled = true;
}
}
At runtime, the timer simply increments the private position variable and invalidates the display with each tick:
private void tmrScroll_Tick(object sender, System.EventArgs e)
{
position += scrollAmount;
// Force a refresh.
this.Invalidate();
}
The painting logic takes care of the rest. First, the OnPaintBackground() method is overridden to prevent the default grey background from being painted (which would add significant flicker). Next, the OnPaint() method draws the blank background and the scrolled text to an image object in memory, and paints it to the form at once. (This is the double-buffering technique presented in the last chapter.)
protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
{
// Do nothing.
// To prevent flicker, we will draw both the background and the text
// to a buffered image, and draw it to the control all at once.
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
// The following line avoids a design-time error that would
// otherwise occur when the control is first loaded (but does not yet
// have a defined size).
if (e.ClipRectangle.Width == 0)
{
return;
}
base.OnPaint(e);
if (position > this.Width)
{
// Reset the text to scroll back onto the control.
position = -(int)e.Graphics.MeasureString(text, this.Font).Width;
}
// Create the drawing area in memory.
// Double buffering is used to prevent flicker.
Bitmap blt = new Bitmap(e.ClipRectangle.Width, e.ClipRectangle.Height);
Graphics g = Graphics.FromImage(blt);
g.FillRectangle(new SolidBrush(this.BackColor), e.ClipRectangle);
g.DrawString(text, this.Font, new SolidBrush(this.ForeColor), position, 0);
// Render the finished image on the form.
e.Graphics.DrawImageUnscaled(blt, 0, 0);
g.Dispose();
}
If the text has scrolled off the form, the position is reset. However, the new starting position is not (0, 0). Instead, the text is moved to the left by an amount equal to its length. That way, when the scrolling resumes, the last letter appears first from the left side of the control, followed by the rest of the text.The online samples for this chapter include a test program (shown in Figure 13-3) that allows you to try out the marquee control and dynamically modify its scroll speed settings.

Figure 13-3: The MarqueeLabel test utility