A LinkButtonList Control...
Composite controls are a collection of child controls that are grouped together into a single parent control. This happens by creating controls programmatically at runtime and adding them to a collection. This article will implement a LinkButtonList composite control, which will be a collection of custom LinkButtons in the form of a list control.
Composite controls are a collection of child controls that are grouped together into a single parent control. This happens by creating controls programmatically at runtime and adding them to a collection. This article will implement a LinkButtonList composite control, which will be a collection of custom LinkButtons in the form of a list control. The ListControl class that will be inherited from offers many nice features that the control can take advantage of. For example, by inheriting from this class, the control can take advantage of data entry through the Items collection property, similar to the CheckBoxList or RadioButtonList control. In addition, there are several protected methods that are available to handle style and rendering functionality.
A standard LinkButton control can't be used for this control. The reason is the LinkButton must also have the Value and Selected properties to fully accept all the parameters from the ListItem object. This object holds all of the data entered through the Items property, as mentioned above. Everytime one of the LinkButtons is clicked, the page posts back with these values selected. This class has the following definition below:
|
<ToolboxData("<{0}:LinkButtonList runat=server></{0}:LinkButtonList>")> _ Public Class LinkButtonList Inherits ListControl Implements INamingContainer End Class |
Several items are important to note here. First, the ToolboxData attribute affects the appearance in the ASP.NET HTML code, whenever you drag-and-drop the control onto the form. The "{0}" is used to receive the TagPrefix property defined in the @Register directive. This is a common notation when using the String.Format method, which replaces the "{0}" with the TagPrefix. Many attributes can be set for the control, such as DefaultProperty, DefaultEvent, Description, etc. These attributes are defined in the System.ComponentModel namespace, and they affect the appearance of the control and its properties in the designer. In addition, the INamingContainer interface resolves naming conflicts for each control. Whenever there is more than one control with the same ID (or no ID provided at all, which is common when creating controls programmatically), the INamingContainer ensures a unique name by versioning the controls (LinkButton1, LinkButton2, etc.).
The ListControl uses ListItem objects to represent each item in the list. Because the standard LinkButton doesn't support all of these parameters, a custom LinkButton implementation will be used, which is shown below:
|
Public Class LinkButton Inherits System.Web.UI.WebControls.LinkButton Private m_blnSelected As Boolean <DefaultValue(True), Description("Selected property")> _ Public Property Selected() As Boolean Get Return m_blnSelected End Get Set(ByVal Value As Boolean) End Set End Property Public m_strValue As String <Description("The value of the link")> _ Public Property Value() As String Get Return m_strValue End Get Set(ByVal Value As String) m_strValue = Value End Set End Property Public Sub New() MyBase.New() End Sub Public Sub New(ByVal strID As String) MyBase.New() Me.ID = strID End Sub End Class |
For this type of control, it would be most beneficial if the flow of the control be altered at runtime to handle this, which several properties exist to handle this. The first alters the direction of the control (horizontal or vertical rendering) through an enumerated value. Secondly, the spacing between each link can be altered, increasing or decreasing based on an integer value. And lastly, the enabled state of the control can be altered when the LinkButton is selected and posted back to the server. Because of the server round-trips involved, this property can be useful in preventing the user from accidentally clicking a link twice. These properties are listed below:
|
Private m_objDirection As DirectionType = DirectionType.Horizontal <DefaultValue(GetType(DirectionType), "Horizontal"), Description("The direction for the flow of the control")> _ Public Property Direction() As DirectionType Get Return m_objDirection End Get Set(ByVal Value As DirectionType) m_objDirection = Value End Set End Property Private m_blnEnableSelectedItem As Boolean = True <DefaultValue(True), Description("Whether you can click the selected item")> _ Public Property EnableSelectedItem() As Boolean Get Return m_blnEnableSelectedItem End Get Set(ByVal Value As Boolean) m_blnEnableSelectedItem = Value End Set End Property Private m_intSpacing As Integer = 0 <DefaultValue(0), Description("Spacing between each link")> _ Public Property Spacing() As Integer Get Return m_intSpacing End Get Set(ByVal Value As Integer) m_intSpacing = Value End Set End Property |
These properties affect the outcome of the control creation in the CreateChildControls method, which handles the population of the controls. The exact time when this method is executed cannot be determined, which is the purpose of the EnsureChildControls method and ChildControlsCreated property. If you notice the properties defined above, they do not call it because the values do not rely on the controls being added to the collection; however, the Render does:
|
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) Me.EnsureChildControls() Me.RenderChildren(writer) End Sub |
The RenderChildren method, which renders the controls in the collection to the browser individually, relies on the CreateChildControls method being called. The EnsureChildControls method verifies that, and calls it if needed; otherwise, if this method wasn’t used, it is possible that no controls would be rendered to the browser.
The properties declared above affect the control flow when the LinkButtons are rendered to the browser. Based on a horizontal or vertical direction setting, the "<br>" or " " characters are used appropriately. The " " character is a non-breaking space character and will accurately reflect the number of spaces in the browser. If you are not familiar with all of the ampersand directives in HTML, you can get a list of them off of the web site. Usually they are combined with an ASCII conversion chart.
The following code is the control spacing implementation in the CreateChildControls method. Although the Direction property specifies the direction of the flow, if the Spacing property is less then or equal to zero, the control will always flow horizontally. This is because the For loop construct will never execute.
|
Dim strSpacer As String = "" 'Create the spacing string; if vertical, use <br> tags; 'otherwise use If (m_objDirection = DirectionType.Vertical) Then For intI As Integer = 1 To m_intSpacing strSpacer &= "<br>" Next Else For intI As Integer = 1 To m_intSpacing strSpacer &= " " Next End If |
Assuming the Direction is set and the Spacing is a non-negative, non-zero number, a spacer string will be added to the controls collection through the LiteralControl object.
|
If (strSpacer <> String.Empty) Then 'Add a spacer in between Me.Controls.Add(New LiteralControl(strSpacer)) End If |
Before we get into the addition of the LinkButtons to the collection, you have to first understand the purpose of two events declared for the control. The ItemCreated event, similar to the DataGrid's implementation, fires every time a LinkButton control is added to the controls collection. This event can be very useful, especially if you are creating JavaScript attributes for the control. The other event is a shadow of the SelectedIndexChanged event. It's declared as shadows because the composite control must be able to raise it whenever the LinkButton is clicked. This event also uses a custom event argument, named SelectedIndexChangedEventArgs.
In addition, each of these events has a protected function with a prefix of "On", which its primary purpose is to raise the event. This is a common implementation for controls, to handle events in this manner.
|
Public Event ItemCreated As EventHandler Public Shadows Event SelectedIndexChanged(ByVal sender As Object, ByVal e As SelectedIndexChangedEventArgs) 'Only purpose is to call the appropriate event; implementing in this way will make changes 'to the event call easier Protected Sub OnItemCreated(ByVal e As EventArgs) RaiseEvent ItemCreated(Me, e) End Sub Protected Shadows Sub OnSelectedIndexChanged(ByVal e As SelectedIndexChangedEventArgs) RaiseEvent SelectedIndexChanged(Me, e) End Sub 'Custom event argument object, which takes a custom LinkButton in the constructor Public Class SelectedIndexChangedEventArgs Inherits System.EventArgs Public Link As LinkButton Public Shadows ReadOnly Property Empty() As SelectedIndexChangedEventArgs Get Return New SelectedIndexChangedEventArgs(Nothing) End Get End Property Friend Sub New(ByVal objLink As LinkButton) Link = objLink End Sub End Class |
For each ListItem created through the Items property in the Visual Studio .NET designer, these values are transferred to the custom LinkButton controls. Note below that when the LinkButtons are created, no ID is provided; this is automatically handled by the INamingContainer interface.
None of the LinkButtons utilize the ViewState; this allows the LinkButton to return to its normal state. In future uses of this control, if this causes problems, the controls collection must be iterated and each LinkButton reset to the default settings. The LinkButton implementation is shown below:
|
'Add the items to the controls collection as linkbuttons in CreateChildControls method
For Each objItem As ListItem In Me.Items Dim objLink As New LinkButton With objLink .Text = objItem.Text .Value = objItem.Value .Selected = objItem.Selected .Enabled = True .EnableViewState = False 'Add a handler for the click event AddHandler .Click, AddressOf lnkButton_Click End With 'Add control to the collection Me.Controls.Add(objLink) 'Every time a link is added the ItemCreated event is raised OnItemCreated(EventArgs.Empty) |
One line of code not mentioned before is the use of the AddHandler method. This method is another way of declaring an event handler; the parameters state the event that will be handled and the address of the delegated function that will receive the focus. In case of the previous example, whenever the LinkButton is clicked, the lnkButton_Click method is called. This is very important because this new event method will handle the changing of the currently selected LinkButton.
One key feature of the control is that the actual LinkButton’s properties are not changed; instead, the ListItem's Selected property is altered. Because the LinkButtons don't utilize the ViewState, the control only needs to worry about setting the ListItem’s properties. The next time the control refreshes, which is after the click is done, the new LinkButton will be selected. Lastly, the SelectedIndexChanged event is raised with the clicked LinkButton.
|
Private Sub lnkButton_Click(ByVal sender As Object, ByVal e As EventArgs) Dim objLink As LinkButtonListCC.LinkButton = CType(sender, LinkButtonListCC.LinkButton) 'Set the current ListItem to be selected Me.Items.FindByValue(objLink.Value).Selected = True objLink.Enabled = m_blnEnableSelectedItem OnSelectedIndexChanged(New SelectedIndexChangedEventArgs(objLink)) End Sub |
This is a simple implementation of this control, with plenty of room for growth in the future. I hope that this article was good start in implementing composite controls.
You may download the code here.