NET User Interfaces in Csharp Windows Forms and Custom Controls [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

NET User Interfaces in Csharp Windows Forms and Custom Controls [Electronic resources] - نسخه متنی

Matthew MacDonald

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید




































Custom Designers



One of the problems with traditional ActiveX control development is that details about a control's design-time behavior are mingled with details about its runtime behavior. With .NET control development, this problem is neatly sidestepped by a new feature called a control designer.


A control designer provides the design-time behavior for a control. The .NET framework provides a basic control designer in the System.Windows.Forms. Design.ControlDesigner class, and some derived classes that add support for child control containment and scrolling. Figure 8-11 shows the hierarchy.




Figure 8-11: Control designer classes


Controls can also derive their own custom designers. Why would you create your own designer?





To add special designer tools, like context menu options.





To remove inappropriate events or properties from view (or add designtime only events or properties).





To add support for controls that contain other controls (like the toolbar) or controls with special needs (like menus).





The next few sections consider all these topics by designing and enhancing a DirectoryTreeDesigner class that works with the DirectoryTree control.



Filtering Properties and Events



Sometimes, an event or property needs to be hidden from a control, but not removed entirely. For example, the ProgressBar control provides a Text property, which it inherits from the base Control class. This property can be used at the programmer's discretion, but it does not have any visible text because the ProgressBar doesn't provide a caption. For this reason, the Text property should be hidden from the Properties window.


If you are defining or overriding a property, you can use the Browsable attribute to keep it from appearing in the Properties window. However, consider the TreeView control, which provides a Nodes collection. You may have noticed that the DirectoryTree displays the Nodes property in the designer, and allows it to be modified, even though the display is built automatically at runtime based on the Drive property. The TreeView.Nodes property is not overridable, so you can't use the Browsable attribute. However, you can create a custom designer that ensures it won't appear at design time.


Designers provide six methods from the IDesignerFilter interface that you can override to filter properties, events, and attributes. These methods are listed in Table 8-3.











Table 8-3: ControlDesigner Filtering Methods






MethodDescription















PostFilterAttributesOverrides this method to remove unused or inappropriate attributes.










PostFilterEventsOverrides this method to remove unused or inappropriate events.










PostFilterPropertiesOverrides this method to remove unused or inappropriate properties.










PreFilterAttributesOverrides this method to add attributes.










PreFilterEventsOverrides this method to add events.










PreFilterPropertiesOverrides this method to add properties.
















To use filtering with the DirectoryTree, create a custom designer class that derives from ControlDesigner. In this designer, you can override the PostFilterProperties() method, and use the provided properties collection to remove properties that you don't want displayed. You remove them by name.



public class DirectoryTreeDesigner : ControlDesigner
{
protected override void PostFilterProperties(System.Collections.IDictionary
properties)
{
properties.Remove("Nodes");
}
}


The next step is to link the custom designer to the DirectoryTree control. To do this, you use the Designer attribute, and specify the appropriate designer type.



[Designer(typeof(DirectoryTreeDesigner))]
public class DirectoryTree : TreeView


Now, when you recompile the control and test it in the client, you'll notice that the Nodes property does not appear in the Properties window. However, the Nodes property is still accessible in code. This allows clients to perform other useful tasks (like enumerating through the collection of nodes) at their discretion. This code also ensures that the Nodes collection is not serialized at design time, effectively sidestepping the problem where the same set of drive nodes are added more than once to an instance of the DirectoryTree control.





Designer Verbs



You can also use a custom designer to add to the context menu that is displayed when a programmer right-clicks your control in the design environment. This menu contains some standard options provided by Visual Studio .NET, but it can also contain your commands (technically known as verbs).


To add verbs, you need to override the Verbs property in your custom designer, create a new DesignerVerbCollection, and add the appropriate DesignerVerb object entries. Your control designer handles the verb click event, generally by updating the associated control.


The following example retrieves a list of all the drives on the current computer, and adds a context menu entry for each one. The user can click the appropriate entry to set the Drive property of the control.



public class DirectoryTreeDesigner : ControlDesigner
{
private DesignerVerbCollection verbs = new DesignerVerbCollection();
public DirectoryTreeDesigner()
{
// Configure the designer verb collection.
string[] drives = System.IO.Directory.GetLogicalDrives();
foreach (string drive in drives)
{
verbs.Add(new DesignerVerb("Set Drive " + drive,
new EventHandler(OnVerb)));
}
}
public override DesignerVerbCollection Verbs
{
get
{
return verbs;
}
}
protected void OnVerb(object sender, EventArgs e)
{
// Retrieve the selected drive.
char driveLetter = ((DesignerVerb)sender).Text[10];
// Adjust the associated control.
((DirectoryTree)this.Control).Drive = driveLetter;
}
}


The resulting context menu for the DirectoryTree control is shown in Figure 8-12.




Figure 8-12: Designer verbs


Generally, you won't use your designer verbs to provide settings for a simple property. A more interesting technique is to provide higher-level configuration operations that adjust several properties at once. One example of this is found in the ASP.NET Calendar control, which allows the user to choose a theme from a list of preset choices (see Figure 8-13). When a theme is selected, several properties are modified in conjunction.




Figure 8-13: The Calendar themes


Implementing this design is refreshingly easy. Just add a Windows form to your project and display it when the appropriate designer verb is selected. Here's another simple example using the DirectoryTree. This time, only a single verb is available, which then displays a window that allows the user to choose a drive. When a drive is chosen, a public form-level variable is set and retrieved by the designer, which applies the change. This approach is more manageable than the previous design, and doesn't clutter the context menu with drive letters.



public class DirectoryTreeDesigner : ControlDesigner
{
private DesignerVerbCollection verbs = new DesignerVerbCollection();
public DirectoryTreeDesigner()
{
verbs.Add(new DesignerVerb("Set Drive",
new EventHandler(OnVerb)));
}
public override DesignerVerbCollection Verbs
{
get
{
return verbs;
}
}
protected void OnVerb(object sender, EventArgs e)
{
// Show the form.
SelectDrive frm = new SelectDrive();
frm.DriveSelection = ((DirectoryTree)this.Control).Drive;
frm.ShowDialog();
// Adjust the associated control.
((DirectoryTree)this.Control).Drive = frm.DriveSelection;
}
}


The SelectDrive form is quite simple:



public class SelectDrive : System.Windows.Forms.Form
{
public char DriveSelection;
// (Designer code omitted.)
private void SelectDrive_Load(object sender, System.EventArgs e)
{
string[] drives = System.IO.Directory.GetLogicalDrives();
lstDrives.DataSource = drives;
// Select the current drive.
lstDrives.SelectedIndex = lstDrives.FindString(
DriveSelection.ToString());
// Attach the event handler.
// This step is performed after the selected index is set,
// to prevent it from being overwritten as the list is built.
lstDrives.SelectedIndexChanged += new
EventHandler(lstDrives_SelectedIndexChanged);
}
private void lstDrives_SelectedIndexChanged(object sender,
System.EventArgs e)
{
DriveSelection = lstDrives.Text[0];
}
}


Figure 8-14 shows the drive selection window that appears when the user edits the Drive property.




Figure 8-14: A custom drive selection window


One quirk remains in the control designer. When the DirectoryTree.Drive property is modified by the designer, the Properties window is not updated until the control is deselected and then reselected. To correct this defect, you need to explicitly notify the IDE that a change has been made.


The rewritten OnVerb() method handles this detail:



protected sub OnVerb(object sender, EventArgs e)
{
// Show the form.
SelectDrive frm = new SelectDrive();
frm.DriveSelection = ((DirectoryTree)this.Control).Drive;
frm.ShowDialog();
// Adjust the associated control.
((DirectoryTree)this.Control).Drive = frm.DriveSelection;
// Notify the IDE that the Drive property has changed.
PropertyDescriptorCollection properties;
properties = TypeDescriptor.GetProperties(typeof(DirectoryTree));
PropertyDescriptor changedProperty = properties.Find("Drive", false);
this.RaiseComponentChanged(changedProperty, ", frm.DriveSelection);

}


The final designer code for this example can be found in the DirectoryTree project with the online samples for this chapter.



Note


When you add a form to a control project in this way, the client is able to see the form class in your designer and create and display instances of it. If this isn't the behavior you want, you need to nest your form class inside your control class and make it private or protected. Unfortunately, if you do this you have to forego Visual Studio .NET's design-time support for the form and manually copy the form code into the class.






Control Designer Notifications



Visual Studio .NET only creates one instance of a control designer per form. For example, if you create a custom DirectoryTreeDesigner class and add three DirectoryTree controls to a form, the single DirectoryTreeDesigner instance is reused to provide the behavior for all three trees. This detail can usually be ignored, unless you are designing controls that contain other special controls. For example, the TabControl class is designed to host one or more TabPage controls. Every time a TabPage is added, the TabControl needs to update its visual appearance (for example, the tab strip at the top) accordingly.


To perform this sort of functionality, you need to create a control class that derives from ControlDesignerParent, and then access the features of the IComponentChangeService. Luckily, the ControlDesigner class provides a GetService() method to help you out. Here's an example that uses the GetService() method to register for notifications when child components have been added:



public void MyControlDesigner()
{
IComponentChangeService service;
service = GetService(typeof(IComponentChangeService));
service.ComponentAdded += new ComponentEventHandler(ComponentAdded);
}


You should place this constructor inside your custom designer class, so that the designer registers for child control notifications as soon as it is created.


This book doesn't consider custom container controls, and so none of the examples use the IComponentChangeService. However, if it's something you would like to explore, start with the overview of key events in Table 8-4.











Table 8-4: IComponentChangeService Methods






EventDescription















ComponentAddedTriggered when a component is added to the control at design time.










ComponentAddingTriggered when a component is in the process of being added to the control at design time.










ComponentChangedTriggered when a contained component has changed at design time.










ComponentChangingTriggered when a component is in the process of changing at design time.










ComponentRemovedTriggered when a component is removed at design time.










ComponentRemovingTriggered when a component is in the process of being removed.



















/ 142