Sitefinity CMS

Implementing Control Designers Send comments on this topic.
Developing with Sitefinity > Controls > Adding New Controls to Sitefinity > Implementing Control Designers

Glossary Item Box

So far we have covered many features of Sitefinity controls and created a pretty powerful and straightforward control. The final step we are going to make is to implement Control Designer for the control – it will allow even most inexperienced users to edit the control.

Our Control Designer will provide users with the ability to define the links the control will display, determine in which window should links be opened, and finally - select the color of links – all this through a very simple and straightforward user interface:

Controls - Implementing Designers

Figure 1: LinksList control designer user interface


The screen shown in Figure 1 is the first thing the user will see when editing the control. Control Designer is generally used for providing users with a simple way to edit the most important properties. To get access to all the exposed properties the user can simple switch to Advanced mode.

 

Implementing Control Designer

The sole purpose of Control Designer is to edit the properties of the control. The concept of ControlDesigner is very similar to TypeEditor (explained in the previous topic, download code from here: LinksList6). However, the main difference is that Control Designer is used to edit more properties, while TypeEditor is generally used to edit only one property.


To implement a Control Designer, we need to add the attribute to the class declaration of our control:

C# Copy Code
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Web.UI.WebControls;
using Telerik.Cms.Web.UI;
using Telerik.Framework.Web.Design;

[DefaultProperty("ControlTitle")]
[Telerik.Framework.Web.Design.ControlDesigner(
"LinksListDesigner")]
public partial class SampleControls_LinksList7 : System.Web.UI.UserControl, ILinksList
{

}

 

The next thing we need to do is to implement the LinksListDesigner class, which we will place in the App_Code folder. The code for this class looks like this:

LinksListDesigner.cs Copy Code
using System;
using System.Collections;
using System.Web.UI;
using System.Collections.Generic;
using System.Web.UI.WebControls;
using System.ComponentModel;
using Telerik.Framework.Web;
using Telerik.Cms.Web.UI;
using Telerik.Web.UI;
/// <summary>
/// Summary description for LinksListDesigner
/// </summary>
public class LinksListDesigner : Telerik.Framework.Web.Design.ControlDesigner
{
   #region Public Properties
   
/// <summary>
   
/// Gets or sets the template used by LinksListDesigner control
   
/// </summary>
   
public ITemplate LayoutTemplate
   {
       get
       {
           
return layoutTemplate;
       }
       set
       {
           layoutTemplate = value;
       }
   }
   
/// <summary>
   
/// Gets or sets the path of the template used by LinksListDesigner control
   
/// </summary>
   
public virtual string LayoutTemplatePath
   {
       get
       {
           
if (ViewState["LayoutTemplatePath"] == null)
               
return "~/SampleControls/Utilities/LinksListDesignerTemplate.ascx";
           
return ViewState["LayoutTemplatePath"] as string;
       }
       set
       {
           ViewState[
"LayoutTemplatePath"] = value;
       }
   }
   #endregion
   #region Base Overrides
   
/// <summary>
   
/// Restores control state information from a previous page request that was saved by the SaveControlState
   
/// method.
   
/// </summary>
   
/// <param name="savedState">Represents the control state to be restored.</param>
   
/// <remarks>Notice that this method loads the state from the base class as well</remarks>
   
protected override void LoadControlState(object savedState)
   {
       
if (savedState != null)
       {
           
object[] state = (object[])savedState;
           
base.LoadControlState(state[0]);
           temporaryLinksList = (Dictionary<
string, string>)state[1];
       }
   }
   
/// <summary>
   
/// Saves server control state changes.
   
/// </summary>
   
/// <returns>Array of objects to be saved with the control state</returns>
   
/// <remarks>Notice that this method saves the state for the base class as well</remarks>
   
protected override object SaveControlState()
   {
       
return new object[] {
   
base.SaveControlState(),
               temporaryLinksList
  };
   }
   
/// <summary>
   
/// Renders the HTML opening tag of the control to the specified writer. This method is used primarily by control developers.
   
/// </summary>
   
/// <param name="writer">A <see cref="T:System.Web.UI.HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
   
public override void RenderBeginTag(HtmlTextWriter writer)
   {
       
//Do not render
   }
   
/// <summary>
   
/// Renders the HTML closing tag of the control into the specified writer. This method is used primarily by control developers.
   
/// </summary>
   
/// <param name="writer">A <see cref="T:System.Web.UI.HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
   
public override void RenderEndTag(HtmlTextWriter writer)
   {
       
//Do not render
   }
   
/// <summary>
   
/// Creates the child controls in LinksListDesigner control.
   
/// </summary>
   
protected override void CreateChildControls()
   {
       Controls.Clear();
       InitializeTemplate();
       InitializeComponent();
       container.LinksGrid.MasterTableView.DataKeyNames =
new string[] { "Key" };
       container.LinksGrid.DeleteCommand +=
new GridCommandEventHandler(LinksGrid_DeleteCommand);
       container.LinksGrid.EditCommand +=
new GridCommandEventHandler(LinksGrid_EditCommand);
       container.LinksGrid.CancelCommand +=
new GridCommandEventHandler(LinksGrid_CancelCommand);
       container.LinksGrid.UpdateCommand +=
new GridCommandEventHandler(LinksGrid_UpdateCommand);
       container.LinksGrid.InsertCommand +=
new GridCommandEventHandler(LinksGrid_InsertCommand);
       container.LinksGrid.NeedDataSource +=
new GridNeedDataSourceEventHandler(LinksGrid_NeedDataSource);
       container.TargetRadioButtonList.SelectedIndex = (component.LinkTarget ==
string.Empty) ? 0 : 1;
       container.TargetRadioButtonList.SelectedIndexChanged +=
new EventHandler(TargetRadioButtonList_SelectedIndexChanged);
       container.LinksColorPicker.AutoPostBack = true;
       container.LinksColorPicker.ColorChanged +=
new EventHandler(LinksColorPicker_ColorChanged);
       Controls.Add(container);
   }
   #endregion
   #region Protected Virtual Methods
   
/// <summary>
   
/// Initializes the template to use. The principle is very similar to how we do it in all the controls
   
/// </summary>
   
protected virtual void InitializeTemplate()
   {
       container =
new LinksListDesignerContainer(this);
       layoutTemplate = ControlUtils.GetTemplate<DefaultMyBulletedListDesignerTemplate>(LayoutTemplatePath);
       layoutTemplate.InstantiateIn(container);
   }
   
/// <summary>
   
/// Initializes the component which is our public control.
   
/// </summary>
   
/// <remarks>
   
/// By "component" we understand the control for which designer is setting properties. By having a
   
/// reference to the "component" we can access or modify the properties of that control / component.
   
/// </remarks>
   
protected virtual void InitializeComponent()
   {
       
if (DesignedControl != null)
       {
           component = (ILinksList)DesignedControl;
           properties = TypeDescriptor.GetProperties(component);
       }
   }
   #endregion
   #region Private methods
   
private void BindGrid()
   {
       container.LinksGrid.DataSource = temporaryLinksList;
       container.LinksGrid.DataBind();
   }
   
private void UpdateLinksList()
   {
       component.ListLinks = temporaryLinksList;
       
base.OnPropertyChanged(EventArgs.Empty);
   }
   
private void TargetRadioButtonList_SelectedIndexChanged(object sender, EventArgs e)
   {
       component.LinkTarget = container.TargetRadioButtonList.SelectedValue;
       
base.OnPropertyChanged(EventArgs.Empty);
   }
   
private void LinksColorPicker_ColorChanged(object sender, EventArgs e)
   {
       component.LinkColor = System.Drawing.ColorTranslator.ToHtml(container.LinksColorPicker.SelectedColor);
       
base.OnPropertyChanged(EventArgs.Empty);
   }
   #endregion
   #region Event Handlers
   
private void LinksGrid_NeedDataSource(object source, GridNeedDataSourceEventArgs e)
   {
       
if (temporaryLinksList == null)
           temporaryLinksList = component.ListLinks;
       container.LinksGrid.DataSource = temporaryLinksList;
   }
   
private void LinksGrid_InsertCommand(object source, GridCommandEventArgs e)
   {
       GridEditableItem editedItem = e.Item
as GridEditableItem;
       Hashtable newValues =
new Hashtable();
       e.Item.OwnerTableView.ExtractValuesFromItem(newValues, editedItem);
       temporaryLinksList.Add(newValues[
"Key"].ToString(), newValues["Value"].ToString());
       UpdateLinksList();
       BindGrid();
   }
   
private void LinksGrid_DeleteCommand(object source, GridCommandEventArgs e)
   {
       
string key = e.Item.OwnerTableView.Items[e.Item.ItemIndex]["Key"].Text;
       
if (temporaryLinksList.ContainsKey(key))
           temporaryLinksList.Remove(key);
       UpdateLinksList();
       BindGrid();
   }
   
private void LinksGrid_EditCommand(object source, GridCommandEventArgs e)
   {
       e.Item.OwnerTableView.Items[e.Item.ItemIndex].Edit = true;
       BindGrid();
   }
   
private void LinksGrid_CancelCommand(object source, GridCommandEventArgs e)
   {
       
if (e.Item.ItemIndex > 0 && e.Item.OwnerTableView.Items[e.Item.ItemIndex].IsInEditMode)
           e.Item.OwnerTableView.Items[e.Item.ItemIndex].Edit = false;
       BindGrid();
   }
   
private void LinksGrid_UpdateCommand(object source, GridCommandEventArgs e)
   {
       GridEditableItem editedItem = e.Item
as GridEditableItem;
       
string key = e.Item.OwnerTableView.Items[e.Item.ItemIndex]["Key"].Text;
       Hashtable newValues =
new Hashtable();
       e.Item.OwnerTableView.ExtractValuesFromItem(newValues, editedItem);
       Dictionary<
string, string> updatedListLinks = new Dictionary<string, string>();
       
foreach (KeyValuePair<string, string> link in temporaryLinksList)
       {
           
if (link.Key == key)
               updatedListLinks.Add(newValues[
"Key"].ToString(), newValues["Value"].ToString());
           
else
               
updatedListLinks.Add(link.Key, link.Value);
       }
       temporaryLinksList = updatedListLinks;
       UpdateLinksList();
       BindGrid();
   }
   #endregion
   #region Protected Fields
   
/// <summary>
   
/// Gets or sets the myBulletedList designer container.
   
/// </summary>
   
/// <value>The myBulletedList designer container.</value>
   
protected LinksListDesignerContainer Container
   {
       get
       {
           
return container;
       }
       set
       {
           container = value;
       }
   }
   
/// <summary>
   
/// Gets or sets the component which is of ILinksList type.
   
/// </summary>
   
/// <value>The component which is of ILinksList type.</value>
   
protected ILinksList Component
   {
       get
       {
           
return component;
       }
       set
       {
           component = value;
       }
   }
   #endregion
   #region Private Fields
   
private ITemplate layoutTemplate;
   
private LinksListDesignerContainer container;
   
private ILinksList component;
   
private PropertyDescriptorCollection properties;
   
   
private Dictionary<string, string> temporaryLinksList;

   #endregion
   #region Default Template
   
/// <summary>
   
/// Default template for the LinksListDesigner control designer. NOT IMPLEMENTED!
   
/// </summary>
   
protected class DefaultMyBulletedListDesignerTemplate : ITemplate
   {
       
/// <summary>
       
/// When implemented by a class, defines the object that child controls and templates belong to.
       
/// These child controls are in turn defined within an inline template.
       
/// </summary>
       
/// <param name="container">The object to contain the instances of controls from the inline template.</param>
       
public void InstantiateIn(Control container)
       {
           
throw new NotImplementedException("Default control designer not implemented!");
       }
   }
   #endregion
   #region Container
   
/// <summary>
   
/// The container class for the LinksListDesigner control designer.
   
/// </summary>
   
protected class LinksListDesignerContainer : GenericContainer<LinksListDesigner>
   {
       
/// <summary>
       
/// Initializes a new instance of the LinksListDesignerContainer class.
       
/// </summary>
       
/// <param name="owner">The LinksListDesigner control.</param>
       
public LinksListDesignerContainer(LinksListDesigner owner)
           :
base(owner, true)
       {
       }
       
public RadGrid LinksGrid
       {
           get
           {
               
if (linksGrid == null)
                   linksGrid = FindRequiredControl<RadGrid>(
"linksGrid");
               
return linksGrid;
           }
       }
       
public RadioButtonList TargetRadioButtonList
       {
           get
           {
               
if (targetRadioButtonList == null)
                   targetRadioButtonList = FindRequiredControl<RadioButtonList>(
"targetRadioButtonList");
               
return targetRadioButtonList;
           }
       }
       
public RadColorPicker LinksColorPicker
       {
           get
           {
               
if (linksColorPicker == null)
                   linksColorPicker = FindRequiredControl<RadColorPicker>(
"linksColorPicker");
               
return linksColorPicker;
           }
       }
       
private RadGrid linksGrid;
       
private RadioButtonList targetRadioButtonList;
       
private RadColorPicker linksColorPicker;
   }
   #endregion
}

 

Similarily to the TypeEditor, Control Designer is also Custom Control. The User Interface for this control is defined in an external template (defined by the LayoutTemplatePath property) and the code for the template looks like this:

ASPX Copy Code
<div style="background-color:#E2E8E9; padding:10px;">
<
table width="100%" cellpadding="0" cellspacing="10" border="0">
   
<tr>
       
<td style="width: 70%; vertical-align:top;">
           
<h3>
               
Edit the list of links you want to display</h3>
           
<telerik:RadGrid ID="linksGrid" runat="server" AutoGenerateColumns="False" GridLines="None"
               
Skin="Web20">
               
<MasterTableView CommandItemDisplay="Top">
                   
<CommandItemSettings AddNewRecordText="Add new link" />
                   
<RowIndicatorColumn Visible="False">
                       
<HeaderStyle Width="20px"></HeaderStyle>
                   
</RowIndicatorColumn>
                   
<ExpandCollapseColumn Visible="False" Resizable="False">
                       
<HeaderStyle Width="20px"></HeaderStyle>
                   
</ExpandCollapseColumn>
                   
<Columns>
                       
<telerik:GridBoundColumn DataField="Key" HeaderText="Link title" UniqueName="Key">
                       
</telerik:GridBoundColumn>
                       
<telerik:GridBoundColumn DataField="Value" HeaderText="Link URL" UniqueName="Value">
                       
</telerik:GridBoundColumn>
                       
<telerik:GridButtonColumn CommandName="Delete" Text="Delete" UniqueName="Delete">
                       
</telerik:GridButtonColumn>
                       
<telerik:GridButtonColumn CommandName="Edit" Text="Edit" UniqueName="Edit">
                       
</telerik:GridButtonColumn>
                   
</Columns>
                   
<EditFormSettings>
                       
<PopUpSettings ScrollBars="None"></PopUpSettings>
                   
</EditFormSettings>
               
</MasterTableView>
           
</telerik:RadGrid>
       
</td>
       
<td style="vertical-align:top;">
           
<p>
               
<h3>Open links in :</h3>
               
<asp:RadioButtonList ID="targetRadioButtonList" runat="server" RepeatDirection="Horizontal">
                   
<asp:ListItem Selected="True" Value="">Current Window</asp:ListItem>
                   
<asp:ListItem Value="_BLANK">New Window</asp:ListItem>
               
</asp:RadioButtonList>
           
</p>
           
<p>
               
<h3>Choose links color:</h3>
               
<telerik:RadColorPicker ID="linksColorPicker" Runat="server" Preset="Standard"
                   
Skin="Default">
               
</telerik:RadColorPicker>
           
</p>
       
</td>
   
</tr>
</
table>
</
div>

 

The most important property of the ControlDesigner class is the Component property. The base ControlDesigner class will set this property to be the instance of the control which implemented the ControlDesigner – in our case Links List control. Generally, the Component property would be of the type of the control that implemented it. However, since our Links List control is a user control, we have implemented ILinksList interface which is implemented by our Links List control. By having the Component property to expose the underlying Links List control we can modify any of its properties by simply modifying the properties of Component property.

 

For the purpose of the ControlDesigner sample we have also added a new property to our LinksList control, namely the LinkColor property – which defines the color of the links in the list:

C# Copy Code
/// <summary>
/// Gets or sets the color of links
/// </summary>
[Category("Appearance")]
public string LinkColor
{
   get
   {
       
if (string.IsNullOrEmpty(linkColor))
           linkColor =
"#0000FF";
       
return linkColor;
   }
   set
   {
       linkColor = value;
   }
}

 

Once again, the logic for applying the color to the links will be performed in the Page_Load event handler:

C# Copy Code
protected void Page_Load(object sender, EventArgs e)
{
   
// set the text of title label to the value of ControlTitle property
   
controlTitleLabel.Text = ControlTitle;
   
// clear the bulleted list of any items and populate it
   
// with the values of ListLinks dictionary
   
linksBulletedList.Items.Clear();
   
foreach (KeyValuePair<string, string> listLink in ListLinks)
   {
       ListItem item =
new ListItem(listLink.Key, listLink.Value);
       item.Attributes[
"class"] = "linkList";
       linksBulletedList.Items.Add(item);
   }

   Style linkStyle =
new Style();
   linkStyle.ForeColor = System.Drawing.ColorTranslator.FromHtml(LinkColor);
   Page.Header.StyleSheet.CreateStyleRule(linkStyle,
null, ".linkList a");
}

   
In order to make sure that any CSS applied through the page theme does not interfere with our choice of link color, we are adding the css class to each list item and then registering the inline css declaration to the page.


The control described in this sample could be downloaded from here: LinksList7.