Chapter 4: Classic Controls
This chapter considers some of the most common types of controls, like menus, text boxes, and buttons. Many of these controls have existed since the dawn of Windows programming and don't need much description. To keep things interesting I'll present some related .NET variants. For example, at the same time that you look at the label, list box, and domain controls, you will learn about the hyperlink label, checked list box, and rich date controls. I'll also describe menus in detail, and show you a few new tricks, including how you can add thumbnail images to owner-drawn menu items, and attach context menus to other controls.
The final part of this chapter demonstrates two advanced .NET features. First, you learn how you can add support for drag-and-drop operations to your .NET applications, and allow the user to move controls and transfer information. Then you examine different ways to implement validation to gracefully handle invalid input before it becomes a problem.
Types of Controls
Not all controls are created equal. .NET uses specialized controls that don't appear on forms, enhance other controls, and provide backward compatibility with legacy ActiveX controls. The next few sections provide a whirlwind tour of these specialized control types, and how they fit into the .NET class library.
Invisible Controls
Invisible controls don't require a portion of form real estate. These include controls that never have a visual appearance, like the Timer and the ErrorProvider. They also include others that appear in special circumstances or in windows of their own, like the ContextMenu and the common dialog controls (OpenFileDialog, SaveFileDialog, ColorDialog, and so on).
When you drag an invisible control onto the form surface, a special icon appears for it in the component tray (see Figure 4-1). You configure its properties through this icon. If you look at the automatically generated code for the form, you'll see that the code for creating the invisible control is added as it would be for a normal control. However, the invisible control is not added to the form's Controls collection.
Figure 4-1: The component tray
In some cases, it's worth asking whether invisible controls are really controls at all. For example, the Timer is just a special way to automate user interface changes. In some respects, it's simpler than true multithreaded programming because it uses safe task switching and automatically performs its work on the user interface thread, ensuring that you don't need to marshal calls when interacting with a control. (In other words, there is no need to use the Control.Invoke() method, because the controls in the form are always on the same thread as the timer.) However, it's a far stretch to call the Timer a true control in the sense that the text box and label are.
Note
Invisible controls don't derive from the Control class. Instead, they derive from System.ComponentModel.Component (the Control class also derives from this class). The Component class adds the basic features needed for an item to be hosted in a container and provides a Dispose() method that causes it to release its resources immediately.Often, invisible controls can be created more flexibly at runtime in your own code. In some cases (like when you want to share one invisible control between forms), it's a necessity. For the most part, it's a matter of preference. The common dialog controls which represent one example of invisible controls, are described in the following chapter. Menus are another example of invisible controls, and are described later in this chapter.
Provider Controls
Providers are a special type of invisible control. They extend the properties of other controls on the current form. For example, the ToolTipProvider allows any control to display a tooltip when the mouse hovers over it. To use the ToolTipProvider, drag an instance of it onto the form. You can then set a tooltip in one of two ways:
At design-time, select the appropriate control and look in the Properties window for the property "ToolTip on tipProvider" (where tipProvider is the name of the ToolTipProvider control).
At runtime, use the ToolTipProvider.SetToolTip() method. You can also use the GetToolTip() method to retrieve a control's tooltip.
tips.SetToolTip(txtName, "Enter Your Name Here")
Tip
There really isn't any difference between using the SetToolTip() method and the extended ToolTip property provided by the designer.With providers, Visual Studio .NET simply translates what you type in the Properties window into the appropriate method call, and adds the code to the form class. So when you set the ToolTip property, you are still in fact using the SetToolTip() method.You can also configure some generic tooltip settings by adjusting the properties of the ToolTipProvider control, as detailed in Table 4-1.
Table 4-1: ToolTipProvider Members
MemberPurpose
ActiveThe same as Enabled for most controls. When set to false, no tooltips are shown.
AutomaticDelay, AutoPopDelay, InitialDelay, ReshowDelayThese settings specify the number of milliseconds before the tooltip appears, the time that it remains visible if the mouse is stationary, and the time required to make it reappear. Generally, you should use the default values.
ShowAlwaysIf set to true, tooltips appear when the mouse hovers over a control even if the window containing the control does not currently have focus.
SetToolTip(), GetToolTip(), and RemoveAll()These methods allow you to attach a descriptive string to a control and retrieve it. To remove a tooltip, you can either attach an empty string, or use RemoveAll() to clear all tooltips at once.
.NET does not provide many provider controls, as they are generally used for specialized features. Toward the end of this chapter, you'll see an example that uses the ErrorProvider with control validation. Later, in Chapter 7, where you see how you can create custom provider controls.
ActiveX Controls
.NET includes excellent interoperability features that allow you to continue using COM components and ActiveX controls in your current applications. If you are using Visual Studio .NET, the process is even automated for you.
To add an ActiveX control to one of your projects in Visual Studio .NET, right-click the toolbox and select Customize Toolbox. Select the COM Components tab, and find the appropriate control on the list, and put a check mark next to it (see Figure 4-2).
Figure 4-2: Adding a COM reference
Nothing happens until you add an instance of this control to a form. The first time you do this, Visual Studio .NET automatically creates a special interop assembly for you. For example, if you add the MSChart control, which has no direct .NET equivalent, it creates a file with a name like AxInterop.MSChart20Lib_2_0.dll.
The "Ax" at the beginning of the name identifies the fact that this interop assembly derives from System.Windows.Forms.AxHost. This class is used to create any .NET wrapper for an ActiveX control. It works "in between" your .NET code and the ActiveX component, as shown in Figure 4-3.
Figure 4-3: AxHost interaction
The control on your form is a legitimate .NET control, as you can see by examining the automatically generated designer code that defines and instantiates it. For example, consider an automatically generated interop class that supports the MSChart control:
AxMSChart20Lib.AxMSChart AxMSChart1;
Here's the code used to configure the control, in true .NET fashion:
this.AxMSChart1 = new AxMSChart20Lib.AxMSChart();
this.AxMSChart1.Location = new System.Drawing.Point(36, 24);
this.AxMSChart1.Name = "AxMSChart1"; this.axMSChart1.OcxState =
((AxHost.State)(resources.GetObject("axMSChart1.OcxState")));
this.AxMSChart1.Size = new System.Drawing.Size(216, 72);
this.AxMSChart1.TabIndex = 4;
You can see that this control supports basic .NET properties like Size and Location. It also uses a special OcxState property (inherited from the AxHost class) that retrieves the persisted state of an ActiveX control. From your program's point of view, you can communicate with a normal .NET control that supports .NET event handling and the basic set of features in the Control class. The AxHost-based control quietly communicates with the original ActiveX control, and mimics its behavior on the form. You can even dynamically resize the control and modify its properties using the built-in property pages, and it will respond exactly as it should.
In some cases, the new class may introduce changes. For example, when the MSFlexGrid control is imported, it changes the syntax used to set some properties into method calls:
grid.set_ColWidth(1, 3000); // This was grid.ColWidth(1) = 3000;
grid.set_ColAlignment(0, 1); // This was grid.ColAlightment(0) = 1;
Fortunately, you can always use the Object Browser to get to the bottom of any new changes.
If you are a war-hardened COM veteran, you can create interop controls by hand. However, this process is time-consuming and error-prone, and generally won't produce a better result than Visual Studio .NET's automatic support. Instead, you might want to subclass the interop control that Visual Studio .NET creates. In other words, you could create a custom control that inherits from the interop control. This extra layer gives you the chance to add additional .NET features, and won't hamper performance.
Should You Import ActiveX Controls?
Importing controls is easy, and it most cases it works without a hitch. Right now, it might be required to convert existing programs without rewriting large pieces of functionality. And while it is possible to recreate .NET controls for the MSChart or Internet Explorer Web Browser components, it can be time consuming.
You should also be aware of some of the potential problems:
ActiveX licensing issues are back. .NET controls demonstrate the amazing xcopy installation capability of the .NET platform. ActiveX controls, however, need to be registered and reregistered whenever a change occurs. This isn't a new problem, but the return of an ugly one.
Security issues appear. The .NET framework uses a special fine-grain approach to security, which allows controls to be used in semi-trusted environments with most of their functionality intact. ActiveX controls require full unmanaged code permission, which makes them more difficult to use in some scenarios.
Performance could be affected. Generally, this is the least likely concern. ActiveX emulation is extremely fast in .NET. In some cases, certain controls may exhibit problems, but that will be the exception.
.NET controls will always be the best solution, and in the coming months there will be a proliferation of new third-party options that surpass most of the ActiveX controls used today. Until that time, you may want to use the built-in ActiveX interop, particularly if you have custom controls and don't have the time or budget to redesign them for .NET.
Some of ActiveX controls that you still need to use in the .NET world (at least for the time being) include:
Microsoft's Web Browser control for displaying HTML content.
Any ActiveX control related to Help, including Microsoft's new MS Help 2.0 components, which you explore in Chapter 14.
Special grid or charting controls (some of which were included with previous Visual Studio releases) like Microsoft's FlexGrid, DataRepeater, and Charting controls. The .NET framework provides some basic tools, and there are sure to be a host of third party .NET controls in this area, but not all the existing controls have been brought over.
Microsoft controls for animation or hosting media player.
Microsoft Office-based components (including charting and spreadsheet components).
Specialty Microsoft controls like the masked edit text box and the drop-down image list (although these can be manually recreated with .NET code without too much difficulty).