Creating custom controls is a key technique in .NET development. This article by Matthew is the first of a two part series where he looks at how to create a custom Windows Form control that behaves properly in Visual Studio .NET.
Custom Controls and Design-Time Support: Part 1/2 - The DirectoryTree Control (contd.) (Page 3 of 5 )
Every time a directory branch is expanded, the inherited control checks if a dummy node is present and, if it is, the dummy node is removed and the directories are read from the disk. Note that the base TreeView events are handled by overriding the corresponding "On" methods, which is the recommended approach. These methods must always call the base implementation of the overridden method to ensure the control works as expected, and raises the event to all listeners.
// If a dummy node is found, remove it and read the real directory // list. if (e.Node.Nodes[0].Text == "*") { e.Node.Nodes.Clear(); Fill(e.Node); } }
The Directory Selected event is raised when a node is clicked. Once again, the event is handled by method overriding:
protected override void OnAfterSelect(TreeViewEventArgs e) { base.OnAfterSelect(e); // Raise the DirectorySelected event. if (DirectorySelected != null) { DirectorySelected(this, new DirectorySelectedEventArgs(e.Node.FullPath)); } }
The DirectorySelected EventArgs class is quite straightforward, with a single DirectoryName property and a basic constructor:
public class DirectorySelectedEventArgs : EventArgs { public string DirectoryName;
public DirectorySelectedEventArgs(string directoryName) { this.DirectoryName = directoryName; }
}
Throughout the rest of this article, and tomorrow's installment, we'll look at the DirectoryTree control, and consider how to improve its design-time support, bringing it from a useful control class to a redistributable control component.
Creating a Client To use your control in another project, you need to add a reference to the compiled assembly. When you add a reference, Visual Studio .NET stores the location of the file. Every time you rebuild your file, it copies the latest version from that directory into the bin directory, along with the executable for your test program. That ensures that you are always testing against the most recent build. Because .NET recognizes classes using assembly metadata, you never need to worry about registering or registering your control.
To add a reference to a control, right-click on the Toolbox, and choose Customize. Then, select the .NET Framework Components tab, and click the Browse button. Once you select the appropriate assembly, all the controls will be added to the list, and checkmarked automatically (see Figure 8-4).
When you click OK, your control will be added to the bottom of the Toolbox, alongside its .NET counterparts (alternatively, you could add a custom Toolbox tab for your custom controls to improve organization). Because no custom icon is configured for the DirectoryTree control, it will appear with the default gear icon. You can then create instances of this control by drawing on the design surface.
When you actually deploy an application that uses a custom control, all you need to do is ensure that the required control DLL is in the same directory as the application executable. When you copy these files to another computer, you do not need to worry about registering them or performing any additional steps. This is the infamous XCOPY deployment that is heavily hyped with .NET.
Quirky Design-Time Behavior When you first begin using the DirectoryTree control, you may notice some quirky design-time behavior. At first, it seems straightforward enough-once you set the Drive property, the corresponding directory tree will appear. You can even expand nodes and browse the directory structure at design time.
However, when you start your program, a second set of identical directory nodes will appear. The problem is that the nodes you create at design time are automatically serialized to the form's designer code. At runtime, the control is recreated, the directory nodes are rebuilt when the drive property is set, and then the serialized nodes are added.
There are several ways you can resolve this problem. First of all, you could change the order of the form designer lines so that the Drive property is configured after the serialized nodes are added (setting the Drive property automatically clears the current list of nodes). Alternatively, you could create a custom designer, as we will in the next article. The simplest approach, however, is just to configure the DirectoryTree control so that it doesn't provide its directory node display at design-time. You can implement this simple fix by explicitly checking what mode the control is in before refreshing the display:
public char Drive { get { return _drive; } set { _drive = value; if (!this.DesignMode) { RefreshDisplay(); } } }
Adding a Toolbox Bitmap Adding a toolbox icon is refreshing easy. All you need to do is add a bitmap to your project, and ensure it has the same file name as your custom control class. This bitmap must meet a few basic criteria:
It must be 16 pixels by 16 pixels. Otherwise, Visual Studio .NET will attempt to scale it at the results will be ugly.
It must use only 16 colors.
Set the Build Action for the file to Embedded Resource, and recompile the control project. When you add the control to a client project, the embedded bitmap will appear in the toolbox.