Collapsible Panel Control...
The panel control is a handy container control, especially for grouping controls together physically.
The panel control is a handy container control, especially for grouping controls together physically. The neat thing about panels is that through the Render method, additional content/controls can be added as a header, footer, or other function. The following control is a collapsible panel that shows/hides the panel content. A link handles the responsibility of collapsing/expanding the panel and its content through a postback; upon posting back, if the panel collapsed, the inner content of the panel is not rendered. Expanding shows the content again. That is the basic action of the panel. Here is how it works: an inner property, IsCollapsed, stores whether the panel is collapsed or not. The setter mechanism uses a new feature, where you can declare a separate access modifier for the getter or setter within the property. In this example, this allows the property to be public, while exposing the setter only to the control class.
|
<Browsable(False)> _ Public Property IsCollapsed() As Boolean Get If (ViewState("Collapsed") IsNot Nothing) Then Return CType(ViewState("Collapsed"), Boolean) End If Return False End Get Private Set(ByVal value As Boolean) ViewState("Collapsed") = value End Set End Property |
This property can’t change the collapsed state; rather, the only way to collapse and expand is by using the methods. There are two sets of events, Collapsing/Collapsed and Expanding/Expanded that fire when the appropriate method is called. The “ING” event passes in a CancelEventHandler with a Boolean variable stating whether to cancel the event, which the user of the control can do this through the event handler. The other event is called CollapsedStateChanged that fires every time the state of the collapsing actually changes. The definitions are defined below:
|
Public Sub Collapse() Dim objArgs As New CancelEventArgs(False) OnCollapsing(objArgs) If (Not objArgs.Cancel) Then If (Me.IsCollapsed <> True) Then Me.IsCollapsed = True OnCollapsedStateChanged(EventArgs.Empty) End If OnCollapsed(EventArgs.Empty) End If End Sub Public Sub Expand() Dim objArgs As New CancelEventArgs(False) OnExpanding(objArgs) If (Not objArgs.Cancel) Then If (Me.IsCollapsed <> False) Then Me.IsCollapsed = False OnCollapsedStateChanged(EventArgs.Empty) End If OnExpanded(EventArgs.Empty) End If End Sub |
Shown above, the events are fired in the On<EventName> methods, which can be overridden. This is a standard approach and is shown in the code. Next, the link that collapses/expands defines the text through two properties:
|
< _ Category("Appearance"), _ Description("The collapse text for the link"), _ DefaultValue("<<") _ > _ Public Property ToCollapseText() As String Get If (ViewState("ToCollapseText") IsNot Nothing) Then Return CType(ViewState("ToCollapseText"), String) End If Return "<<" End Get Set(ByVal value As String) ViewState("ToCollapseText") = value End Set End Property < _ Category("Appearance"), _ Description("The expand text for the link"), _ DefaultValue(">>") _ > _ Public Property ToExpandText() As String Get If (ViewState("ToExpandText") IsNot Nothing) Then Return CType(ViewState("ToExpandText"), String) End If Return ">>" End Get Set(ByVal value As String) ViewState("ToExpandText") = value End Set End Property |
The link is rendered in the render method, and implements IPostBackEventHandler functionality. A separate method renders the link, and the link’s href property stores the value of the Page.ClientScript.GetPostBackEventReference, which is a string containing the method that performs the postback. The method takes two properties; the first one is the reference to the control, and the second is the string for the event argument. The event argument is supplied on the post back, shown next.
|
Private Sub RenderButton(ByVal writer As HtmlTextWriter) writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:" & Page.ClientScript.GetPostBackEventReference(Me, IIf(Me.IsCollapsed, "expand", "collapse").ToString())) writer.RenderBeginTag(HtmlTextWriterTag.A) If (Me.IsCollapsed) Then writer.Write(Me.ToExpandText) Else writer.Write(Me.ToCollapseText) End If writer.RenderEndTag() End Sub |
The RaisePostBackEvent method receives the event argument, and if collapsing, call the collapse method; otherwise, call the expand method. Which action to take is actually determined above in setting the event argument (in the IIF statement) and is based off of the IsCollapsed property.
|
Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent If (eventArgument = "collapse") Then Me.Collapse() ElseIf (eventArgument = "expand") Then Me.Expand() End If End Sub |
For the button, I had a ButtonAlign and ButtonType (which doesn’t work) property that changes the alignment and type (again, doesn’t work). The button align changes the align in the rendered table. The final rendering of the control is shown below. A two row table is created; the first row renders the link for collapsing/expanding and the second row renders the Panel (base) render method.
|
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) writer.RenderBeginTag(HtmlTextWriterTag.Table) writer.RenderBeginTag(HtmlTextWriterTag.Tr) If (Me.ButtonAlign <> WebControls.HorizontalAlign.NotSet) Then writer.AddAttribute(HtmlTextWriterAttribute.Align, Me.ButtonAlign.ToString()) End If writer.RenderBeginTag(HtmlTextWriterTag.Td) Me.RenderButton(writer) writer.RenderEndTag() '/td writer.RenderEndTag() '/tr writer.RenderBeginTag(HtmlTextWriterTag.Tr) writer.RenderBeginTag(HtmlTextWriterTag.Td) If (Not Me.IsCollapsed) Then MyBase.Render(writer) End If writer.RenderEndTag() '/td writer.RenderEndTag() '/tr writer.RenderEndTag() '/table End Sub |
I originally tried to use control state, but the issue was that you have to load control state at the prerender stage, which was too late for me. Control state is a mechanism to store a property that is critical to the control; it retains the value even if viewstate is turned on or off. Here was my implementation:
|
Protected Overrides Sub LoadControlState(ByVal savedState As Object) Dim objPair As Pair = CType(savedState, Pair) If (objPair IsNot Nothing) Then MyBase.LoadControlState(objPair.First) _collapsed = CType(objPair.Second, Boolean) End If End Sub Protected Overrides Function SaveControlState() As Object Return New Pair(MyBase.SaveControlState(), _collapsed) End Function |
Future enhancements to this would be to setup the ButtonType property by putting extra code in the
RenderButton method. In addition, you could style the header row independently. You could expose
a template for the header as another approach. Alternatively, the collapsing/expanding could be
done by a javascript event, or by using callbacks, which is a javascript mechanism to connect to
the server, avoiding a postback while still connecting to the server.
The test page shows the panel, as well as two checkboxes, that can be checked to cancel the collapse
or the expand, as a means to test it out.