![]() |
Desktop Development »
Miscellaneous »
Miscellaneous Controls
Beginner
License: The Code Project Open License (CPOL)
GradientPanel and AlphaLabel: an introduction to building controlsBy Herre KuijpersThis article is meant for beginner programmers who are interested in building their own custom controls. As an example a GradientPanel control is implemented. |
C# 1.0, Windows, .NET 1.0, .NET 1.1, WinForms, VS.NET2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

How important is it to have a smooth, sexy, cool but subtle looking user interface? The look & feel of an application can be quickly improved by using visually attractive designs, e.g., by simply using gradient background fills or (semi) transparent controls. The GradientPanel control will provide this kind functionality for you, yet it will be as easy to use just like the regular Panel control.
Because of the lack of a decent transparent Label control in .NET 1.1, I included my AlphaLabel control in the sources (it is derived from Label). The AlphaLabel control uses alpha blending to create a semi or fully tansparent background effect. Note that .NET 2.0 users have a Label control that is transparant by default. However, it doesn't support alpha blending :-p.
This article is meant for beginner programmers who are interested in designing and writing their own custom controls. I know there are a number of beginner articles out there that explain how to build custom controls. This article just describes the process from my experience and my perspective, and I hope this can be a valuable contribution to the community.
As a 'case', I decided to use the GradientPanel control as an example on how to do this. I will not explain the AlphaLabel control in this article, however I have fully documented the source code and it should not be difficult to understand.
The AlphaLabel supports the following features:
Alpha property (0=100% transparent, 255=100% opaque).
Label behaviour.The GradientPanel will support the following features:
Panel functionality (including turning off gradient fills).
Panel, this will be a container control).
Additionally, I will give some hints and tips on how to make the control look professional.
The C# code used in this article is based on Visual Studio 2003 for .NET 1.1. However, it is not difficult to implement this also in a .NET 2.0 environment. The GradientPanel control is a good example of how to implement your own custom control, as it requires and shows some basic features used for almost every custom control:
A little bit of GDI+ (Graphics Device Interface) programming will be involved here. If you plan on creating more visually attractive controls, it sure pays off to get to know the GDI+ functions well. This article, however, will only use some basic GDI functions. Also, this article will show how to reserve GDI+ resources.
This is not always very trivial, especially when you implement a control that is derived from another control, as in this case. I will discuss the most commonly used events to override in this article.
A lot of controls presented here on CodeProject sadly often skip this part. They only implement the core functionality of the control, but forget that there is also a thing called a 'design time' development environment. Controls are meant to be used by fellow programmers and designers. Therefore, publicly distributed controls should support 'design time' development. This article will give an introduction on how this can be achieved.
I will explain building the control on a step-by-step basis. First off, all I want to point out my personal view on custom controls to any potential control builder; try to stick to the following rules of thumb:
Every programmer loves lightweight controls, just drag, drop, and go! Nobody wants to bother with installers, missing sources, or missing references or missing resources (e.g., fonts or graphics). Programmers (yes, those are your end-users) are human too, if it does not work at once, they will often just give up.
Personally, I would advise never to use external or non-system libraries, unless absolutely necessary. What good is a component if you first need to install other components to use it?
Unless you are planning to fully build your control from scratch (which is an advanced topic), I would suggest to choose a suitable base control that handles most of the controls' functions for you. This way, the amount of coding needed is limited. In this example, we will be using the Panel control as a base control.
Basically, it means to decompose potential complex controls into smaller parts. For instance, it may be tempting to set a text property for the panel control to allow the user to write text in the control. Instead, choosing to create a gradient panel, which will only add gradient, nothing more nothing less makes it simple and very clear for the user (programmer) what the control can do.
Properties define the states of the control. The number of states of the control increases exponentially with each new property introduced to the control, therefore the complexity of your control increases exponentially. So choose your properties wisely, and try to stick to the commonly chosen properties as used by other controls, where possible.
Hopefully, this piece of advice can help as a guideline for the design of your future control. So let's start some implementation.
For now, I will presume that the control will be implemented directly into the application. This has two advantages: the sources stay small, and the control will be easier to debug (!). Alternatively, you can implement the control in a separate library. For now, this is out of the scope for this article.
It is good practice to compile whenever you change something. This will prevent having to troubleshoot errors in an early stage.
// Default namespace
using System;
// used for adding property attributes
using System.ComponentModel;
// used for drawing with GDI+
using System.Drawing;
// Drawing2D supports drawing gradients
using System.Drawing.Drawing2D;
// this namespace contains the base control: Panel
using System.Windows.Forms;
Panel. Change the class as follows: class GradientPanel : Panel
GradientPanel control should now show up in the components list. Press OK, the control will now be available in the toolbox (My user controls tab).
GradientPanel control from the toolbox onto the Form. The control should render like a regular Panel control, except that it is now called a GradientPanel control.
Note: if you are trying this in VS 2005, the control gets added to the toolbox automatically after compilation.
So now that the basic project structure is setup and running, it is now time to actually start some customization. Basically, we want to keep the same functionality as the regular Panel except that it should render a gradient background. Because the gradient fill requires two colors, we will be re-using the ForeColor and BackColor properties for this (this means less programming!).
Additionally, we would like to specify the angle under which the gradient is to be drawn. Because we want this parameter to be configurable at design time, the best solution is to implement a public property for this called GradientAngle.
// declare the private members that will store
// the property values private float gradientAngle;
public float GradientAngle
{
get {
return gradientAngle;
}
set {
gradientAngle = value;
// make sure the control refreshes itself and its childcontrols
// when this property changes
this.Invalidate(true);
}
}
this.Invalidate() call. This call will make the control ask the operating system to refresh itself. This means that the refreshing is not done in the Invalidate() call itself, but at some other time when the operating system has time to process it. This is done through the OnPaint and OnPaintBackground events.
OnPaintBackground event. By simply typing the word 'override' and then press space, a list will popup showing all base control methods that can be overridden! Very handy indeed. Select the OnPaintBackground and press the tab button. Automatically, the body of the method is generated for you (less typing!).
Now, simply add some code to gradient fill the background, and your code should look something like this:
protected override void OnPaintBackground(PaintEventArgs pevent)
{
// call the base class to do it's background painting first
base.OnPaintBackground (pevent);
// create the brush (GDI object) used to paint the gradient fill
Brush brush = new LinearGradientBrush(this.ClientRectangle,
ForeColor, BackColor, gradientAngle, true);
// use the brush to paint on the graphics device (GDI object)
pevent.Graphics.FillRectangle(brush, this.ClientRectangle);
// dispose of the brush after we're
done painting
brush.Dispose();
}
There you have it, a GradientPanel with a gradient background. Feel free to play with the control's properties. Change the angle, and the fore- and backcolor at design time.
The basic functionality is now implemented and the control is ready for use. However, one last step is sometimes required. In this case, you can decide for yourself if you need it.
Typically, when building controls, it is very important to be efficient when using system resources. Especially because a form may contain multiple instances of your control. If your control is inefficient with resources, the form may become very slow and irresponsive. In the worst case, it will eat your system resources like memory or CPU cycles and ultimately freeze your application.
Even in this simple control, we are using system resources. Notice in the OnPainBackground method the use of a Brush and of the Graphics object. These objects come from the System.Drawing namespace and are .NET wrapper classes around the GDI+ objects used by the operating system. Notice that in this particular example, the Brush object is created, used, and disposed every time the background needs repainting. Disposing the Brush here is required because we need to release the system resources as soon as we are done with them. But this is really a waste of CPU cycles: why create and dispose the Brush object on every Paint event? Most of the time, the Brush object will not change. The Brush will only change if the forecolor, backcolor, or angle changes.
If you plan to optimize your code, you can take the following steps:
Brush object to be used by this control as a private member. To make sure it has some valid value, initialize it to the control color. private Brush brush = new SolidBrush(SystemColors.Control);
private method CreateBrush() in which the linear brush is created. Insert the code to dispose of the Brush if it exists and create a new one. private void CreateBrush()
{
//dispose of the brush if it exists
if (brush != null)
brush.Dispose();
// create the brush
brush = new LinearGradientBrush(this.ClientRectangle,
ForeColor, BackColor, gradientAngle, true);
// make sure the control refreshes itself
// and its child controls when the brush changes
this.Invalidate(true);
}
ForeColor and BackColor properties. In the setter of the GradientAngle, ForeColor, and BackColor properties, add the call to CreateBrush(). public float GradientAngle
{
get { return gradientAngle; }
set {
gradientAngle = value;
CreateBrush();
}
}
public override Color ForeColor
{
get { return base.ForeColor; }
set {
base.ForeColor = value;
CreateBrush();
}
}
public override Color BackColor
{
get { return base.BackColor; }
set {
base.BackColor = value;
CreateBrush();
}
}
OnPaintBackground method. protected override void OnPaintBackground(PaintEventArgs pevent)
{
base.OnPaintBackground (pevent);
pevent.Graphics.FillRectangle(brush, this.ClientRectangle);
}
Brush when the control is created. This can be done by overriding the OnCreateControl event. protected override void OnCreateControl()
{
// first initialize the base control and its properties
base.OnCreateControl ();
// now create the brush after all properties have been initialized
CreateBrush();
}
Form at design time.You may have noticed that when you resize the control at design time, or when you change the form's backcolor, the gradient is not displayed correctly. Before optimizing our code, the Brush was created on every OnPaintBackground event. When you resize the control, this event is fired a number of times. But because we are not creating the Brush in the OnPaintBackground event anymore, we will have to take care of this manually.
Brush is created when resizing the control or when its parent control or form's background color is changed, override the OnResize() and OnBackColorChanged methods, and call the CreateBrush(). protected override void OnResize(EventArgs e)
{
base.OnResize (e);
CreateBrush();
}
protected override void OnBackColorChanged(EventArgs e)
{
base.OnBackColorChanged (e);
CreateBrush();
}
Form at design- and runtime! This concludes the introduction to building your own controls. If you still encounter problems, find the sources available for download on this page.
As shown in this introduction, even creating a simple control such as this already requires a number of overrides and additional coding in order to get the desired functionality both at design- and runtime.
Before rounding up this article, I want to give some general hints, tips, and reminders for beginner programmers:
///<summary> notation. These comments will show up in the programmer's IDE. Specially, when the control gets more complex and has many more properties, it is important to have these well documented in order to avoid confusion.
#region and #endregion to group similar methods or functionality together. This will greatly improve the readability of your code (of course, this is a very general statement and applies to all source code).
[Category("Appearance")] attribute for the GradientAngle property, means that the property will show up in the Appearance section in the categorized propery view in your IDE.
Console.WriteLine("important debug information");
These trace statements will display in your IDE's Output window, also at design time!
There are obviously many more hints and tips out there. The best way of learning is simply by trying it in practice. I hope this article has given you more insight in the not so trivial world of building custom controls, and I hope you can use it as a starting point for your next custom control project.
Version 1.0 of the GradientPanel and AlphaLabel controls were written on September 2006. This article was published in September 2006.
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 6 Sep 2006 Editor: Smitha Vijayan |
Copyright 2006 by Herre Kuijpers Everything else Copyright © CodeProject, 1999-2009 Web21 | Advertise on the Code Project |