devxlogo

.NET Building Blocks: Custom User Control Fundamentals

.NET Building Blocks: Custom User Control Fundamentals

With the .NET framework, you can build Windows Forms applications (those that run on your local machine) or Web Form applications (those that run in your browser). A Windows Form is simply a container for a collection of controls?buttons, fields, drop-down boxes, selectors, and so forth?that users may interact with and manipulate. This article discusses the fundamentals of such controls, including both how to consume them at design time and, in particular, how to create your own custom controls. It is not nearly as daunting as you might think at first glance to create custom controls; after you learn the principles laid out here, you will be able to make your own library of building blocks, adding your controls to the Visual Studio Toolbox right alongside the standard ones.
What You’ll Cover

  • Types of custom controls. You will learn about the different types, when to use them, and how to create them.
  • Testing custom controls. Visual Studio provides a runnable wrapper?a remarkable tool called the “UserControl Test Container.”
  • Incorporating custom controls into your applications. Understand the logistics of control libraries, loading them into your Toolbox, and manipulating them.
  • Augmenting custom controls. With some handy tips you can integrate your controls seamlessly into Visual Studio.
What You Need
  • Visual Studio 2008 or VS2005
  • C# 3.0 or 2.0 (there are a couple of spots you’ll need to adjust if using C# 2.0 in VS2005)
  • .NET Framework: 2.0 or higher

A Simple Composite User Control
In this first exercise, you will build “ClockControl,” a simple digital-clock timer control. The basic example comes from the MSDN documentation “Walkthrough: Authoring a Composite Control with Visual C#,” because it serves as a good launching point for further discussion. The MSDN walkthrough provides step-by-step instructions to create this user control, and is a useful aid if you don’t have much experience with controls, so I won’t duplicate those instructions here. The version of the example you’ll see here deviates from the walkthrough both in component naming and in coding; it’s simpler and easier to comprehend. Also, rather than having to build the example, you can download the finished code, which you can then load and run immediately.

If you do want to build it yourself, here’s a brief version of the entire procedure:

  1. Start a new project of type Windows Forms Control Library. When compiled, this will result in a DLL (library) file instead of an executable.
  2. Visual Studio automatically adds a single user control, represented by UserControl1.cs and its associated designer files. Rename the UserControl1.cs file in Solution Explorer to ClockControl.cs. Visual Studio will ask if you want to rename associated objects when you do this; answer “Yes”.
  3. Just as you would with a Windows Forms application, add a standard Label control and a Timer component to the designer surface.
  4. Set these important properties of the Timer component:
    • Because the clock displays at a resolution of one second, the timer activity should match: set the Interval property to 1000 (milliseconds).
    • Turn on the Timer component by setting the Enabled property to True.

The MSDN walkthrough gives the code in smaller chunks, but because it’s such a short program, here’s the whole thing:

   namespace ControlLib   {      public partial class ClockControl : UserControl      {        public ClockControl()        {          InitializeComponent();        }           public Color ClockForeColor        {          get { return clockLabel.ForeColor; }          set { clockLabel.ForeColor = value; }        }           public Color ClockBackColor        {          get { return clockLabel.BackColor; }          set { clockLabel.BackColor = value; }        }           protected virtual void timer1_Tick(object sender, EventArgs e)        {          clockLabel.Text = DateTime.Now.ToLongTimeString();        }         }   }
Author’s Note: I streamlined and shortened the code, and introduced a few minor stylistic changes that make it cleaner. These include:

  • Naming the control “ClockControl” instead of “ctlClock” to avoid abbreviations and to use the suffix to indicate what the type is?a control.
  • Naming the label “clockLabel” instead of “lblDisplay,” signifying that the object is a Label and its purpose is to display the clock.
  • Eliminating redundant color capture in the foreground and background color properties, instead assigning and retrieving them directly from an embedded control.

Testing a Control

?
Figure 1. UserControl Test Container: Visual Studio lets you perform isolated “containerized” testing of your custom controls, in this case the ClockControl.

When you create a Windows Forms Application project and compile it, Visual Studio generates a binary executable file (named .exe) that you can execute like any other Windows program. When you create a Windows Forms Control Library project and compile it, however, Visual Studio generates a dynamic linked library (DLL) file. DLLs are also binary files, but they’re not stand-alone executables; they must be referenced by some executable file for the code in them to actually run. The seemingly natural way to test a control, then, is to create a sample Windows Forms application, place your control on the form, and then run the application. In fact, that is what you had to do in the ancient days of .NET 1.1 and earlier. Starting with .NET 2.0, however, Visual Studio has provided a UserControl Test Container that saves you all that work. Using it is as simple as running an application by pressing F5 or Ctrl-F5 in Visual Studio. When you do this inside a control library project, you launch the test container (see Figure 1). Observe that the clock updates every second with the current time, so the test container is truly running your control just as if it were in a Windows Forms application (which it is).

As you will observe from Figure 1, the UserControl Test Container provides quite a bit more than you would get if you just placed your control on a test form and ran it. First, it lets you select any user control in your current library from the drop-down control at the top of the form. Because there is only one user control in your library so far (ClockControl), it automatically selects that control and runs it. If you had more than one, you could invoke whichever one you wanted to test by selecting it and clicking the Load button.

The second major benefit of the UserControl Test Container is that it gives you access to all your control’s public properties.

Some of those properties cannot meaningfully be changed in the test container, primarily those that describe the relationship between your control and its parent form. So you cannot, for example, change the Location property from its value of (0, 0). This rule does not entirely hold true, though, as you could change the Dock property value from None to Fill?and you would see the background of the control expand to fill the preview pane.

?
Figure 2. Property Editor: Activating the value field of a property automatically opens an appropriate property editor?in this case, a color picker palette.

Probably the most interesting properties are those your control explicitly exposes as public properties (ClockBackColor and ClockForeColor for the ClockControl). Assuming you have the properties grouped by category, by default your properties will be in the miscellaneous (Misc) category at the bottom of the list. Clicking in the property value for a color enables the field and exposes a dropdown button (see Figure 2). Opening the drop-down reveals a property editor for the property’s type?a multi-tabbed color picker palette in this case. In Figure 2, you can see the colors selected for both the foreground and background, with the color palette exposed. Changes to these property values are reflected in the preview pane immediately.

Now that you’ve implemented and tested the ClockControl, you are free to use it like any standard .NET control. You need only load it into the Toolbox (which you will learn about shortly). Once in the Toolbox, you can drag it onto the designer surface just like any built-in control such as a Button or CheckBox. When you place a user control on your form, it behaves like any other standard control: selecting it in the designer shows its properties in the Properties window, where you can change the ClockBackColor or ClockForeColor or other standard properties. You could also place your user control into another user control (just as you placed a Label control into the ClockControl), or you can extend the control through inheritance, discussed next.

Creating an Inherited User Control
Standard .NET controls and components are building blocks that you use to create applications or other building blocks. In the previous section, you created a new building block called ClockControl. In this section, you will build yet another building block, based on the ClockControl. Add a new item to your project and the Add New Item dialog opens, as in Figure 3. Select the “Inherited User Control” template and name it “AlarmClockControl.”

?
Figure 3. Creating an Inherited Control: When you create an Inherited User Control project, you’ll get a second dialog where you select the control from which to inherit.

 

?
Figure 4. Impact of a Completed Control: When you create the AlarmClockControl inherited user control, Visual Studio adds the already-completed ClockControl to the Toolbox automatically, and you can see its inherited, exposed properties in the Properties window.

Upon pressing Add, you will get an additional dialog box that is specific to inherited controls. This dialog asks you what control you wish to inherit from. By default, it lists all the controls in the current project. In this case, you have only one so far, the ClockControl; choose that one.

When you complete the dialog, the user control designer opens the new AlarmClockControl. On the left, in the Toolbox, you have a new category of ControlLib components listing the one and only control you have added so far, ClockControl (see Figure 4). So just like a Button or a Label, you could drag a ClockControl onto the designer surface to add it to your application or to build another composite control. Because you’re building an inherited control?inheriting from the ClockControl?you need do nothing further. Over on the right side, you’ll see the familiar exposed properties, shown in the Misc category here in the designer just as they were in the UserControl Test Container.

When the designer opens, it automatically selects the entire user control, so you’ll see the properties of the control itself in the Properties window. However, for an inherited user control the designer lets you explore the hierarchy of your control simply by clicking internal components. For example, click on the clock display and the clockLabel becomes the selected component. You can then see its properties in the Properties window, albeit as read-only (see Figure 5).

?
Figure 5. Inherited User Control Internals: Visual Studio lets you see (but not modify) properties of child controls in the parent object by selecting them.

Continuing in parallel with Microsoft’s walkthrough, switch to the code view and add an AlarmTime property of type DateTime and a Boolean AlarmSet property. The default generated code with the added properties is shown below:

   namespace ControlLib   {      public partial class AlarmClockControl :          ControlLib.ClockControl      {         public DateTime AlarmTime { get; set; }         public bool AlarmSet { get; set; }            public AlarmClockControl()         {            InitializeComponent();         }      }   }   

The preceding code sample takes advantage of a new feature in C# 3.0 called auto-implemented properties. (See what’s new in C# 3.0 here.) For a property that follows the standard pattern of a backing variable, a setter that assigns to the backing variable, and a getter that retrieves from it, you can now use a much shorter notation: no backing variable declaration and no bodies for the getter and setter, as shown for AlarmTime and AlarmSet. If you are using Visual Studio 2005 or C# 2.0, you will need to use the more verbose forms for the getter and setter accessors.

After declaring the two properties, go back to the designer and add both a label (named AlarmLabel) and a button (named alarmOffButton). Finally, you need to override the base ClockControl timer’s Tick event handler to flash an alarm at the set time, and you’ll need to add an event handler for the new Button’s Click event. The complete code sample is shown next with the new portions in bold:

   namespace ControlLib   {      public partial class AlarmClockControl : ControlLib.ClockControl      {         public DateTime AlarmTime { get; set; }         public bool AlarmSet { get; set; }            private bool colorTicker;            public AlarmClockControl()         {            InitializeComponent();         }            protected override void timer1_Tick(            object sender, System.EventArgs e)         {            base.timer1_Tick(sender, e);               if (AlarmSet)            {               if (AlarmTime.Date == DateTime.Now.Date                  && AlarmTime.Hour == DateTime.Now.Hour                  && AlarmTime.Minute == DateTime.Now.Minute)               {                  // If the date, hour, and minute                   // of the alarm time are the same                  // as the current time, flash an alarm.                   alarmLabel.Visible = true;                  alarmLabel.BackColor = colorTicker ?                      Color.Blue : Color.Red;                  colorTicker = !colorTicker;               }               else               {                  // After the alarm has sounded for a minute,                   // hide it again.                   alarmLabel.Visible = false;               }            }         }            private void alarmOffButton_Click(object sender, EventArgs e)         {            AlarmSet = false;            alarmLabel.Visible = false;         }      }   }
?
Figure 6. Executing the AlarmClockControl: The test container shows the alarm going off when the current time reaches the set time (the value of the AlarmTime property).

To test the AlarmClockControl, press F5 and the test container opens (see Figure 6). Typically, you’d set the AlarmTime and AlarmSet properties in code, but to test the alarm, set the properties manually in the test container window. Select the AlarmTime property and set it a minute or two ahead of the current time. Change the AlarmSet property to True to activate the alarm clock. At the designated time the alarm will begin to flash. Press the Disable Alarm button to deactivate it.

Using the AlarmClockControl
To complete the real-world process of building a user control, the final step is to include the AlarmClockControl in an application. Create a new Windows Forms Application project in the same solution called TestAlarmClock. Visual Studio opens the designer with a blank form and (because the new project exists in the same solution as the user controls) automatically populates the Toolbox with the ClockControl and the AlarmClockControl controls (see Figure 7).

?
Figure 7. Auto-Populating the Toolbox: A new project in a solution automatically includes available controls from other projects in that solution in the Toolbox under ControlLib Components.

Drag an AlarmClockControl and a standard DateTimePicker control onto the designer surface. To wire these up, you’ll need an event handler for the DateTimePicker. Double-click the DateTimePicker to create an event handler for its default ValueChanged event. Enter the two lines of code highlighted below into the event handler method. Note that these two lines of code do programmatically precisely what you did manually in the UserControl Test Container in the previous section. Here’s the entire program (not counting the designer-generated code):

   namespace TestAlarmClock   {      public partial class Form1 : Form      {         public Form1()         {            InitializeComponent();         }            private void dateTimePicker1_ValueChanged(            object sender, EventArgs e)         {            alarmClockControl1.AlarmTime = dateTimePicker1.Value;            alarmClockControl1.AlarmSet = true;         }      }   }
?
Figure 8. The TestAlarmClock Application: The completed application UI is on the left; an exploded view, showing the nested components is on the right.

When you execute the program, you turn on the AlarmClockControl just by entering an alarm time value. (Note: Don’t open the calendar in the DatePicker; just select one component of the time and type in a new value.) You disable the alarm by pressing the Disable Alarm button. Figure 8 shows the completed application UI in two different forms. The flat form at left shows what you see in the visual designer. The exploded form at right illustrates the hierarchical components that you have just assembled:

  • The form contains a DateTimePicker and an AlarmClockControl.
  • The AlarmClockControl contains a Button and (by inheritance) a ClockControl.
  • The ClockControl contains a Label and a (non-visible) Timer.

Figure 8 provides a visual aid to understanding what properties are accessible where. At any given level, you can access exposed properties only for those controls and components that are immediate children or inherited controls. In other words, from the form level, you can set public properties of both the DateTimePicker and AlarmClockControl. The AlarmClockControl defines AlarmTime and AlarmSet properties, but because it inherits from ClockControl, you also have access to ClockControl’s public properties: ClockForeColor and ClockBackColor.

Another Look at the Test Container
If you are an aficionado of test-driven design, you know that unit testing of UI components always presents its own set of difficulties. The UserControl Test Container of Visual Studio exemplifies a good intermediate step between automated unit testing and manual UI testing at the application level. The test container is still manual UI testing, but it allows you to test a single unit (control) rather than having to test an application in its entirety. I call isolated unit testing container testing.

The AlarmClockControl that you built provides sufficient means to exercise the control, via the AlarmTime property, the AlarmSet property, and the Disable Alarm button. However, AlarmClockControl is probably in the minority; generally user controls will not provide sufficient means for meaningful testing in isolation. Therefore, you must usually instrument your control for container testing, just as you have to instrument non-UI code for unit testing. For this discussion, take a look at the ProgressBarMessager control running in the test container (see Figure 9). This control comes from my .NET open source software collection (get the API, or download the control collection). The purpose of this control is to enhance a standard ProgressBar to support progress messages that appear sequentially beneath the bar and can provide useful feedback during a lengthy operation. The ProgressBarMessager control also includes a button so users can cancel the operation if desired.

?
Figure 9. The ProgressBarMessager Control: Unlike the AlarmClockControl, this control does not run autonomously; instead, it’s instrumented with an extra ContainerTest property for the express purpose of container testing. Enabling that property causes an action that allows the control to be exercised in the test container.

The ProgressBarMessager has an added property called ContainerTest that defaults to false (see Figure 9). The intent is that changing the ContainerTest property to true should reveal additional controls that are necessary and sufficient to enable testing this control in isolation, without having to first embed it in a master form and hook it up manually. In this case, when you set ContainerTest to true, the control responds by displaying a hidden Step button. This button performs one step in the natural action of a progress bar. For example, the sequence of frames in Figure 10 begins at the upper left with the Step button exposed; subsequent frames in the image result from pressing the Step button repeatedly.

?
Figure 10. Testing the ProgressBarMessager: Repeated Step button presses let you test the progress bar, causing additional messages to appear. The control expands vertically up to a preset size, and then scrolls.

The first press displays an initial message with a small green arrow, indicating it is the current step being executed. That corresponds to a call to Reset() followed by a call to ReportInitialMessage(). Subsequent Step presses correspond to calls to the PerformStep() method. The second press, therefore, changes the arrow in front of the first message to a check and adds an elapsed time for that step. It then adds the second message (“step 2”) and an arrow indicating that is now the step being executed. Continued presses of the button add more steps. Note that the control expands vertically to contain a property-selectable number of messages (10 by default). Continued presses beyond that simply scroll the messages, as the final frame in Figure 10 shows.

When to Test
Testing a control embedded in a complete application or in the UserControl Test Container are both aspects of run-time testing. But one of the cleverer features of Visual Studio is something you might never notice: the ability to execute your code at design-time. You may have already noticed this in action when building the examples in this article. For example, the AlarmClockControl showed its parent control (ClockControl) in the designer with a time display that updated every second?without executing anything! That clock, as you know, gets updated by your timer tick event handler, so it is clearly running your code.

You aren’t limited to passive observation as your code executes; you can interact with the design-time execution of your program by manipulating properties. For example, if you change the AlarmClockControl’s ClockForeColor property in the Properties window of the designer, you are in fact accessing the code that manages that property:

   public Color ClockForeColor   {      get { return clockLabel.ForeColor; }      set { clockLabel.ForeColor = value; }   }

Specifically, changing the property invokes the ClockForeColor setter, which, in this case, executes just one line of code: the assignment to the ForeColor property of the underlying label. But you are not limited to simple assignments, as evidenced in the aforementioned timer tick event handler.

So what about .NET controls? Placing a Label on your designer surface, and then changing the BackColor property of that label does the same thing; it executes the setter property of the Label control. In other words, Visual Studio produces the design-time display of a control by executing the code that renders of that control. For further exploration of this topic, see the MSDN topic “Extending Design-Time Support.”

Making Your User Controls Available
As you have seen, controls defined in projects in the current solution appear in the Toolbox automatically. According to MSDN (Walkthrough: Automatically Populating the Toolbox with Custom Components, this is true, but your controls:

  • Must implement IComponent
  • Must not have the ToolboxItem attribute set to false
  • Must not have the DesignTimeVisible attribute set to false

As far as using controls in other solutions, which is typically where you want them, there are a number of possibilities. With .NET Framework 3.5, the preferred technique is described here: How to: Package and Install Custom Controls for the Toolbox. However, if you wish to avoid the overhead of packaging your controls for the Visual Studio Content Installer, you can still use older methods from.NET 2.0 and .NET 3.0. For community-packaged controls, see How to: Add Online Controls to the Toolbox. I could not find a reference in MSDN for a simple installation, but here’s the technique.

Simple Toolbox Install
First, right-click on the Toolbox, select “Choose Items?” and you will see the Choose Toolbox Items dialog shown in Figure 11. This includes all the standard components in your Toolbox. To add the controls in your own library, just select the “Browse?” button. That will bring up a standard file dialog filtered on .dll and .exe files. Navigate to your own .dll file and choose Open. Your controls will then be added back to the left dialog in the figure, sorted, and automatically checked to be included. Uncheck any that you do not wish to include, and then press OK to close the dialog. Your controls will now appear in the Toolbox in the General category.

?
Figure 11. Adding Controls to the Toolbox: Selecting “Browse?” lets you select a library whose controls are then added to the original dialog box. You may select/unselect any you wish. The final set then populates your Toolbox.

 

Author’s Note: I would have thought that you could specify an attribute on a control that would add it to a particular category in the Toolbox, but I have not found any way to do this. If you do, please drop me a line. While discussing the Toolbox: Have you ever wondered whether a control exists that accomplishes something you need to do, but you couldn’t tell which control it might be? The Windows Forms Programming Guide provides a thorough list of Windows Forms Controls by Function detailing all the common controls, plus a further link to a complete list so you can easily search for what you might need.

Visual Studio also adds a reference to your library to the References for your current project. That is necessary and sufficient for manipulating controls in the visual designer; however, if you also wish to reference library classes in your code you need to add an appropriate using statement, such as using ControlLib;. The system will know what ControlLib is because of the reference that was just added.

Adding components from a library to your Toolbox is a global action in Visual Studio. That is, if you close your current solution and create a new one, the ClockControl and AlarmClockControl will still be in the Toolbox?but not in your project’s references. Visual Studio helps take care of this apparent inconsistency?as soon as you drag a ClockControl from the Toolbox onto your new form, it automatically adds the reference to this new project as well.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist