Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / Windows Forms
Article

GradientPanel and AlphaLabel: an introduction to building controls

Rate me:
Please Sign up or sign in to vote.
4.69/5 (11 votes)
6 Sep 2006CPOL14 min read 52.3K   691   54   6
This article is meant for beginner programmers who are interested in building their own custom controls. As an example a GradientPanel control is implemented.

Sample Image - GradientPanel.png

Introduction

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:

  • Control the transparency level by setting the Alpha property (0=100% transparent, 255=100% opaque).
  • Regular Label behaviour.

The GradientPanel will support the following features:

  • Standard Panel functionality (including turning off gradient fills).
  • Allow containing other controls (just like Panel, this will be a container control).
  • Linear gradient fill between two colors maximum.
  • Gradient fill under any angle.
  • Automatically adjusting the gradient after resizing the control.

Additionally, I will give some hints and tips on how to make the control look professional.

Background

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:

  • How and where are some commonly used drawing operations used?

    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.

  • How and which event to override?

    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.

  • How to make the control configurable in your application at design time?

    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.

Using the code

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:

  1. Make the custom control as lightweight as possible.

    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.

  2. Limit the use of external (non-system) libraries.

    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?

  3. Choose a suitable parent control to derive from.

    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.

  4. Design the custom control for only a limited set of functions.

    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.

  5. Try to keep the number of properties as limited as possible.

    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.

Step 1: Setting up the project

  1. Create a new C# Windows Application project in Visual Studio (2003) called "GradientPanelApp".

    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.

  2. Add a new class called 'GradientPanel.cs'.
  3. Press "ctrl-shift-B": Compile the project (!).

    It is good practice to compile whenever you change something. This will prevent having to troubleshoot errors in an early stage.

Step 2: Choose a base class

  1. Open the GradientPanel.cs.
  2. In order to work with controls, forms, and GDI+, we will need to add some namespaces to the file. Add/modify the following namespaces at the top of the file:
    C#
    // 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; 
  3. Derive the class from Panel. Change the class as follows:
    C#
    class GradientPanel : Panel
  4. Compile the project.

Step 3: Test the control

  1. It is good practice to test the control from an early stage on. Therefore, we will now add the control to the control toolbox.
  2. Go to the Tools menu, and select "Add/Remove Toolbox Items...".
  3. Click the Browse button, and browse to the location of the GradientPanelApp project. Then, select the subfolder bin\debug. This folder should contain the GradientPanelApp.exe after you compiled succesfully.
  4. The trick is to select your own executable for the component. Press OK, the 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).
  5. Open Form1.cs in designer mode.
  6. Drag the 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.
  7. Compile the project.

Note: if you are trying this in VS 2005, the control gets added to the toolbox automatically after compilation.

Step 3: Customize the control

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.

  1. Open the GradientPanel.cs source.
  2. In order to create the new property, add the following code to the class:
    C#
    // 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); 
        }
    }
  3. Notice that the property 'setter' include the 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.
  4. Because we want to draw the gradient fill on the background of the control, we will be overriding the 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:

    C#
    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();
    }
  5. Once again, compile your code.
  6. Switch back to Form1 in design mode, and notice already a gradient fill from Black (forecolor) to the control color (backcolor) from left to right (angle of 0 degrees).

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.

Step 4: Optimize your code

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:

  1. Declare the 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.
    C#
    private Brush brush = new SolidBrush(SystemColors.Control);
  2. Create a new 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.
    C#
    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);
    }
  3. Override the ForeColor and BackColor properties. In the setter of the GradientAngle, ForeColor, and BackColor properties, add the call to CreateBrush().
    C#
    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();
        }
    }        
  4. Remove the inefficient code from the OnPaintBackground method.
    C#
    protected override void OnPaintBackground(PaintEventArgs pevent)
    {
        base.OnPaintBackground (pevent);
        pevent.Graphics.FillRectangle(brush, this.ClientRectangle);
    }
  5. Initialize the Brush when the control is created. This can be done by overriding the OnCreateControl event.
    C#
    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();
    }    
  6. Compile your control and try it out in the 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.

  7. To make sure the 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().
    C#
    protected override void OnResize(EventArgs e)
    {
        base.OnResize (e);
        CreateBrush();
    }
    
    protected override void OnBackColorChanged(EventArgs e)
    {
        base.OnBackColorChanged (e);
        CreateBrush();
    }            
  8. Compile the control again, and test if it is working correctly now in the 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.

Points of Interest

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:

  • This may sound trivial, but try to comment your newly defined public properties and methods using the ///<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.
  • Use the #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).
  • Use so called 'Attributes' to set 'properties for your properties'. I did not discuss this topic here as it is more advanced. However, in the provided source code, I set attributes for my custom created properties. These attributes are used by the .NET environment at compile time, and adds additional behaviour to your code with very little effort. For instance, by setting the [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.
  • Test, test, and test. Compile often. Try to test every path of execution. E.g., for controls, a typcial situation is when you drag a new instance of the control on your form at design time. Often, the control is not rendered correctly, and some additional initialization code is needed.
  • You may experience that you want to debug the behaviour of your control at design time. Unfortunately, this is not supported. In order to get some kind of feedback of what is happening, you can put 'trace' statements in your code that display relevant information (e.g., the name of the event fired). You can do this simply by inserting the following code:
    C#
    Console.WriteLine("important debug information");

    These trace statements will display in your IDE's Output window, also at design time!

  • As stated earlier, keep your controls as simple as possible. Decompose complicated controls to smaller simpler ones. Try to keep a small 'footprint'. If your control uses a lot of external references (of non-standard libraries), all of those libraries will need to be deployed with your control! Why is this bad? Well, controls are meant to be reusable. Everytime you would want to reuse the control, you would get 'infected' with all sorts of external libraries. Therefore, try to keep references at a minimum. Instead, include the sources if possible rather than using an external reference.
  • Remember your target audience. Who are your end users? Most of the time, your end users will be programmers rather than application users. Therefore, controls should be designed to be used and refined by programmers. Make your controls configurable through properties, and extensible by firing events (not discussed in this article), and allowing overrides and subclassing of your control (not discussed here either).
  • If you decide to create more complex properties, e.g., a collection of controls, serialization of these properties is not automatically handled by your IDE. What does this mean? It means you will have to implement your own code to store the settings of your properties that were set by the programmer at design time! Luckily, if you stick to simple property types, VS2003 handles this for you and stores it in the 'Windows Form Designer generated code' region.

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.

History

Version 1.0 of the GradientPanel and AlphaLabel controls were written on September 2006. This article was published in September 2006.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Rubicon
Netherlands Netherlands
Currently Herre Kuijpers is employed at Rubicon. During his career he developed skills with all kinds of technologies, methodologies and programming languages such as c#, ASP.Net, .Net Core, VC++, Javascript, SQL, Agile, Scrum, DevOps, ALM. Currently he fulfills the role of software architect in various projects.

Herre Kuijpers is a very experienced software architect with deep knowledge of software design and development on the Microsoft .Net platform. He has a broad knowledge of Microsoft products and knows how these, in combination with custom software, can be optimally implemented in the often complex environment of the customer.

Comments and Discussions

 
GeneralLess flicker with DoubleBuffered Pin
MartinSchmidt2-May-10 22:46
MartinSchmidt2-May-10 22:46 
GeneralGreat work Pin
navalArya16-May-07 16:00
navalArya16-May-07 16:00 
GeneralConverting to 2005 Pin
Pat Dooley26-Apr-07 14:57
professionalPat Dooley26-Apr-07 14:57 
GeneralRe: Converting to 2005 Pin
Pat Dooley26-Apr-07 15:09
professionalPat Dooley26-Apr-07 15:09 
GeneralGDI resources Pin
AllanNielsen11-Sep-06 19:33
AllanNielsen11-Sep-06 19:33 
Hi,

Very tidy article, however I have one critisism, you should create and hold open GDI resources in a control EVER. Use it, dispose it. GDI resources are very expensive, only create and use it when you need to.

The Using command helps with this in VS2005:

Using myPen as new pen(color.red)
' do some drawing
End Using

Check out how MS does the drawing of their controls using "Reflector" (google it, if you don't have it).

Got a 4 from me.

Regards
Allan
GeneralRe: GDI resources Pin
Herre Kuijpers2-Oct-06 2:34
Herre Kuijpers2-Oct-06 2:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.