Dynamic Rating System Part 3: Designer Support...
The Visual Studio .NET environment has a wide array of design-time features available in the framework. Advanced editing capabilities can be invoked through designer verbs, and provide strong property editing support for ASP.NET server controls.
Previously, we've looked at the dynamic rating system ASP.NET control from the control perspective only; developing client javascript and UI features for the control itself. Now, we will turn to design-time support.
The Visual Studio .NET IDE uses the design-time environment to add additional features to an ASP.NET control and its properties. The designer can display a windows form for advanced editing of properties, provide advanced editing capabilities, override the design-time functionality of the control, and invoke custom functionality through verbs. The designer in this example will provide a designer verb by overriding the Verbs collection. In addition, this custom verb that appears in the properties window will show a custom property builder, along the same lines of the DataGrid’s property builder option. This article will illustrate this as it goes along; first, let's look at the designer.
The GetDesignTimeHtml method renders the control in the designer, simply by invoking the base class's GetDesignTimeHtml method. This simply invokes the render method of the control. This will only happen if the ImageUrl and SelectedImageUrl properties aren’t equal to the string class’s Empty property. It is important to note that with string variables are typically checked for an instance of null, but in the property setter, if the value is null, string.Empty is returned, remedying us of this check. The reference to the DynamicRatingSystem control is retrieved from the Component property, and assigned to the control local variable.
DynamicRatingSystem control = this.Component as DynamicRatingSystem;
if (control.ImageUrl != string.Empty && control.SelectedImageUrl != string.Empty)
return base.GetDesignTimeHtml();
return this.GetEmptyDesignTimeHtml();
The next feature key to providing an advanced editor is overriding the verbs collection. Verbs have the ability to invoke custom capabilities within the designer. Verbs are links in the properties window, below the property grid and above the description. By clicking the link, the verb invokes an event handler that performs some custom capabilities.
The following example overrides the Verbs collection (DesignerVerbCollection return type) to return our new Property Builder verb. This verb will invoke the PropertyBuilder_Click event handler. The code for this event handler is described later. The DesignerVerbCollection’s Add method requires an object of type DesignerVerb, which the constructor takes the text of the verb and the event handler as it's constructor parameters.
|
private DesignerVerbCollection _verbs; public override DesignerVerbCollection Verbs { get { if (_verbs == null) { _verbs = new DesignerVerbCollection(); _verbs.Add(new DesignerVerb("Property Builder", new EventHandler(PropertyBuilder_Click))); } return _verbs; } } |
The custom event handler will display a windows form that appears as an advanced editor. To perform these capabilities, a separate editor class must be defined that inherits from the WindowsFormsComponentEditor class. This approach strays from the mechanics of complex property editing, as shown in my previous article. The editor class is instantiated directly, which is one of the main differences over property editing. DynamicRatingSystemEditor, inheriting from WindowsFormsComponentEditor, defines one method, EditComponent, which defines the code that will display the windows form in the designer.
|
void PropertyBuilder_Click(object sender, EventArgs e) { DynamicRatingSystemEditor editor = new DynamicRatingSystemEditor(); editor.EditComponent(this.Component); } |
The EditComponent method uses the designer host (IDesignerHost interface) to create a transaction. It also uses the IComponentChangeService interface to notify the designer that the component has changed, and update the control’s properties. This approach was outlined in the book "Developing Microsoft ASP.NET Server Controls and Components," by Nikhil Kothari and Vandana Datye. It is an excellent book and documents many of the control-building features well.
To start, the following defines the beginning of the EditComponent method and the initializing of some of the variables. The IDesignerTransaction property ("transaction") is a designer transaction, created by IDesignerHost, that commits or rollbacks all of the transactions that occur during property editing. The IComponentChangeService property ("changeService") notifies the designer when the control changes, in case there are any dependencies. The changed property is a boolean value verifying that the control has changed through the property builder service. These will be in use later.
|
public override bool EditComponent(ITypeDescriptorContext context, object component) { DynamicRatingSystem control = component as DynamicRatingSystem; if (control == null) return false; IComponentChangeService changeService = null; DesignerTransaction transaction = null; bool changed = false; . . . } |
There are two levels of try code blocks. The following code exists in the master try statement (omitted from code example). To verify that the design-time services are available, the Control property, a reference to the DynamicRatingSystem control, verifies that the Site property is available. If it is available, the component has been sited and design-time services are available. ISite defines the GetService method, which is used to retrieve design-time services. The reference to IDesignerHost is retrieved from this method. The IDesignerHost interface manages components and transactions within the design-time environment, and will be useful for both capabilities in this example. The IDesignerHost object creates a transaction through the CreateTransaction method, as well as retrieves a reference to IComponentChangeService.
|
IDesignerHost host = null; if (control.Site != null) { host = (IDesignerHost)control.Site.GetService(typeof(IDesignerHost)); transaction = host.CreateTransaction("Builder"); changeService = (IComponentChangeService)control.Site.GetService(typeof(IComponentChangeService)); try { changeService.OnComponentChanging(control, null); } catch (CheckoutException ex) { if (ex.Equals(CheckoutException.Canceled)) return false; throw ex; } } |
The inner try statement calls OnComponentChanging, which by the normal convention occurs before the component is changed. However, if an exception occurs, it may be because the change was canceled, which is the purpose of the Equals statement in the CheckoutException error handler. If the exception equals the static variable Canceled, then the editing is canceled without throwing an error.
The next inner try statement handles the property editing by instantiating the form object and calling the ShowDialog() method. The form is of type DynamicRatingSystemPropertyBuilder, and the constructor was modified to include the DynamicRatingSystem control reference as a parameter. This makes it easier to modify the parameters within the windows form. If the form returns an OK result, return true; the editing occurred successfully.
At this point, regardless of the fact that a return value has executed, the finally code block is executed. If the property has changed (determined by the changed boolean variable), the OnComponentChanged method is fired, notifying the change. However, at this point, the transaction hasn't finished, and the changes could be rolled back to their original state.
|
try { DynamicRatingSystemPropertyBuilder form = new DynamicRatingSystemPropertyBuilder(control); form.ShowDialog(); if (form.DialogResult == DialogResult.OK) { changed = true; return true; } } finally { if (changeService != null && changed) changeService.OnComponentChanged(control, null, null, null); } |
The last finally statement (part of the master try statement) determines whether changes have been made, and commits or cancels the transaction. All changes are rolled back if no change has occurred. This can be the result of an error thrown also, which would be premature to the setting of the changed variable.
|
finally { if (changed) transaction.Commit(); else transaction.Cancel(); } |
This article discusses the design-time features and adds a property builder to the DynamicRatingSystem control. The property builder is a simple windows form editing the minimum and maximum properties. To invoke the property builder, click the "Property Builder" link in the designer. Download and view the code, as it will help to see the EditComponent method in its entirety.
You may download the code here.