Getting Started

Leverage Custom Controls

Learn the basic types of custom controls, as well as how to extend them. Also, learn how to take fuller advantage of drag-and-drop within your applications.

Technology Toolbox: C#

This article is excerpted from Chapter 7, "Windows Forms Controls," of Stephen Perry's book, Core C# and .NET, with permission from Prentice Hall [2005, ISBN: 0131472275]. It has been edited for length and format to fit the magazine.

At some point, you will face a programming task for which a standard WinForms control does not provide the functionality you need. For example, you might want to extend a TextBox control so that its background color changes according to its content; group a frequently used set of radio buttons into a single control; or create a new control that shows a digital clock face with the date underneath. These needs correspond to the three principal types of custom controls: a control that derives from an existing control and extends its functionality; a control that can serve as a container to allow multiple controls to interact (a user control); and a control that derives directly from the Control class. The third type of control is one that is built "from scratch," and it is the developer's responsibility to draw its GUI interface and implement the methods and properties that allow it to be manipulated by code.

I'll walk you through how to implement each of these types of controls, as well as provide some important background information on how to manipulate and extend these same controls. I'll also show you how to take advantage of drag-and-drop controls, for which Visual Studio 2005 includes robust support.

Let's begin with the easiest way to create a custom control: extending an existing control. Specifically, let's derive a TextBox that accepts only digits. The code is quite simple. Create a new NumericTextBox class with TextBox as its base class. The only code required is an event handler to process the KeyPress event and accept only a digit:

class NumericTextBox: TextBox 
{
   public NumericTextBox() 
   {
      this.KeyPress += new 
         KeyPressEventHandler(TextBoxKeyPress);
   }
   protected void TextBoxKeyPress(object sender, 
      KeyPressEventArgs e)
   {
      if (! char.IsDigit(e.KeyChar)) e.Handled = true;
   }
}

You can add the extended control to any form once you compile it into a DLL file.

Building a custom user control is also straightforward. Think of a user control as a subform. Like a form, it provides a container surface on which related widgets are placed. When compiled, the entire set of controls is treated as a single user control. Of course, users still can interact directly with any of the member controls. Programmatic and design-time access to control members is available through methods and properties defined on the user control.

The easiest way to design a control is with an IDE such as Visual Studio .NET, which makes it easy to position and size controls. The usual way to create a user control in VS.NET is to open a project as a Windows Control Library type. This brings up a control designer window immediately. You can also access the design window in a Windows application by selecting Project | Add User Control from the top menu bar or by right-clicking on the Solution Explorer and selecting Add | Add User Control. VS.NET can speed up the process of creating a control, but it doesn't generate any proprietary code that you can't duplicate using a text editor.

Create a User Control
Let's create a control that you can use to create a questionnaire. The control consists of a label whose value represents the question, and three radio buttons contained on a panel control that represent the user's choice of answers. The control exposes three properties: one that assigns the question to the label, one that sets the background color of the panel control, and another that identifies the radio button associated with the user's answer.

Representing members as fields within the UserControl1 class is straightforward:

public class UserControl1 : 
   System.Windows.Forms.UserControl
{
   private Panel panel1;
   private RadioButton radAgree;
   private RadioButton radDisagree;
   private RadioButton radUn;
   private Label qLabel;

The full code listing for implementing properties for a custom user control contains the code for three properties: SetQ, which sets the label's text property to the question; PanelColor, which sets the color of the panel; and Choice, which returns the answer selected by the user as a Choices enum type (see Listing 1).

The user control is added to the toolbox under the Windows Forms tab automatically if you develop it as part of a VS.NET Windows application project. Simply select it and drop it onto the form. Otherwise, right-click on a toolbox tab, select Customize ToolBox, browse for the control, and add it to the toolbox.

Let's put this new control to work. Place two control instances on a form and name them Q1 and Q2:

private usercontrol.UserControl1 Q1;
private usercontrol.UserControl1 Q2;

You can set the properties in the constructor or at run time in the Form.Load event handler. If you're using VS.NET, you can set the properties at design time using the Property Browser:

Q1.SetQ = "The economy is performing well"; 
Q2.SetQ = "I'm not worried about the budget deficit.";
Q1.PanelColor = Color.Beige;

The final step in the application is to do something with the results after the questionnaire has been completed. The following code iterates through the controls on the form when the button is clicked. When a UserControl1 type is encountered, its Choice property is used to return the user's selection:

private void button1_Click(object sender, 
   System.EventArgs e)
{
   foreach (Control ct in this.Controls)
   {
      if (ct is usercontrol.UserControl1)  
      {
         UserControl1 uc = (UserControl1)ct;
         // Display control name and user's answer
         MessageBox.Show(ct.Name+" "+ 
            uc.Choice.ToString());
      }
   }
}

If you're developing an application with VS.NET that uses this custom control, you'll find that the Property Browser lists all of the read/write properties. By default, these properties are placed in a Misc category and have no description associated with them. You can add a professional touch to your control by creating a category for the control's events and properties and adding a textual description for each category member.

The categories and descriptions available in the Property Browser come from metadata based on attributes attached to a type's members. For example, this code adds attributes to the PanelColor property:

[Browsable(true),
Category("QControl"),
Description("Color of panel behind question block")]
public Color PanelColor
{
   set {panel1.BackColor = value;}
   get {return (panel1.BackColor);}
}

The Browsable attribute indicates whether the property is to be displayed in the browser. The default is true. The other two attributes specify the category under which the property is displayed, and the text that appears below the Property Browser when the property is selected.

Always keep in mind that the motive for creating custom user controls is reusability. There is no point in spending time creating elaborate controls that you use only once. As this example illustrates, such controls are most effective when they solve a problem that occurs repeatedly.

Use Drag-and-Drop With Controls
So far, I've covered how to extend basic controls with WinForms. The next step is to learn how to manipulate controls using drag-and-drop within Visual Studio. The ability to drag data from one control and drop it onto another has long been a familiar feature of GUI programming. .NET supports this feature with several classes and enumerations that enable a control to be the target and/or source of the drag-and-drop operation.

The operation requires a source control that contains the data to be moved or copied, and a target control that receives the dragged data. The source initiates the action in response to an event—usually a MouseDown event. The source control's event handler begins the actual operation by invoking its DoDragDrop method. This method has two parameters: the data being dragged and a DragDropEffects enum type parameter that specifies the effects or actions the source control supports (see Table 1).

As the mouse moves across the form, the DoDragDrop method determines the control under the current cursor location. If this control has its AllowDrop property set to True, it is a valid drop target and its DragEnter event is raised. The DragEnter event handler has two tasks: to verify that the data being dragged is an acceptable type, and to ensure the requested action (Effect) is acceptable. When the actual drop occurs, the destination control raises a DragDrop event. This event handler is responsible for placing the data in the target control.

The source control performs any cleanup operations after the DragDrop event handler finishes. For example, if the operation involves moving data—as opposed to copying data—the data must be removed from the source control.

To demonstrate these ideas, let's create an application that assigns players to a team from a roster of available players. You create Team A by dragging names from the Available Players list to the Team A list. Both lists are implemented with listboxes, and the Available Players list is set for single selection.

You select a name by right-clicking and dragging the name to the target list. To add some interest, holding the Ctrl key copies a name rather than moving it.

After you create the form and its controls, the first step is to set up the source control (lstPlayers) to respond to the MouseDown event and set up the target control (lstTeamA) to handle the DragEnter and DragDrop events:

lstPlayers.MouseDown += 
   new MouseEventHandler(Players_MouseDown);
lstTeamA.DragEnter += 
   new DragEventHandler(TeamA_DragEnter);
lstTeamA.DragDrop += 
   new DragEventHandler(TeamA_Drop);

Next, code the event handlers on the source and target control(s) that implement the drag-and-drop operation.

Select and Move Items
The MouseDown event handler for the source listbox first checks to ensure that an item has been selected. It then calls DoDragDrop, passing it the value of the selected item as well as the acceptable effects: Move and Copy. The DragDropEffects enumeration has a FlagsAttribute attribute, which means that any bitwise combination of its values can be passed. The value returned from this method is the effect that the target actually uses. The event handler uses this information to perform any operations required to implement the effect. In this example, a move operation means that the dragged value must be removed from the source control (see Listing 2).

The destination control must implement the event handlers for the DragEnter and DragDrop events. Both of these events receive a DragEventArgs type parameter that contains the information required to process the drag-and-drop event (see Table 2).

Using the Data, Effect, and KeyState members is easy enough. The DragEnter event handler uses Data.GetDataPresent to ensure the data is a type the target control can process. Similarly, the DragDrop event handler uses Data.GetData to access the data being dragged to it. The parameter to this method is usually a static field of the DataFormats class that specifies the format of the returned data. The DragEnter event handler uses KeyState to determine the status of the mouse and keys in order to determine the effect it will use to process the data. Remember that pressing the Ctrl key signals that data is to be copied rather than moved. The DragEnter event handler sets Effect to notify the source how—or if—it processed the data. A setting of DragDropEffects.None prevents the DragDrop event from firing. The code for the event handlers is reasonably simple (see Listing 3).

Create an enum with the FlagsAttributes attribute to make checking the KeyState value easier and more readable. The logical "anding" of KeyState with the value of the CtrlKey (8) returns a value equal to the value of the CtrlKey if the Ctrl key is pressed.

A control can serve as both source and target in the same application. You could make this example more flexible by having the listboxes assume both roles. This would allow you to return a player from lstTeamA back to the lstPlayers listbox. Adding the appropriate event handlers is all that's required.

The controls available for use with WinForms are both powerful and flexible. This article touches on only a couple aspects of the overall capabilities of such controls, but it should serve as a good starting point if you want to seek out more information on them.

comments powered by Disqus
Most   Popular
Upcoming Events

.NET Insight

Sign up for our newsletter.

Terms and Privacy Policy consent

I agree to this site's Privacy Policy.