From the April 2002 issue of MSDN Magazine.
Developing Compelling User Controls that Target Forms in the .NET Framework
|David S. Platt|
|This article assumes you're familiar with Visual Basic .NET or C#|
|Level of Difficulty 1 2 3 |
|Download the code for this article: WinForms.exe (305KB) |
|SUMMARY In the beginning, writing controls meant dealing with Windows messages. Then came Visual Basic controls, which introduced methods, properties, and events. Later, ActiveX controls, which ran atop COM, became popular. While each innovation in control writing brought more flexibility, nothing has matched the versatility of the new .NET Windows Forms controls and Web Forms controls.|
This article, the first of a two-part series, introduces the reader to Windows Forms, beginning with their inheritance from one of the .NET CLR base classes, which makes control creation much faster than before. Control programming is illustrated through the development of a login control. The equally flexible Web Forms controls will be covered in Part 2.
|icrosoft® .NET provides excellent support for the development and use of controls. The inheritance-based architecture of the .NET Common Language Runtime (CLR) makes controls easier to write than before, and because all developers use the same base implementation for both controls and containers, they are more widely compatible than ever.|
Microsoft .NET provides two types of controls: Windows® Forms controls and Web Forms controls. You use the Windows Forms controls when writing a forms-based program to run on a Windows-based desktop. This is conceptually similar to what you do today when you use ActiveX® controls on a Visual Basic® form. Web Forms controls represent an entirely new idea, extending the successful idea of controls and Forms to Web pages served up by ASP.NET. This article, the first in a two-part series, deals with Windows Forms controls. In a later issue of MSDN® Magazine, I'll bring you the second part, which explains Web Forms controls.
Controls Then and Now The concept of a control, a reusable piece of software functionality that (usually) provides a visible interface, has been fantastically successful for Microsoft. Providing application programmers with controls such as buttons for invoking commands and textboxes for entering strings was a radical idea when it debuted in Windows 1.0 in 1985. Programmers didn't have to write the code to accomplish these repetitive tasks, so development was faster. Since all programmers shared the same buttons and textboxes, these controls looked and worked the same from one application to the next. The user experience became easier and richer, and developers could justify a large investment in writing rich and powerful controls.
While the concept has remained constant over the past 15 years, as you know, the implementation of controls in the Windows environment has gone through at least three radical changes. The original Windows controls were implemented as child windows that communicated with their containers by means of Windows messages. This worked well as a proof of concept, but proved unwieldy to program. Visual Basic custom controls (VBX controls), introduced a decade ago, provided the now familiar concept of methods, properties, and events. VBX controls lived on forms and were much easier to program than the original child windows.
When Windows switched from 16 bits to 32, Microsoft switched to OLE custom controls (OCX, later renamed ActiveX controls). These controls looked and felt like VBX controls, using methods, properties, and events, but internally they were built on the foundation of COM. ActiveX controls were difficult to write because the operating system didn't supply any infrastructure for them, meaning you had to write every bit of it yourself. Even if developers had the skill set to write ActiveX controls, the volume of infrastructural code that was required meant that there was no way to get it all correct.
Various tools soon emerged, however, that prefabricated the common portions of their infrastructure, such as the MFC COleControl class or the Active Template Library's CComControl. But even here, all the different tools' base control implementations differed in very subtle and hard-to-fix ways. Furthermore, since COM didn't support inheritance, controls had no commonality of implementation. This meant that there was no good way for a purchaser of an existing ActiveX control to extend or modify its functionality.
This brings us up to the present, where controls development just got so much easier.
Start with Inherited Functionality As you've no doubt heard by now, the .NET CLR is a standardized set of base functionality for every type of Windows-based program or component. Every source code language that targets the CLR compiles to the same intermediate language (MSIL), which is then compiled into object code by a just-in-time compiler. This means that every programmer's implementation uses the same language at run time, regardless of the source language in which it was originally written. Developers reuse code from the system libraries via the object-oriented programming technique known as inheritance. To inherit software functionality, you write a new object class, called a derived class, and tell the compiler that it inherits from another class, known as the base class. The compiler will then include all of the base class's functionality in the derived class by reference. Think of it as cutting and pasting without actually moving anything.
Developers who write Windows Forms controls (hereafter simply "controls") will find that they inherit a control's base functionality from one of the .NET CLR base classes. The choice of base class is the first design decision to make in developing a control. It determines the functionality that you inherit, and thus the amount that you need to write yourself. I'll describe each of the three basic choices you can make.
Inheriting from the CLR base class System.Windows.Forms.Control is the simplest and most basic option. Your control inherits a set of default properties and methods that many controls need, such as foreground and background colors, along with the ability to add your own custom ones. It inherits a set of default events, such as Click, and of course you can add your own custom events as well. It inherits the ability to be hosted by a form and to live in the toolbox for use by form designers. You don't inherit any type of code for rendering the control's appearance, but you do inherit a convenient place to put your own code. If you are used to writing ActiveX controls you will find this the most familiar approach, particularly if you use MFC or ATL.
Inheriting from an existing control is another available option. For example, you could inherit from System.Windows.Forms.TextBox, which itself derives from System.Windows.Forms.Control. You choose this option if you like the functionality of an existing control in general, but want to modify it or extend it in some way. For example, you might use this option to write a textbox control that accepts only numbers. Developers who remember subclassing a Windows control (replacing the window response function so as to filter and modify incoming Windows messages before passing them to the control's original response function, or occasionally swallowing them) will find this approach familiar. You couldn't really do it with an ActiveX control because COM did not support inheritance, but with .NET it's now quite easy. You inherit all the methods, properties, and events of the existing control, as well as its painting behavior. You write only the code for the behavior that differs from the existing control. You can also do it for non-system controls as long as the developer has made its methods overridable, which I'll discuss later in this article.
Finally, you can choose to inherit from the CLR base class System.Windows.Forms.UserControl. This class derives from System.Windows.Forms.Control. Your control will essentially be a miniature form containing other controls, thereby allowing you to write more complex pieces of prefabricated functionality. For example, you can produce a control that contains textboxes for a user ID and password, labels identifying each textbox, buttons to signal when the user has finished entering data, and validation code to ensure that each control contains legal data. You could then reuse this control in a number of different applications that require a user to log in. Each login screen would share the same user interface, and you wouldn't have to write the code every time. The internal "constituent" controls paint themselves, and you can also write additional custom painting code if you so desire. If your new control can be expressed even partially in terms of other existing controls, this is probably the best choice for you.
I'll discuss each of these three options in the remainder of this article and provide sample programs that demonstrate their functionality. (You can find the sample code at the link at the top of this article). The code samples used in the figures of this article are mostly written in Visual Basic, but I've written all the sample code in both Visual Basic and C#, and I've shown C# in the figures wherever it differs significantly from Visual Basic. I stand by my statement that these languages resemble each other far more than devotees of either language would like to admit.
Deriving from System.Windows.Forms.Control As I always do when learning or demonstrating (funny how those two are often the same thing) a new piece of software, I wrote the simplest example I could think of. To learn .NET controls, I decided to write a blinking label control. You can see a client program containing this control in Figure 1. While a static image in the magazine can't show it, you'll find that if you download and run the control and its client, the label switches color from black to the form's background color (hence seeming to disappear) and back again every second. It fires an event to its container every time it changes color. I've programmed the container to emit a beep every time the control changes from background to black.
Figure 1Blinking Label Control
To start my control development project, I wanted to create a new project containing a class that derives from System.Windows.Forms.Control. This may not have been seen as a common project type, as there is no way of accomplishing it directly in Visual Studio® .NET. You must first create a project with a Class Library or User Control type to generate the necessary files for building a .NET DLL assembly. Then right-click on the project and pick Add New Item from the context menu, at which time you will see the dialog box that is shown in Figure 2. Select the Custom Control template.
Figure 2Add New Item
You can now delete the original project source code files if you want to. Alternatively, you can simply change the base class on the original generated class to System.Windows.Forms.Control. In this case, you will have to manually add the handler for the base class's OnPaint event, as I'll describe.
Next, I decided to add the custom properties that I wanted my control to expose. In this case, I added a property called BlinkInterval, which specifies the time interval in seconds after which my control changes color. I added this to my control's source code file in the same manner as I would for any other property in any other .NET component. Users of Visual Basic type it in, whereas C# users have a wizard to help them out. The code for this property is shown in Figure 3.
I also added another property called BlinkOffColor, which is the color that the control user wants the control to display during its time off cycle. I also added to the control's constructor my own code to start a timer and set its expiration to the BlinkInterval property set by the user. It doesn't demonstrate anything useful about controls, but so readers won't think I'm using the Dark Side of the Force, I show this code in Figure 4.
A custom control is responsible for rendering its own appearance. Since every control is different, Microsoft couldn't usefully provide any sort of meaningful default drawing code. You place the code that draws the control's appearance in the OnPaint event handler, which corresponds to the Windows WM_PAINT message (remember Windows messages?). Your control receives this event when the operating system's window manager detects that a portion of your control has been invalidated (marked as needing repainting) by the movement of other windows on the screen, or by program control from within your application as you saw in the timer function in Figure 4.
The control's base class contains a default handler for this event, which does nothing. You must override the base class's handler and place your own code there to make the control look the way you want it to. If you generate a custom control for the project, a handler for this event gets added automatically to your code. If you change the base class of a UserControl project, you'll have to add it yourself. It contains no code when you first add it, so if you simply build an empty control and add it to a form, you won't see anything there and you'll think it's broken. It isn't; you just haven't added any of the necessary drawing code yet.
The OnPaint handler for my blinking label control is shown in Figure 5. The event handler receives a single parameter, an object of class System.Windows.Forms.PaintEventArgs. This object contains two read-only properties that are of interest here. The first, a rectangle named ClipRectangle, specifies the coordinates, relative to the control itself, that require painting. Large controls, or those with slow drawing algorithms, can check this rectangle so they don't bother trying to repaint portions of the control that don't need it. My simple control here ignores it.
Of more interest is the property named Graphics, which is an object of class System.Drawing.Graphics. This object contains methods that allow your program to access the GDI+ code of .NET, which produces output on the screen. A full examination of GDI+ requires an article in itself, if not a whole book, so I won't go into it in any detail here. In this case, I use its DrawString method to paint my label's text string onto the control surface. The variables m_BlinkOnBrush and m_BlinkOffBrush represent two brushes that I've created elsewhere in the program. m_BlinkOnBrush contains the color that the designer has set in the control's properties to use when the control is in the "on" portion of its blink cycle; m_BlinkOffBrush contains the color when it is in the "off" portion of the cycle.
When I derived this control from the base class, the control inherited all the methods and properties of that base class. Even a list would be beyond the scope of this article; suffice it to say that there are a lot of them. I've used several of them in this control. Looking at the OnPaint handler method, every variable you see with the prefix Me is a member of the base class. You don't have to type the Me first; your code will work just fine if you omit it. In C#, it would be "this," and you could likewise omit it. But I showed it in this example so that you can see at a glance the properties I've used that belong to the base class.
I used the base class property called Text for my label's text instead of creating a new property. The base class also contains a property called Font, which I used in the same manner. It also contains a property called DesignMode, which tells me if my control is being used in a designer like Visual Studio, as opposed to being used in an actual client app. In the former case, I turned off the blinking by always painting with the same brush, otherwise it would drive anyone crazy looking at it while programming. In another part of my control (not shown) I used the base class property ForeColor to create the brush used as the color of the text during the "on" part of the blink cycle.
I wanted my control to fire events to its container. This is how the control notifies the container that something interesting has happened to it. The .NET Framework contains a built-in eventing system which any component can use. A component assembly contains metadata identifying the events that it knows how to fire. The client of that component, in this case the form containing the controls, reads the list of possible events and adds handler functions for the ones that it wants to hear about.
The control's base class already contains many events, such as Click, which I didn't use in this sample. But I wanted to add at least one custom event so you can see how the mechanism works. Adding an event to my control was quite easy. In Visual Basic, I simply declared the fact that my control fired an event and what the parameters of the event were, as you can see here:
It's slightly different in C#, where I needed one line declaring what the handler function for the event should look like and a second line saying, "OK, here's an event that uses that handler," as you can see here:
Public Event BlinkStateChanged(ByVal UseBlinkOnColor As Boolean)
You'll have to agree that both of these are trivial. In order to fire the event to any interested listeners, Visual Basic requires the use of the keyword RaiseEvent:
public delegate void BlinkStateChangedHandler (bool UseBlinkColor) ;
public event BlinkStateChangedHandler BlinkStateChanged ;
C# simply requires you to call the declared event's handler function with the line:
' Fire the blink event to the control's container, in case it cares.
That's all I needed to do to write my control. Next, I wanted to use it in a client program. I created a normal solution containing a Windows Application project. With the application's main form open, I wanted to add my new control to the toolbox so that I could use it on my form. To do that, I right-clicked on the toolbox and selected Customize Toolbox from the context menu. This brought up the dialog box that you see in Figure 6. You surf to your new control, select the DLL and check it, and it appears in your toolbox. You can now add it to a form, set its properties, write code that calls its methods, and write handler code for its events.
BlinkStateChanged (m_UseBlinkOnColor) ;
Figure 6Customize Toolbox
Extending an Existing Control Next, I wanted to experiment with writing a control that derived from an existing control. I decided to extend the system textbox control by writing code that would validate an e-mail address, making sure that it contains an @ sign and a dot. Every time the text in the box changed, I examined the new text and turned the background color pink when it didn't contain what appeared to be a valid e-mail address, and turned it green when it did. You can see a client program containing this control in Figure 7.
Figure 7E-mail Validator
Writing this control was almost absurdly easy (see the code in Figure 8). I generated a solution containing a custom control project as described in the previous section. I then changed its base class to System.Windows.Forms.TextBox, and like magic, my control now knows how to do everything a textbox does.
Next I needed to write the code to modify how my control's behavior differs from that of the base class. In this case, I overrode a base class method called OnTextChanged. You can see a list of possible functions to override and add the override handler in the dropdown lists in Visual Basic. In C#, you need to bring up the Class View window to search the class hierarchy, go under the form name to Bases and Interfaces, then right-click on the method name and finish by selecting Add | Override from the context menu.
In my overriding function, you can see that I first forwarded the call to my base class by calling MyBase.OnTextChanged. The same holds for C# except that the keyword is base rather than MyBase. I did this because I wanted to add my validation functionality to the control, not replace what the control already did. If I wanted to replace what the textbox normally does in this case, then I wouldn't make this call at all. It was entirely up to me to choose which behavior I wanted to use, or which behavior I thought my customers would want to buy, and to write my override code accordingly.
When you are writing override code, it is important to determine what the base class method does in order to decide whether to replace it or piggyback on it, and in the latter case, whether your code should go before the base class call or after. You can't just omit the call, run a quick check to see if the sky falls in, and leave the call out if it doesn't. You have to look it up in the documentation, which in this case reads, "Notes to Inheritors: When overriding OnTextChanged in a derived class, be sure to call the base class's OnTextChanged method so that registered delegates receive the event." This means that if my control's container was also listening for the TextChanged event, which it has every right to do, then it wouldn't receive that event if I hadn't forwarded the call to the base class. This is very important to keep track of.
It is important to remember that the author of the original control has the final say over whether you can override any particular method. If a method is declared with the keyword Overridable in Visual Basic or virtual in C#, then a derived class can override it; but if it isn't, then the derived class can't. For example, the base class method TextBox.Clear is not declared in this manner, so you cannot override it. You can see which methods are overridable by using the Class View pane of Visual Studio, as shown in Figure 9. If you select a method in the top pane, the property CanOverride is shown in the bottom pane.
Figure 9Overridable Property
When you are designing your own controls, you will need to decide which of your custom methods can be overridden and which cannot. I've always leaned toward making everything overridable. Whenever I ask control developers why this or that method is not overridable, they invariably reply, "Why would you want to do that?" And my answer is, "You can't possibly know everything that every one of your customers might ever want to do. I'm over 21 years old, and unless my overriding a method might inadvertently hurt an innocent bystander and expose you to potential liability, you shouldn't be telling me what I can and can't do. Therefore all methods should be overridable."
Continuing with my overriding function, you can see the business logic for checking the control's text in the base class property Text and setting the background color in the base class property BackColor according to its content. That's all I needed to do to write a new control that inherits from an existing control. I couldn't do that before .NET, and I think it's a cool thing to be able to do. You won't have to live with controls that almost work; you can use inheritance to derive a new one that does exactly what you want it to do.
Writing a User Control One of the first questions students would ask me when I taught them to write ActiveX controls was whether one control could contain another control. You could do it, but it took a lot of work and was creaky when it ran. This is a useful feature, however, and .NET supports it natively through the class UserControl. A UserControl is a control that contains its own little form on which you can place other .NET controls, either standard or custom. Since UserControl derives from the system base class Control, you inherit all the base class functionality that I described in the preceding sections of this article.
I wanted to write a simple UserControl, which meant that I had to think of a business purpose that could be usefully served by an agglomeration of standard controls. I decided to write a login control that contained textboxes for the user to type in his ID and password, labels for explaining what goes where, OK and Cancel buttons for signaling the users intentions to the container, and an ErrorProvider control for signaling errors. A screen shot of a client program containing this control is shown in Figure 10.
Figure 10A User Control
A UserControl can do anything that you can do in the standard Control class, as the former derives from the latter. While the wizard does not generate an OnPaint event for a UserControl, you can easily add it yourself and put in it whatever code you want to be executed. To demonstrate this, I added code to draw a rectangle around the control's border, as you can see in Figure 10.
A UserControl exists primarily to hold the controls it contains, which are called constituent controls, and to write logic tying their operations together. After I generated my solution and project, I used the standard Visual Studio designer and toolbox to drag controls to my UserControl, just as if it were a plain old form. It's easy, it's fast, and you're already used to it. You can then write whatever glue code you want to tie them together.
In my sample program, I put in a handler for the OK and Cancel buttons' Click events. With the OK button, I checked to make sure that both textboxes contained data. If not, I set the ErrorProvider control to signal the user that the textbox cannot be empty. If both textboxes contained data, then I fired a custom event (that I added, as shown earlier in this article) to notify the container that the user has clicked the OK button. This event passes two parameters containing the user ID and password that the user has entered, as shown in Figure 11. In the case of a Cancel button, I fired a second event containing no parameters to notify the container that the user has clicked this button.
The constituent controls of a UserControl are private. Actually, they are declared as Friend, which means that any code in the same assembly can access them, but code outside the assembly cannot. On the one hand, this is good because it means that you don't have to worry about some application designer messing with constituent properties that your UserControl needs for its business logic. On the other hand, if you want the designers that use your UserControl to have access to any of the constituents' properties or methods, then you must write code to expose them through accessor functions or properties on the containing UserControl.
In the sample that I've written, the UserControl contains a property called BothTextBoxesBackColor. I've written the code to set the background color of both constituent textboxes to the value of this property on the containing UserControl:
Its value defaults to the textbox class's own background color, but the designer of the sample client app has set it to a tasteful light yellow. The control developer (me) has decided that in this control, both textboxes will have the same background color no matter how badly the form designer (also me) wants a different one.
Public Property BothTextBoxesBackColor() As System.Drawing.Color
Set(ByVal Value As System.Drawing.Color)
m_BothTextBoxesBackColor = Value
TextBox1.BackColor = m_BothTextBoxesBackColor
TextBox2.BackColor = m_BothTextBoxesBackColor
If this example were a production control, it might be nice to add a property to the UserControl that would allow the container to set the initial strings in the User ID textbox. This would allow the application designer to remember the user ID from one session to the next and automatically show it next time, thereby saving a step for the user. I'll leave this as an exercise for you.
| For related articles see:|
The Future of Visual Basic: Web Forms, Web Services, and Language Enhancements Slated for Next Generation
ASP .NET: Collect Customer Order Information on an Internet Site Using XML and Web Forms
ASP .NET: Web Forms Let You Drag and Drop Your Way to Powerful Web Apps
| David S. Platt is president and founder of Rolling Thunder Computing Inc. He teaches COM and COM+ at Harvard University and at companies worldwide. He publishes a free e-mail newsletter on COM+, available at http://www.rollthunder.com. Portions of this article will appear in his upcoming book, Introducing Microsoft .NET, 2nd Edition (Microsoft Press) due in May 2002.|