Blog Home  Home Feed your aggregator (RSS 2.0)  
ASP.NET designers - The ControlDesigner class - Manuel Abadia's ASP.NET stuff
 
# Monday, April 3, 2006

In my last post I talked about designers and the basic functionality of System.ComponentModel.Design.ComponentDesigner. Now it’s time to study System.Web.UI.Design.ControlDesigner, the base class for all ASP.NET designers.

This post is more theoretical than practical but I’ll reference the excellent examples of Jonathan Hawkins when talking about some of the properties and methods. The source for his samples is available here:

http://blogs.msdn.com/jhawk/archive/2005/11/11/491951.aspx

The ControlDesigner class has a lot of properties and methods (non overridden properties and methods are not shown):

public class ControlDesigner : HtmlControlDesigner

{

        public ControlDesigner();

 

        public virtual string ID { get; set; }

        public virtual bool AllowResize { get; }

        public override DesignerActionListCollection ActionLists { get; }

        public virtual DesignerAutoFormatCollection AutoFormats { get; }

        protected virtual bool DataBindingsEnabled { get; }

 

        protected ControlDesignerState DesignerState { get; }

 

        protected bool InTemplateMode { get; }

        protected internal virtual bool HidePropertiesInTemplateMode { get; }

        public virtual TemplateGroupCollection TemplateGroups { get; }

 

        protected WebFormsRootDesigner RootDesigner { get; }

        protected IControlDesignerTag Tag { get; }

 

        public Control ViewControl { get; set; }

        public virtual bool ViewControlCreated { get; set; }

        protected virtual bool UsePreviewControl { get; }

 

        protected virtual Control CreateViewControl();

 

        protected override void PreFilterProperties(IDictionary properties);

 

        public virtual string GetDesignTimeHtml();

        public virtual string GetDesignTimeHtml(DesignerRegionCollection regions);

        protected virtual string GetEmptyDesignTimeHtml();

        protected virtual string GetErrorDesignTimeHtml(Exception e);

        protected string CreateErrorDesignTimeHtml(string errorMessage);

        protected string CreateErrorDesignTimeHtml(string errorMessage, Exception e);

        protected string CreatePlaceHolderDesignTimeHtml();

        protected string CreatePlaceHolderDesignTimeHtml(string instruction);

        public virtual void UpdateDesignTimeHtml();

 

        public override void Initialize(IComponent component);

        protected void SetViewFlags(ViewFlags viewFlags, bool setFlag);

 

        protected virtual void OnClick(DesignerRegionMouseEventArgs e);

        protected virtual void OnPaint(System.Windows.Forms.PaintEventArgs e);

        public System.Drawing.Rectangle GetBounds();

        public void Invalidate();

        public void Invalidate(System.Drawing.Rectangle rectangle);

 

        public virtual string GetEditableDesignerRegionContent(EditableDesignerRegion region);

        public virtual void SetEditableDesignerRegionContent(EditableDesignerRegion region, string content);

        protected void SetRegionContent(EditableDesignerRegion region, string content);

 

        public virtual void OnAutoFormatApplied(DesignerAutoFormat appliedAutoFormat);

 

        public virtual string GetPersistenceContent();

        public ViewRendering GetViewRendering();

        public static ViewRendering GetViewRendering(Control control);

        public static ViewRendering GetViewRendering(ControlDesigner designer);

 

        public static void InvokeTransactedChange(IComponent component, TransactedChangeCallback callback, object context, string description);

        public static void InvokeTransactedChange(IComponent component, TransactedChangeCallback callback, object context, string description, MemberDescriptor member);

        public static void InvokeTransactedChange(IServiceProvider serviceProvider, IComponent component, TransactedChangeCallback callback, object context, string description, MemberDescriptor member);

        public virtual void OnComponentChanging(object sender, ComponentChangingEventArgs ce);

        public virtual void OnComponentChanged(object sender, ComponentChangedEventArgs ce);

    }

}

 

The ID property holds the name of the component in the designer.

If the AllowResize property is set to “true”, we can change the control dimensions when we drag a side of the bounding box of the control. Even if it’s false, the control dimensions can be changed modifying the width and height properties in the property grid. The default implementation returns true if the control inherits from WebControl or a derivate class.

The ActionList property returns a DesignerActionListCollection that configure the smart tag. When the design time infrastructure is about to draw the smart tag, it gets the ActionList of the designer, which contains a collection of DesignerActionList. If we want to add stuff to the smart tag we have to create a derived class from DesignerActionList and override the GetSortedActionItems method and return a list of DesignerActionItems. Each DesignerActionItems represents an item in the smart tag. There are several kinds of DesignerActionItems:
• DesignerActionMethodItem: It’s shown as a Hyperlink in the smart tag and executes a method when it’s clicked.
• DesignerActionPropertyItem: Allows editing properties in the smart tag. What is shown in the designer is dependent of the type of the property. It can show a custom editor if the type has an associated UITypeEditor, a DropDownList if it’s an enum, a TextBox if the type can be converted to a string and a CheckBox if it’s a Boolean value.
• DesignerActionTextItem: Shows static text in the smart panel.
• DesignerActionHeaderItem: like DesignerActionTextItem but with bold style applied to the font.

ActionList items

The implementation of the ActionList property adds an Edit DataBindings item in the smart tag of the control if the control is Bindable.

The AutoFormat property returns a DesignerAutoFormatCollection. If the collection has items, a new action is available in the smart tag called “Auto Format” that lets you choose one of the available formats. The default implementation doesn’t add any formats.

The DataBindingsEnabled property returns true if the current selected region of the control supports data bindings.

In .NET Framework 2.0 regions were added to designers. A region is a part of the HTML markup generated for the control in design time that can be highlighted, clicked and can handle its own events. The regions have to be added to the regions collection in the GetDesignTimeHtml method. After creating the regions we have to assign part of the HTML markup to a region. In order to do so we have to add the DesignerRegion.DesignerRegionAttributeName Attribute to the HTML. In JHawk’s PhotoViewer there are two regions, one for the photos (region 1) and the other (region 0) for the navigation images. The markup in design time is something like this:

<span id=”BeautifulPhotos”>

<table border=”0”>

    <tr>

        <td id=”BeautifulPhotos_PhotoContainer” _designerRegion=”1”></td>

    </tr>

    <tr>

        <td id=”BeautifulPhotos_NavContainer” _designerRegion=”0”></td>

    </tr>

</table>

</span>


A DesignerRegion can’t be edited, so it’s usually used to implement “design time buttons” that change another region. If we want to be able to add controls to a region we have to use an EditableDesignerRegion or TemplatedEditableDesignerRegion.

The DesignerState property returns an object that is used to store design time data that will persist even if you close the WebForm and open it again later. In JHawk’s samples it’s used to store the current view for the MultiList control.

InTemplateMode is a read only property that is true when the designer editing a template.

If the property HidePropertiesInTemplateMode is set to “true”, when the designer is in template mode the only property that is shown is the ID. The other properties are marked as Browsable(false) in the PreFilterProperties method.

Using templates in our controls is very easy in this version of the .NET Framework. Overriding the TemplateGroups property we can add all necessary template groups and template definitions. When a control has templates enabled, there’s a new option in the smart tag and in the context menu called “Edit Templates”, where the template groups are shown. If we click on a template group, all template definitions of the group are shown. If we’re using the smart tag we can edit a template definition individually. In a template definition we specify the control property (of type ITemplate) that will be associated with the template.

Overriding TemplateGroups just let the design time infrastructure know that we have templates but it doesn’t enable design time template editing. In order to do so we have to call SetViewFlags(ViewFlags.TemplateEditing, true).

The properties ViewControl, ViewControlCreated and UsePreviewControl are related. If we are creating a control that supports multiple templates but display only one at a time, the user could have set one template to display at runtime but may want to edit another template at design time. However, if we change the current template the changes will be persisted to the generated HTML. To avoid this kind of problems we can use a different control for rendering in design time. To use a different control we set the UsePreviewControl to “true”. The designer accesses the control used for rendering with the ViewControl property (if we’re not using a preview control this returns the Component being designed). The ViewControlCreated is a flag used to know if the ViewControl needs to be created or not (if we’re using a preview control). If we are using a preview control, the CreateViewControl method is called in order to get the preview control. The default implementation returns a clone of the control we’re designing.

The GetDesignTimeHtml method returns the HTML to render at design time. There are two overloads of this method, one without parameters and one with a list of regions. The implementation of the second one is just to call the one without parameters. The design time infrastructure selects the method to call checking if we support regions or not.

The implementation of the GetDesignTimeHtml method without parameters, calls the Render method from the control returned by the ViewControl property. If the designer isn’t using a preview control and the control returned by ViewControl is not visible, the visibility is turned on just for the call to the Render method.
If the Render method returns null or the empty string, the GetEmptyDesignTimeHtml method is called. The implementation of the GetEmptyDesignTimeHtml returns the name and type of the control.

If there is an error while rendering the control, the GetErrorDesignTimeHtml is called. The implementation GetErrorDesignTimeHtml calls CreateErrorDesignTimeHtml, which returns a table with two rows showing information about the exception.

The method CreatePlaceHolderDesignTimeHtml can be used to return information in a table similar to the one returned by GetErrorDesignTimeHtml.

The UpdateDesignTimeHtml method updates the control in design time calling the GetDesignTimeHtml method. This method is called automatically when the component is changed using the IComponentChangeService or a PropertyDescriptor.

The Initialize method (that is part of the IDesigner interface) is called when the designer is being loaded. In this method we can set up the properties of the view control using the SetViewFlags method and we have to check that the component we’re designing it’s from the right type. The implementation of the ControlDesigner class checks that the component inherits from System.Web.UI.Control.

The OnClick method is called when the user clicks on the control being designed. If we have multiple regions, we can use the DesignerRegionMouseEventArgs to figure out which region was clicked and where exactly.

Most of the time, we can render our control using just HTML with the GetDesignTimeHtml method. However, in rare cases we may want to add something in design time difficult to do with HTML but easy with GDI+. To do so we have to set the CustomPaint flag calling to the SetViewFlags method and override the OnPaint method (the argument to the OnPaint method has a Graphics property) so we can draw on the top of the rendered design time HTML.  In JHawk’s samples, the MultiList control uses custom painting to draw the header with a nice gradient.

If we’re using GDI+ to draw we may find useful the GetBounds method that returns the boundaries of the control. To redraw the control (or part of it) we can use the Invalidate method.

The GetEditableDesignerRegionContent and SetEditableDesignerRegionContent methods are used when our control has editable regions. The design time infrastructure doesn’t know what to show in the editable region so it calls the GetEditableDesignerRegionContent where we have to return the HTML that the designer will show in the EditableDesignerRegion. Every time we modify something in an editable region, the design time infrastructure will call the SetEditableDesignerRegionContent with the current HTML that it’s inside the EditableDesignerRegion. We can use the HTML to update some properties of the control we’re designing. The implementation of SetEditableDesignerRegionContent does nothing. The implementation of GetEditableDesignerRegionContent returns the empty string. In JHawk’s MultiList control, those methods are overridden to load or save data for the current selected template.

The OnAutoFormatApplied is called after the user selects a format from the Auto Format dialog box to do any additional post processing. The implementation does nothing.

The GetPersistenceContent is called by the design time infrastructure to get the inner HTML of the control we’re designing. The implementation of ControlDesigner serializes the Component from the designer. In case you need to serialize to the ASPX (or ASCX) something that can’t be done with standard properties and attributes you should override this method.

The GetViewRendering methods return an object of type ViewRendering from a control or a designer. The ViewRendering class has only 2 properties:
• Content: that returns the HTML markup in design time for the associated control.
• Regions: that returns the collection of available regions for the associated designer.

The OnComponentChanging and OnComponentChanged methods are called when the associated component is about or has changed respectively. The implementation of the ControlDesigner class does nothing for OnComponentChanging. For OnComponentChanged, the implementation calls to UpdateDesignTimeHtml, updates the persisted markup and removes any data binding expression where the property that has changed was involved.

We can change properties of the component we’re designing directly at any time but the best we can do is to create transactions because if the user undoes the action the changes can be discarded only if we created a transaction or if we used the type descriptor to set a property (that creates an implicit transaction for us). To create a transaction explicitly we have to call the InvokeTransactedChange method (note that this method will call OnComponentChanging and OnComponentChanged).

This concludes our study of the ControlDesigner class.

In a future post I’ll post about the DataSourceDesigner, that adds some extra functionality to the one provided by ControlDesigner.

Monday, April 3, 2006 11:29:28 AM (Romance Daylight Time, UTC+02:00)  #    Comments [0]   ASP.NET  |  Tracked by:
"DataSourceControls summary" (Manuel Abadia's ASP.NET stuff) [Trackback]
http://www.manuelabadia.com/blog/PermaLink,guid,7cef4a16-10ec-44e5-8f3c-36a977b8... [Pingback]
Copyright © 2014 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.