In the previous sample (download from here: LinksList5) we have demonstrated how to
implement complex properties on Sitefinity controls and represent them as strings. While we have achieved our goal of being able to expose complex properties, we have ended up
with a not-very-user-friendly way of editing these properties. In this topic we will implement a TypeEditor which will give our users simple and straightforward way of editing
complex properties.
Let’s start by looking at the screenshot of the end result:

Figure 1: "Select" button appears next to the property which implements TypeEditor attribute
After clicking on the "Select" button, the end user will get the user friendly screen for editing ListLinks property:

Figure 2: User interface for ListLinks property TypeEditor
The TypeEditor is actually a new control which has the single purpose of providing user interface for editing the property which has implemented it. As you can
see from Figure 2, the list of links has been represented through a grid which has commands for adding new links, editing and deleting existing ones. While our Links List
control is becoming more complex now (we have implemented two additional classes for it: DictionaryConverter and TypeEditor) it is also becoming more powerful and flexible, and at
the same time maintaining its simplicity for the end user.
Now that we have seen the end result, let’s see the code that will make this happen. First of all we need to implement an additional attribute on our ListLinks property:
| C# |
Copy Code |
|
/// <summary>
/// Gets or sets the list of items to be displayed by the bulleted list control
/// </summary> [Category("List settings")]
[TypeConverter("Telerik.Samples.DictionaryConverter, App_Code")]
[WebEditor("Telerik.Samples.LinksListItemEditor, App_Code")] public Dictionary<string, string> ListLinks
{
get
{
// if listLinks dictionary is null, we'll create an empty dictionary
// to avoid dealing with null reference in code
if (listLinks == null)
listLinks = new Dictionary<string, string>();
return listLinks;
}
set
{
listLinks = value;
}
}
|
In addition to the Category and TypeConverter attributes that we have implemented in previous samples, we will also add a WebEditor attribute which points to
the class (control) which provides user friendly interface for editing ListLinks property.
 |
The WebEditor class must be implemented as a custom control because it has to inherit from the
WebUITypeEditor generic class, and C# classes can inherit only one base class. Naturally, User Controls have to inherit from UserControl
class which makes it impossible to implement WebEditor as User Control. |
Type Editor Class
Our TypeEditor class is nothing more than a custom control which has its own user interface and handles the events of that user interface. The only important thing to note is
that the sole purpose of the TypeEditor is to come up with the value for the property it is editing. By inheriting from WebUITypeEditor generic class, we will
have to implement Value property on our TypeEditor class. The importance of this property is that once the user clicks on the "I’m done" button, Sitefinity will
take that property and set its value as the value of the property that we were editing with TypeEditor (in our case - ListLinks property).
Now, let us examine the code for LinksListItemEditor class:
| LinksListItemEditor.cs |
Copy Code |
|
using System; using System.Collections; using System.Web.UI; using
System.Web.UI.WebControls; using System.Collections.Generic; namespace Telerik.Samples
{
using Cms.Web.UI;
using Framework.Web;
using Web.UI;
/// <summary>
/// WebEditor control used for editing the items (ListLinks property)
/// of the links list in user friendly manner
/// </summary>
public class LinksListItemEditor :
WebUITypeEditor<Dictionary<string,string>>, IControlPropertyEditor
{
#region Properties
/// <summary>
/// Gets or sets the Value property - Value property represents the property which
TypeEditor
/// will edit of the parent control which fired the type editor. In our sample, Value
property
/// represents ListLinks property of the LinksList6.ascx user control
/// </summary>
public override Dictionary<string , string>
Value
{
get
{
return listLinks;
}
set
{
listLinks = value;
}
}
/// <summary>
/// Gets or sets the path of the Dialog template
/// </summary>
public string DialogTemplatePath
{
get
{
string value
= (string)ViewState["DialogTemplatePath"];
return String.IsNullOrEmpty(value) ? "~/SampleControls/Utilities/TypeEditorTemplate.ascx" :
value;
}
set
{
ViewState["DialogTemplatePath"] = value;
}
}
/// <summary>
/// Gets or sets the Dialog template
/// </summary>
public ITemplate DialogTemplate
{
get
{
dialogTemplate =
ControlUtils.GetTemplate<DefaultDialogTemplate>(DialogTemplatePath);
return dialogTemplate;
}
set
{
dialogTemplate = value;
}
}
/// <summary>
/// Returns a dictionary with the dependent properties
/// </summary>
/// <remarks>
/// DependentProperties property allows us to modify additional properties
/// (not just the value of the property which fired TypeEditor) of the control
which
/// has called TypeEditor. In this sample we will modify LinkTarget property in
addition
/// to ListLinks property which is represented through ValueProperty of this
control.
/// </remarks>
public IDictionary<string, object> DependentProperties
{
get
{
if (dependentProps == null)
{
dependentProps = new Dictionary<string, object>();
dependentProps.Add("LinkTarget", linkTarget);
}
return dependentProps;
}
}
#endregion
#region Methods
/// <summary>
/// Overriden. Cancels the rendering of a beginning HTML tag for the control.
/// </summary>
/// <param name="writer">The HtmlTextWriter object used to render the
markup.</param>
public override void
RenderBeginTag(HtmlTextWriter writer)
{
// Do not render begin tag
}
/// <summary>
/// Overriden. Cancels the rendering of an ending HTML tag for the control.
/// </summary>
/// <param name="writer">The HtmlTextWriter object used to render the
markup.</param>
public override void
RenderEndTag(HtmlTextWriter writer)
{
// Do not render end tag
}
/// <summary>
/// Overrides the base method and registers ControlDesignerBase control as one whose
control state
/// must be persisted.
/// </summary>
/// <param name="e"></param>
protected override void
OnInit(EventArgs e)
{
base.OnInit(e);
if (Page != null)
Page.RegisterRequiresControlState(this);
}
/// <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>
protected override void
LoadControlState(object savedState)
{
if (savedState !=
null)
{
object[]
state = (object[])savedState;
listLinks = (Dictionary<string, string>)state[0];
linkTarget = (string)state[1];
}
}
/// <summary>
/// Saves server control state changes.
/// </summary>
/// <returns></returns>
protected override object
SaveControlState()
{
return new object[] {
listLinks,
linkTarget
};
}
/// <summary>
/// Overriden. Called to populate the child control hierarchy. This is the main
/// method to render the control's markup, since it is a CompositeControl and
contains
/// child controls.
/// </summary>
protected override void
CreateChildControls()
{
// initialize the container and template
dialogContainer = new
Container(this);
DialogTemplate.InstantiateIn(dialogContainer);
dialogContainer.LinksGrid.MasterTableView.DataKeyNames = new string[] {"Key"};
dialogContainer.LinksGrid.DeleteCommand += new
GridCommandEventHandler(LinksGrid_DeleteCommand);
dialogContainer.LinksGrid.EditCommand += new
GridCommandEventHandler(LinksGrid_EditCommand);
dialogContainer.LinksGrid.CancelCommand += new
GridCommandEventHandler(LinksGrid_CancelCommand);
dialogContainer.LinksGrid.UpdateCommand += new
GridCommandEventHandler(LinksGrid_UpdateCommand);
dialogContainer.LinksGrid.InsertCommand += new
GridCommandEventHandler(LinksGrid_InsertCommand);
dialogContainer.LinksGrid.NeedDataSource += new
GridNeedDataSourceEventHandler(LinksGrid_NeedDataSource);
dialogContainer.TargetRadioButtonList.AutoPostBack = true;
dialogContainer.TargetRadioButtonList.SelectedIndexChanged += new EventHandler(TargetRadioButtonList_SelectedIndexChanged);
if (DependentProperties["LinkTarget"].ToString() == "_BLANK")
dialogContainer.TargetRadioButtonList.SelectedIndex = 1;
else
dialogContainer.TargetRadioButtonList.SelectedIndex = 0;
DependentProperties["LinkTarget"]
= linkTarget;
Controls.Add(dialogContainer);
}
#endregion
#region Private methods
private void BindGrid()
{
dialogContainer.LinksGrid.DataSource = Value;
dialogContainer.LinksGrid.DataBind();
}
#endregion
#region Event handlers
private void LinksGrid_NeedDataSource(object source, GridNeedDataSourceEventArgs e)
{
dialogContainer.LinksGrid.DataSource = Value;
}
private void LinksGrid_InsertCommand(object source, GridCommandEventArgs e)
{
GridEditableItem editedItem = e.Item as GridEditableItem;
Hashtable newValues = new Hashtable();
e.Item.OwnerTableView.ExtractValuesFromItem(newValues, editedItem);
listLinks.Add(newValues["Key"].ToString(), newValues["Value"].ToString());
BindGrid();
}
private void LinksGrid_DeleteCommand(object source, GridCommandEventArgs e)
{
string key =
e.Item.OwnerTableView.Items[e.Item.ItemIndex]["Key"].Text;
if (listLinks.ContainsKey(key))
listLinks.Remove(key);
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 listLinks)
{
if(link.Key
== key)
updatedListLinks.Add(newValues["Key"].ToString(),
newValues["Value"].ToString());
else
updatedListLinks.Add(link.Key, link.Value);
}
listLinks = updatedListLinks;
BindGrid();
}
private void TargetRadioButtonList_SelectedIndexChanged(object sender, EventArgs e)
{
linkTarget = dialogContainer.TargetRadioButtonList.SelectedValue;
}
#endregion
#region Private Propeties
private Dictionary<string, string> listLinks;
private string linkTarget;
private ITemplate dialogTemplate;
private Container dialogContainer;
private Dictionary<string, object> dependentProps;
#endregion
#region Default Templates
/// <summary>
/// Class which defines the template for this control, in case template has not
been
/// defined through an external file or through inline declaration
/// </summary>
private class DefaultDialogTemplate : ITemplate
{
public void InstantiateIn(Control container)
{
throw new Exception("Default template for LinksListItemEditor has not been
implemented.");
}
}
#endregion
#region Containers
/// <summary>
/// Class which provides easy, strongly-typed access to the child controls defined in
a template
/// regardless of their position in the child control hierarchy
/// </summary>
private class Container :
GenericContainer<LinksListItemEditor>
{
public Container(LinksListItemEditor
owner)
: base(owner)
{
}
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;
}
}
private RadGrid linksGrid;
private RadioButtonList
targetRadioButtonList;
}
#endregion
}
}
|
The user interface for this control has been declared through the external template whose path is defined through DialogTemplatePath. The template code looks like this:
| TypeEditorTemplate.ascx |
Copy Code |
|
<h3>
Edit the list of links you want to display</h3>
<p>
Open links in :
<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> <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>
|
The topic of the control development in ASP.NET is beyond the scope of this article. The important thing to note is that the TypeEditor control is focused on managing the Value
property, which is nothing more than a mapped ListLinks property from our Links List control.
 |
Notice how we do not have to worry about serializing the Value property, but we are able to work directly with the
Dictionary<string, string> type. By implementing the TypeConverter on that property we have killed two birds with one stone: we have enabled Sitefinity to
treat this property as a string, while at the same time we are free to work with the property in a more natural way – which, in this case, is a
Dictionary<string, string> type. |
Dependant Properties : How to Modify More Properties through TypeEditor of One Property
Sometimes you will have properties that, when edited, affect other properties. A classic example for this is the ProviderName property used in majority of built-in Sitefinity
controls. Namely, let’s take the built-in Sitefinity control Categories List. You may choose to display the categories from Blogs provider, but also you may choose to select
categories from the News provider. Once you open the TypeEditor for SelectedCategories property you can change the provider from which you want to select categories.
So, although the TypeEditor’s primary task is to let users select categories, the user could also choose a provider, and the TypeEditor will set the
ProviderName property of the CategoriesList control as well (not only SelectedCategories property). This is where the DependantProperties comes
into play.
If you take a look at the TypeEditor code above you will notice that LinkTarget property of Links List control has been declared as DependantProperty. While,
in reality this does not make much sense, it lends itself quite well to demonstrate the principle.
You can download the control for this sample from here: LinksList6.