The ListItemsCollectionEditor Class...
It is possible to create a custom editor from one of the derived type editors, such as the ListItemsCollectionEditor.
Editors are a pretty neat feature in the .NET framework. If you've used Visual Studio .NET 2003 or 2005, you've seen examples of this already. For example, whenever a color property (such as ForeColor or BackColor) is selected, a drop-down arrow becomes visible. A tabular form with many color options appears, allowing the user to select a desirable color. Upon selecting a color, the designer disappears and the field contains the name (in string form) of the color selected. Another example, which is more suitable for this article, is the CheckBoxList control. When the Items property is selected, an ellipsis appears, opening up a collection editor. Through this editor, items are created or removed via the Add/Remove buttons at the bottom left of the form. The left section of the form has all of the items entered in the collection, whereas the right section has the details for an individual item.
Both of these examples are usages of Editors within Visual Studio .NET. An editor is a design-time class whose purpose is to making the editing of a property easier. Oftentimes, a windows form or an editor service object renders the editor, but in this article, neither of these approaches is used (this will be written in another article). The actual creation of the editor is really simple and will be discussed later.
Editors are derived from the System.Drawing.Design.UITypeEditor class, which is the base class for most (if not all) existing editors in the .NET framework. It is possible to create a custom editor from one of the derived type editors, such as the ListItemsCollectionEditor. This editor is a specialized editor for collection classes, which is derived in the System.Web.UI.Design.WebControls namespace. The editor will replicate the same functionality above, with an additional property Depth. This property and new collection will be a part of a LayeredCheckBoxList control. Please note that the control isn't meant to work; it is only meant to illustrate the use of an editor.
The LayeredCheckBoxList class uses a LayeredListItemCollection to represent the custom collection with the Depth property. The collection contains a series of LayeredListItem objects that represent the list items with the additional Depth property. The LayeredCheckBoxList and the collection property for the control, LayeredItems, are illustrated below. The _Items variable contains the reference to the collection. To link this property to the editor, the Editor attribute defines the type of the editor to use, and the editor type that it inherits from. The PersistenceMode defines how the collection will be persisted (more about this shortly).
|
Public Class LayeredCheckBoxList Inherits CheckBoxList 'Attributes: ' Editor - defines the editor to use when editing the collection; elipsis appear ' that envoke the editor. The second param is the base editor class. ' DesignerSerializationVisibility - show the content of the items properly ' PersistenceMode - Declare it as an attribute or child property of the element; ' in this example, items will appear underneath the control. Private _Items As New LayeredListItemCollection <Editor(GetType(LayeredListItemCollectionEditor), GetType(System.Web.UI.Design.WebControls.ListItemsCollectionEditor)), _ DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), _ PersistenceMode(PersistenceMode.InnerProperty)> _ Public Shadows Property LayeredItems() As LayeredListItemCollection Get Return _Items End Get Set(ByVal Value As LayeredListItemCollection) _Items = Value End Set End Property End Class |
Setting a PersistenceMode of inner property nests the collection beneath the server tag. All of the collection items are stored as a LayeredListItem. Note that there is no default property for the object; more on this later. For now, understand the structure.
|
<cc1:LayeredCheckBoxList id="LayeredCheckBoxList1" runat="server"> <LayeredItems> <cc1:LayeredListItem Value="asdf" Text="asdfasdfdsf" Depth="0" Selected="False"></cc1:LayeredListItem> <cc1:LayeredListItem Value="asdfdsasdaf" Text="asdfasdfsadfasdf" Depth="0" Selected="False"></cc1:LayeredListItem> </LayeredItems> </cc1:LayeredCheckBoxList> |
The following code is the collection class represented in the web control. The class specifies an Item property for get and set operations on individual LayeredListItem objects. It also contains methods for adding, removing, and searching the collection for existing LayeredListItem objects; however, these methods are pretty self-explanatory and won’t be discussed in the article.
|
<DefaultProperty("Item")> _ Public Class LayeredListItemCollection Inherits CollectionBase 'The item property accesses the items in the collection <DesignerSerializationVisibility(DesignerSerializationVisibility. Visible), _ PersistenceMode(PersistenceMode.Attribute)> _ Default Public Property Item(ByVal intIndex As Integer) As LayeredListItem Get Return list.Item(intIndex) End Get Set(ByVal Value As LayeredListItem) list.Item(intIndex) = Value End Set End Property End Class |
The individual LayeredListItem object, that the collection control will store, defines four properties. Each of these properties defined in this class will be rendered in the collection editor as a property for an individual item (in the right section of the editor shown above). Three are shown below:
|
Public Class LayeredListItem Private _Depth As Integer Public Property Depth() As Integer Get Return _Depth End Get Set(ByVal Value As Integer) _Depth = Value End Set End Property Private _Selected As Boolean Public Property Selected() As Boolean Get Return _Selected End Get Set(ByVal Value As Boolean) _Selected = Value End Set End Property Private _Value As String Public Property Value() As String Get Return _Value End Get Set(ByVal Value As String) _Value = Value End Set End Property |
The last property (Text) has a PersistenceMode attribute that renders it differently from the others. Previously I said that the LayeredListItem object didn’t have a default property. By setting the PersistenceMode of this property to EncodedInnerDefaultProperty, the text property is rendered as a default between the <cc1:LayeredListItem></cc1:LayeredListItem> elements. Only one property can be declared the default; all other properties must be declared as an Attribute. Lastly, the value is encoded, which is a way to allow special characters appear in the text (such as HTML tags) without any complications.
|
Private _Text As String <PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)> _ Public Property Text() As String Get Return _Text End Get Set(ByVal Value As String) _Text = Value End Set End Property |
Our new rendering of the control with the default property is:
|
<cc1:LayeredCheckBoxList id="LayeredCheckBoxList4" runat="server"> <LayeredItems> <cc1:LayeredListItem Value="1" Depth="0" Selected="False">sadfasdf</cc1:LayeredListItem> <cc1:LayeredListItem Value="2" Depth="1" Selected="False">asdfasdf</cc1:LayeredListItem> </LayeredItems> </cc1:LayeredCheckBoxList> |
The class has one more notable feature. The ToString() method controls the representation of the left section of the editor (see the below image). Whatever is returned in the function is rendered for each LayeredListItem. To illustrate this, for each new item, the text of “ {New Implementation}” is added to the text of the element. By default, the standard ListItem object only renders the text property.
|
'Renders for each item in the collection editor in the left part of the screen... 'enter whatever you want to appear in that window here Public Overrides Function ToString() As String Return _Text & " {New Implementation}" End Function |
Now to the editor; the Editor in this case is the simplest part. It inherits from System.Web.UI.Design.WebControls.ListItemsCollectionEditor. The requirement for this class is to define a Sub New() routine, and make a call to the base classes new statement. The only required parameter is the type of the collection to render the editor for. So the type (using GetType()) is passed in for our LayeredListItemCollection class. Previously, the Editor was defined as an attribute for our collection property (LayeredListItems). That property links the control to this custom editor.
|
'Simple enough to create the editor; only required to specify the 'collection name Public Class LayeredListItemCollectionEditor Inherits System.Web.UI.Design.WebControls.ListItemsCollectionEditor Public Sub New() 'Specifies the type of the list item collection (our custom class) 'to edit; ellipsis appear for the property using this editor, and 'the editor loads and edits this collection type MyBase.New(GetType(LayeredListItemCollection)) End Sub End Class |
The final implementation of the CollectionEditor appears below, with our new Depth property in the collection editor. This property will control the indenting of the checkbox field during rendering. Note the “ {New Implementation}” addition returned from the ToString method.
There is one final change needed when setting the PersistenceMode attribute to a value other than “Attribute.” To get the control to parse correctly, two more attributes are needed: the ParseChildren and PersistChildren attributes. These properties allow the control to render as a nested server control, and that these properties don’t need to be parsed. Otherwise, the Visual Studio .NET designer returns an error in the control, because the <LayeredListItems> element is not part of the active schema. Our final implementation of the class is shown below:
|
<DefaultProperty("LayeredItems"), ParseChildren(False), PersistChildren(True)> _ Public Class LayeredCheckBoxList |
About the Code
This code wasn't meant to be functional. You can see that from the rendering on the screen. No elements added to the LayeredItems property are rendered on the screen. This example was only meant to illustrate one form of the ListItemsCollectionEditor class. In addition, I left the Items property remain (a property containing a collection of type ListItemCollection) to illustrate the differences between them. Remember that this example is for design-time editing only. This project doesn't need to be executed to illustrate this example.
You may download the code here.