You visually create the user interface for your .NET Framework program within the Forms designer. By simply dragging and dropping controls from the Toolbox onto a form and setting a few properties in the Properties window, you can quickly build the user interface for your program. The Forms designer was built with .NET components and uses the System.Windows.Forms assembly to display and create the form. Because the System.Windows. Forms assembly is used, programming a form in the designer is similar to programming a form as it executes at run time.
A Forms designer window exposes an object model, as many of the other windows do. The object hierarchy returned from calling the Window.Object property of a Forms designer window is of type System.ComponentModel.Design.IDesignerHost. To examine and modify a form within the designer, you must find the System.Windows.Forms.Control object for that form. You can do this by calling the IDesignerHost.RootComponent property and casting the object returned into a Forms.Control object, as shown in this code snippet:
System.Windows.Forms.Control control; System.ComponentModel.Design.IDesignerHost designerHost; designerHost =(System.ComponentModel.Design.IDesignerHost) applicationObject.ActiveWindow.Object; control = (System.Windows.Forms.Control)designerHost.RootComponent;
Note | The System.Windows.Forms.Form class derives from the System.Windows.Forms. Control class. The code shown here demonstrates how to manipulate a user control, but you can use the same unmodified code to program a Windows Form. |
Using the System.Windows.Forms.Control object, you can connect events to determine when the form was modified; to find and modify properties such as the dimensions of the form; and to place, modify, and remove controls from the form.
If you try to use the IDesignerHost interface from within a macro, a System.Runtime.Remoting. RemotingException exception is thrown. This is because user interface elements, such as the System.Windows.Control object, cannot be remoted across process boundaries. Remember that the Macros IDE runs in a process separate from the Visual Studio process. Because of this restriction, the designer object model can be used only within an add-in and not from a macro.
Once you find the IDesignerHost interface for a Forms designer, you can easily add new controls to the form. To add a control, you need the System.Type object that describes the control. You can find this object by using the Type.GetType static method, which is passed the full class name of a control. For example, to add a list view control to a form, you can use code such as this:
System.Type type; type = System.Type.GetType("System.Windows.Forms.ListView");
You can then pass this Type object to the IDesignerHost.CreateComponent method to create the control. This method has two overloads, the first of which takes two parameters. The first parameter is the Type object we just found, and the second parameter is the variable name of the control we want to create. This variable name must be unique among variables contained within the form's class; otherwise, a name collision will occur and an exception will be generated. The second overload of this method takes as an argument only the Type object; the Forms designer examines the form code to find a unique variable name to use. Both of these overloads emit the appropriate code to instantiate a control of the specified type. The following code creates a list view control with the variable name listViewControl:
System.ComponentModel.IComponent component; component = designerHost.CreateComponent(type, "listViewControl");
If you were to add this code to an add-in and execute it, you wouldn't see the control appear on the form. This is because the control, while instantiated, hasn't been parented to the form and added to the form's Controls collection. To add the control to the form's Controls collection, you must set the Parent property of the control to the form that should contain the control. You can set the Parent property (or any property of a control, for that matter) by using the System.ComponentModel.PropertyDescriptorCollection object. This object contains a collection of properties available for a control; as values are set for the properties they contain, code is generated within the form's class that corresponds to the property you set. You can set the Parent property as follows:
System.ComponentModel.PropertyDescriptorCollection props; System.ComponentModel.PropertyDescriptor propParent; //Find the properties for the listViewControl control: props = System.ComponentModel.TypeDescriptor.GetProperties(component); //Get the Parent property propParent = props["Parent"]; //Set the Parent property to the form: propParent.SetValue(newControl, designerHost.RootComponent);
You now know how to create controls and place them on a form. But how do you find existing controls on a form? As mentioned earlier, the IDesignerHost.RootComponent property returns an object that can be cast into a System.Windows.Forms.Control object. Using this object, you can call methods and properties just as you would at run time to find information about a form. For example, the following code walks the list of controls contained in a System.Windows.Forms.Control object:
System.ComponentModel.Design.IDesignerHost designer; System.Windows.Forms.Control rootControl; //Set the designer variable here from the Window.Object property rootControl = (System.Windows.Forms.Control)designer.RootComponent foreach (System.Windows.Forms.Control control in rootControl.Controls) { //Retrieve desired control information }
You can use the PropertyDescriptorCollection object to find properties of a control much as you would to set properties on the form, except you use the PropertyDescriptor.GetValue method:
System.ComponentModel.Design.IDesignerHost designer; System.ComponentModel.PropertyDescriptor propControls; System.ComponentModel.PropertyDescriptorCollection props; System.ComponentModel.IComponent component; System.Windows.Forms.Form form; System.Drawing.Size size; designer = (System.ComponentModel.Design.IDesignerHost) _ applicationObject.ActiveWindow.Object; component = designer.RootComponent; //Get the Size property using the forms designer: props = System.ComponentModel.TypeDescriptor.GetProperties(component); propControls = props["Size"]; size = (System.Drawing.Size)propControls.GetValue(component); //Get the Size property directly from the form: form = (System.Windows.Forms.Form)component; size = form.Size;
Microsoft Visual Basic versions 6 and earlier have a tool window called Form Layout that shows the size of a form being designed as it would appear on the desktop of your computer. Visual Studio doesn't have this feature, but you can easily create one by using the automation model of the Forms designer. You can find the source code for this sample, called FormView, among the book's sample files.
When the add-in starts, it creates a tool window that draws a virtual monitor representing your computer monitor. The screen of the virtual monitor matches the display resolution of your monitor. (If your computer uses multiple monitors, the resolution of the primary monitor is used.) After connecting to the WindowActivated event, it waits for a Forms designer window to become active, and then it looks at the available controls in the form and draws the form and its controls on the virtual screen. For example, if you create a form that has the calendar and button controls on it, as shown in Figure 9-1, the Forms designer window, shown in Figure 9-2, appears. This sample also demonstrates our next topic, creating your own tool windows that are hosted by Visual Studio.
Figure 9-1: A Windows Form with a calendar control and a button control
Figure 9-2: The Form Layout window showing the form from Figure 9-1