Windows Forms 2.0 Programming (Microsoft .NET Development Series)
ExpandableObjectConverters help break down a complex multivalue property into a nested list of its atomic values. Although this technique simplifies editing of a complicated property, it may not be suitable for other properties that exhibit the following behavior:
Actually, ForeColor satisfies all three points. First, it would be hard to find the color you wanted by typing comma-separated integers like 33, 86, 24 or guessing a named color, like PapayaWhip. Second, there are a lot of colors to choose from. Finally, colors are just plain visual. In addition to supporting in-place editing in the Properties window, properties such as ForeColor help the developer by providing an alternative UI-based property-editing mechanism. You access this tool, shown in Figure 11.24, from a drop-down arrow in the Properties window. Figure 11.24. Color Property Drop-Down UI Editor (See Plate 18)
The result is a prettier, more intuitive way to select a property value. This style of visual editing is supported by the UI type editor, a design-time feature that you can use to similar effect. There are two kinds to choose from: modal or drop-down. Drop-down editors support single-click property selection from a drop-down UI attached to the Properties window. This UI might be a nice way to enhance the AlarmClockControl's Face property, allowing developers to visualize the clock face style as they make their selection, as illustrated in Figure 11.25. Figure 11.25. Custom View Drop-Down UI Editor
You begin implementing a custom UI editor by deriving from the UITypeEditor class (from the System.Drawing.Design namespace): // FaceEditor.cs class FaceEditor : UITypeEditor {...} Next, you override the GetEditStyle and EditValue methods from the UITypeEditor base class: // FaceEditor.cs class FaceEditor : UITypeEditor { public override UITypeEditorEditStyle GetEditStyle( ITypeDescriptorContext context) {...} public override object EditValue( ITypeDescriptorContext context, IServiceProvider provider, object value) {...} }
As with type converters, the appropriate UI type editorprovided by the GetEditor method of the TypeDescription classis stored with each property. When the Properties window updates itself to reflect a control selection in the Windows Forms Designer, it queries GetEditStyle to determine whether it should show a drop-down button, an open dialog button, or nothing in the property value box when the property is selected. This behavior is determined by a value from the UITypeEditorEditStyle enumeration: namespace System.Drawing.Design { enum UITypeEditorEditStyle { None = 1 // Don't display a UI (default) Modal = 2, // Display a modal dialog UI DropDown = 3, // Display a drop-down UI } } Not overriding GetEditStyle is the same as returning UITypeEditorEditStyle.None, which is the default edit style. To show the drop-down UI editor, AlarmClockControl returns UITypeEditorEditStyle.DropDown: // FaceEditor.cs class FaceEditor : UITypeEditor { public override UITypeEditorEditStyle GetEditStyle( ITypeDescriptorContext context) { // Specify a drop-down UITypeEditor return UITypeEditorEditStyle.DropDown; } ... } ITypeDescriptorContext is passed to GetEditStyle to provide contextual information regarding the execution of this method, including the following:
Whereas GetEditStyle is used to initialize the way the property behaves, EditValue actually implements the defined behavior. Whether the UI editor is drop-down or modal, you follow the same basic steps to edit the value:
Drop-Down UI Type Editors
Here's how AlarmClockControl implements these steps to show a drop-down editor for the Face property: // FaceEditor.cs class FaceEditor : UITypeEditor { ... public override object EditValue( ITypeDescriptorContext context, IServiceProvider provider, object value) { if( (context != null) && (provider != null) ) { // Access the Properties window's UI display service IWindowsFormsEditorService editorService = (IWindowsFormsEditorService) provider.GetService(typeof(IWindowsFormsEditorService)); if( editorService != null ) { // Create an instance of the UI editor control, // passing a reference to the editor service FaceEditorControl dropDownEditor = new FaceEditorControl(editorService); // Pass the UI editor control the current property value dropDownEditor.Face = (ClockFace)value; // Display the UI editor control editorService.DropDownControl(dropDownEditor); // Return the new property value from the UI editor control return dropDownEditor.Face; } } return base.EditValue(context, provider, value); } }
When it comes to displaying the UI editor control, you must play nicely in the design-time environment, particularly regarding UI positioning in relation to the Properties window. Specifically, drop-down UI editors must appear flush against the bottom of the property entry. To facilitate this, the Properties window exposes a servicean implementation of the IWindowsFormsEditorService interfaceto manage the loading and unloading of UI editor controls as well as their positioning inside the development environment. The FaceEditor type references this service and calls its DropDownControl method to display the FaceEditorControl, relative to the Properties window edit box. When displayed, FaceEditorControl captures the user selection and returns control to EditValue with the new value. This requires a call to IWindowsFormsEditorService.CloseDropDown from FaceEditorControl, something you do by passing to FaceEditorControl a reference to the IWindowsFormsEditorService interface via its constructor: // FaceEditorControl.cs partial class FaceEditorControl : UserControl { ... ClockFace face = ClockFace.Both; IWindowsFormsEditorService editorService = null; ... public FaceEditorControl(IWindowsFormsEditorService editorService) { InitializeComponent(); this.editorService = editorService; } public ClockFace Face { get { return this.face; } set { this.face = value; } } void bothPictureBox_Click(object sender, EventArgs e) { this.face = ClockFace.Both; // Close the UI editor upon value selection this.editorService.CloseDropDown(); } void analogPictureBox_Click(object sender, EventArgs e) { this.face = ClockFace.Analog; // Close the UI editor upon value selection this.editorService.CloseDropDown(); } void digitalPictureBox_Click(object sender, EventArgs e) { this.face = ClockFace.Digital; // Close the UI editor upon value selection this.editorService.CloseDropDown(); } ... } The final step is to associate FaceEditor with the Face property by adorning the property with the Editor attribute: [Category("Appearance")] [Description("Determines the clock face type to display.")] [DefaultValue(ClockFace.Both)] [Editor(typeof(FaceEditor), typeof(UITypeEditor))] public ClockFace Face {...}
Now FaceEditor is in place for the Face property. When a developer edits that property in the Properties window, it shows a drop-down arrow and the FaceEditorControl as the UI the developer uses to choose a value of the ClockFace enumeration. If the UI editor control you are using is resizable friendly, you can override UITypeEditor's IsDropDownResizable property to return true, rather than the default of false: // FaceEditor.cs partial class FaceEditor : UITypeEditor { ... // If the UI editor control is resizable, override this // property to include a sizing grip on the Properties // window drop-down public override bool IsDropDownResizable { get { return true; } } ... }
This tiny update ensures that the UITypeEditor adds a size grip to your UI editing control, as illustrated in Figure 11.26. Figure 11.26. Custom View Drop-Down UI Editor with Size Grip
Drop-down editors are a great way to enhance the usability of single-click value selection. Modal UI Type Editors
Sometimes, single-click selection isn't the most appropriate; sometimes, unrestricted editing is more desirable. In such situations, you use a modal UITypeEditor implemented as a modal form. For example, AlarmClockControl's digital time format is sufficiently complex to edit in a separate dialog outside the Properties window: // AlarmClockControl.cs partial class AlarmClockControl : ... { ... string digitalTimeFormat = "dd/MM/yyyy hh:mm:ss tt"; ... [Category("Appearance")] [Description("The digital time format, ... ")] [DefaultValue("dd/MM/yyyy hh:mm:ss tt")] public string DigitalTimeFormat { get {...} set {...} } ... }
Date and Time format strings are composed of a complex array of format specifiers that are not easy to remember and certainly aren't intuitive in a Properties window, as shown in Figure 11.27. Figure 11.27. The DigitalTimeFormat Property
Modal UITypeEditors are an ideal way to provide a more intuitive way to construct hard-to-format property values. By providing a custom form, you give developers whatever editing experience is the most suitable for that property type. Figure 11.28 illustrates how the Digital Time Format Editor dialog makes it easier to edit AlarmClockControl's DigitalTimeFormat property. Figure 11.28. Custom DigitalTimeFormat Modal UI Editor
A modal UITypeEditor actually requires slightly different code from that of its drop-down counterpart. You follow the same logical steps as with a drop-down editor, with three minor implementation differences:
AlarmClockControl's modal UI type editor is shown here: // DigitalTimeFormatEditor.cs class DigitalTimeFormatEditor : UITypeEditor { public override UITypeEditorEditStyle GetEditStyle( ITypeDescriptorContext context) { // Specify a modal UITypeEditor return UITypeEditorEditStyle.Modal; } public override object EditValue( ITypeDescriptorContext context, IServiceProvider provider, object value) { if( (context != null) && (provider != null) ) { // Access the Properties window's UI display service IWindowsFormsEditorService editorService = (IWindowsFormsEditorService) provider.GetService(typeof(IWindowsFormsEditorService)); if( editorService != null ) { // Create an instance of the UI editor dialog DigitalTimeFormatEditorForm modalEditor = new DigitalTimeFormatEditorForm(); // Pass the UI editor dialog the current property value modalEditor.DigitalTimeFormat = (string)value; // Display the UI editor dialog if( editorService.ShowDialog(modalEditor) == DialogResult.OK ) { // Return the new property value from the UI editor dialog return modalEditor.DigitalTimeFormat; } } } return base.EditValue(context, provider, value); } }
At this point, normal dialog activities (as covered in Chapter 3: Dialogs) apply for the UI editor's modal form: // DigitalTimeFormatEditorForm.cs partial class DigitalTimeFormatEditorForm : Form { ... string digitalTimeFormat = "dd/MM/yyyy hh:mm:ss tt"; ... public string DigitalTimeFormat { get { return this.digitalTimeFormat; } set { this.digitalTimeFormat = value; } } ... void okButton_Click(object sender, EventArgs e) { this.digitalTimeFormat = this.formatTextBox.Text; } ... }
Again, to associate the new UI type editor with the property, you apply the Editor attribute: [Category("Appearance")] [Description("The digital time format, ...")] [DefaultValue("dd/MM/yyyy hh:mm:ss tt")] [Editor(typeof(DigitalTimeFormatEditor), typeof(UITypeEditor))] public string DigitalTimeFormat {...}
After the Editor attribute is applied, developers access the modal UITypeEditor via an ellipsis-style button displayed in the Properties window, as shown in Figure 11.29. Figure 11.29. Accessing a Modal UITypeEditor
UI type editors allow you to give developers a customized editing environment on a per-property basis, whether it's a drop-down UI to support selection from a list of possible values or a modal dialog to provide an entire editing environment outside the Properties window. |
Категории