VisiPanel - 30 Oct 2009

Tour the Source Code of a Free, Colorful, Expanding Panel Web Control

ControlFreak

LANGUAGES: VB.NET | C#

ASP.NET VERSIONS: 1.x | 2.x

 

VisiPanel

Tour the Source Code of a Free, Colorful, Expanding Panel Web Control

 

By Steve C. Orr

 

A Web developer can never have too many good navigation controls around. Although ASP.NET 2.0 delivers some nice new options, it still doesn t provide anything resembling the expanding panel controls that are popular these days. I haven t seen many free ones around either, so I created one - VisiPanel. You can have it for free, and I ll show you how it all works under the hood in case you d like to learn from it or soup it up a bit.

 

VisiPanel is great for sidebars. It can act as a menu when filled with navigational links, or it can act as a command panel when filled with other kinds of controls. Figure 1 shows several instances of VisiPanel in action.

 


Figure 1: DirectX filters are responsible for VisiPanel s colorful gradient display. Client-side code is in place to smoothly expand and contract the panel when the user clicks on the title bar (without requiring postbacks).

 

User Friendly

At design time the VisiPanel control acts very much like a standard Panel Web control. This is related to the fact that VisiPanel inherits from the Panel control and extends it with enhanced functionality. Any kind of control can be dropped into VisiPanel, and its contents can be arranged in standard ways.

 

Beyond the functionality of the base Panel control, VisiPanel adds several properties and one event (see Figure 2). VisiPanel s OnExpandedChanged event is fired if the user has changed the dropdown state of the control between postbacks. The HeaderText property manages the text displayed in the header portion of the control at run time. The Expanded property toggles the initial dropdown state of the control, so it can be opened or closed programmatically. Finally, the GradientEndColor property can be combined with the BackColor property to provide an alluring background gradient coloring effect at run time.

 

Unique VisiPanel Members

Description

OnExpandedChanged event

VisiPanel s OnExpandedChanged event is fired when the user changes the dropdown state of the control between postbacks.

HeaderText property

The HeaderText property specifies the text that should be displayed in the header portion of the control at run time.

Expanded property

Expanded property toggles the initial dropdown state of the control so it can be opened and closed programmatically.

GradientEndColor property

The The GradientEndColor property can be mixed with the BackColor property to provide an alluring background gradient coloring.

Figure 2: Beyond the functionality of the underlying Panel control, VisiPanel adds several unique properties and one new event.

 

That s about all you need to know to get started using the control. Download the control (see end of article for download details) or enter the code from Listing One into a new Web Control Library project in Visual Studio. To add the control to your Visual Studio toolbox, right click on the toolbox and follow the prompts to browse for VisiPanel.dll. Finally, drag the control from the toolbox onto any WebForm and configure its properties, or enter a declaration such as this into HTML view of the ASPX page:

 

 runat="server"

 BackColor="Beige"

 GradientEndColor="Tan"

 HeaderText="My Header Text">

 Hello World

 

The rest of this article describes the inner workings of the VisiPanel control, so you can learn from it or extend the control with even more advanced functionality.

 

Eye Candy

If you read my Eye Candy article, then you re already familiar with the DirectX filter technique VisiPanel is using for its colorful gradient rendering. The following style declaration does the trick:

 

style="display:block;FILTER:

progid:DXImageTransform.Microsoft.Gradient

(startColorstr='Blue', endColorstr='Red',gradientType='0');"

 

Only Internet Explorer supports this technique (although other browsers degrade nicely) and it s very picky about syntax details, such as having white space and carriage returns in all the right places, so it s nice to have that functionality wrapped into a control like this that can handle the HTML rendering perfectly every time.

 

Structural Integrity

The VisiPanel output is made up of two primary elements, one above the other. Figure 3 illustrates this fact. A single-rowed, three-celled HTML table is on top, followed by the inherited Panel control on bottom. The configurable header text is displayed in the first table cell, followed by two Webdings font characters in the final two cells to represent arrows. Only one of these final two cells is ever displayed at a time, depending on the current expanded or contracted state of the control.

 


Figure 3: VisiPanel is made up of two primary parts: An HTML table is on top; on the bottom is the output of a standard Panel Web control from which VisiPanel inherits.

 

The VisiPanel code manages the output of the top header table and adds a few attributes to the underlying Panel s output for cosmetic purposes.

 

The bottom portion of the control is the (slightly modified) output of a standard Panel Web control. ASP.NET usually chooses to render the Panel as a standard

HTML tag. The base Panel control manages all the child controls, so you ll find no child control management code within VisiPanel at all - even though this functionality works great at run time and design time.

 

Figure 4 lists the custom JavaScript code that s rendered to handle the client-side OnClick event of the header table. This code toggles the visibility of the Panel s output and the arrow table cells. The final line writes the current dropdown state to a hidden field. Without this line, the control would resort to its default dropdown state every time the page posts back, thereby annoying the user by undoing their action. You might think of this as a kind of a home-grown ViewState (standard ViewState wouldn t work because it cannot be directly accessed on the client side).

 

//get references to the panel and the two arrow buttons

var oPnl=document.getElementById('VisiPanel1');

var oDown=document.getElementById('VisiPanel1_ButtonDown');

var oUp= document.getElementById('VisiPanel1_ButtonUp');

//toggle the visibility of these 3 elements

if (oPnl.style.display == 'none')

{

 oPnl.style.display = 'block';

 oDown.style.display='none';

 oUp.style.display='block';

}

else

{

 oPnl.style.display = 'none';

 oDown.style.display='block';

 oUp.style.display='none';

}

//store current visible state for server side processing

document.getElementById('VisiPanel1_hidden').value =

 oPnl.style.display;

Figure 4: This is the client-side JavaScript code that gets executed when the end user clicks the header table of the VisiPanel control. It toggles the Display style of the arrow cells and panel, then writes the state to a hidden textbox so server-side code will be able to determine the dropdown state upon the next postback.

 

The stored state information is also used by the control s server-side code the next time the page is posted back to determine if the user toggled the dropdown state, and, if so, raises the OnExpandedChanged event. You can find this code in the OnInit event shown in Listing One.

 

VisiPanel s constructor (Sub New) sets some appropriate defaults for the base Panel control, specifying the initial size and some other cosmetic details.

 

Rendering Outperforms Composition

To generate the three-celled header table, I could ve used Composition. That is, I could have instantiated a Table object and added three TableCell objects to its TabelRow object. This would generally be done within the CreateChildControls event of the server control. Although Composition is a great way to keep development quick and simple, there is a performance cost associated with instantiating all those objects. For smaller Web sites with less traffic, this likely isn t a big deal. However, if you re developing controls for a highly scalable Web site, you should be aware that Rendering outperforms Composition by a significant amount. That s why I chose Rendering instead of Composition. Rendering is done by overriding the Render event of the base server control and outputting the HTML in a comparatively manual fashion.

 

Listing One shows the overridden Render event, which makes extensive use of the HTMLTextWriter parameter that I ve abbreviated with the variable name w . The first code block uses a StringBuilder object to efficiently concatenate together the required JavaScript, such as that listed in Figure 4.

 

The second code block of the Render event generates the hidden textbox mentioned earlier. It is assigned Name and ID attributes so it can be more easily referenced from client-side code. I could ve used code similar to this to generate the hidden textbox:

 

w.Write("")

 

However, hard-coding HTML in this fashion is asking for future maintenance problems. With XHTML coming on strong in the future, and handheld devices of every kind supporting varying forms of HTML, letting ASP.NET make decisions about HTML generation details is usually a good idea. Because Microsoft practically defines what is proper HTML, it s a good idea to trust their judgment about what precisely should be generated for whichever device is making the request. By using methods such as AddAttribute and RenderBeginTag, ASP.NET decides the precise syntax that is output. Of course, there are many ways to adjust the output in cases where Microsoft s rendering technology has made a decision that contradicts your personal preferences.

 

The next four code blocks of the Render event use similar techniques to generate the header table and the three cells contained within. The mouse cursor style is set to hand to make it evident to the end user that this area is clickable. The JavaScript is assigned to the client-side OnClick event of the table and cosmetic attributes are added to ensure an attractive output. The final two cells are specified to use the Webdings font so the arrow characters will show appropriately. Only one of these arrow cells will be displayed at a time, depending on the current Expanded state of the control.

 

The final two code blocks of the Render event add attributes to the output of the underlying Panel control. First, it must be determined whether the panel will initially be displayed or hidden depending on the current Expanded state of the control. Finally, the base Panel control is instructed to render after the gradient color filter is applied.

 

Conclusion

What lessons have been learned here? By inheriting and extending the existing Panel control, we were able to implement a lot of functionality with surprisingly little code. DirectX filters can be used to spruce up the UI of nearly any existing control. A little JavaScript can go a long way toward improving the performance of Web controls. Rendering outperforms Composition, even though Composition is a somewhat simpler approach from a development perspective.

 

VisiPanel is an attractive control, capable of performing optimally under a heavy load. Expanding panels are a popular and intuitive UI metaphor these days, and adding them to a Web site can be an efficient use of screen real estate. Take the code and use it or extend it. If you find interesting ways to improve upon it, I d love to hear about them!

 

The source code for the VisiPanel control is available for download.

 

Steve C. Orr is an MCSD and a Microsoft MVP in ASP.NET. He s been developing software solutions for leading companies in the Seattle area for more than a decade. When he s not busy designing software systems or writing about them, he can often be found loitering at local user groups and habitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.net or e-mail him at mailto:[email protected]

 

Begin Listing One

Imports System.ComponentModel

Imports System.Web.UI

Imports System.Web.UI.WebControls

Imports System.Drawing

ToolboxData("<{0}:vp runat=server>"), _

DefaultEvent("OnExpandedChanged")> _

Public Class VisiPanel

   Inherits System.Web.UI.WebControls.Panel

#Region " Public Properties "

    Private _headerText As String = Me.ID

    _

   Property [HeaderText]() As String

       Get

           Return _headerText

       End Get

       Set(ByVal Value As String)

           _headerText = Value

       End Set

   End Property

   Private _GradientEndColor As Drawing.Color

    _

   Public Property GradientEndColor() As Color

       Get

           Return _GradientEndColor

       End Get

       Set(ByVal Value As Color)

           _GradientEndColor = Value

       End Set

   End Property

   Private _Expanded As Boolean = True

   

   DefaultValue("1")> _

   Public Property Expanded() As Boolean

       Get

           Return _Expanded

       End Get

       Set(ByVal Value As Boolean)

           _Expanded = Value

       End Set

   End Property

#End Region

#Region " Public Events "

   Public Event OnExpandedChanged(ByVal sender _

   As System.Object, ByVal e As System.EventArgs)

   Protected Overrides Sub OnInit(ByVal e As _

   System.EventArgs)

     If Page.IsPostBack Then

       'Determine if the user expanded or contracted

       'the VisiPanel and fire an

       'OnExpandedChanged event if they did

       Dim vis As String = Page.Request(Me.ClientID & _

           "_hidden").ToString().ToLower

       If (Not vis Is Nothing) Then

         If vis = "none" AndAlso _Expanded <> False Then

           _Expanded = False

           RaiseEvent OnExpandedChanged(Me, Nothing)

         End If

         If vis = "block" AndAlso _Expanded <> True Then

           _Expanded = True

           RaiseEvent OnExpandedChanged(Me, Nothing)

         End If

       End If

     End If

   End Sub

#End Region

 Public Sub New()

   MyBase.New()

   MyBase.BorderStyle = WebControls.BorderStyle.Solid

   MyBase.BorderWidth = New Unit(1, UnitType.Pixel)

   MyBase.Width = New Unit(150, UnitType.Pixel)

   MyBase.Height = New Unit(75, UnitType.Pixel)

   MyBase.BorderColor = Color.Black

 End Sub

 Protected Overrides Sub Render(ByVal w As _

 System.Web.UI.HtmlTextWriter)

   'build the javascript show/hide code

   Dim sb As New System.Text.StringBuilder

   sb.Append("var obj= document.getElementById('")

   sb.Append(Me.ClientID + "');")

   sb.Append("var objDown= document.getElementById('")

   sb.Append(Me.ClientID + "_ButtonDown');")

   sb.Append("var objUp= document.getElementById('")

   sb.Append(Me.ClientID + "_ButtonUp');")

   sb.Append("if (obj.style.display == 'none')")

   sb.Append("{obj.style.display = 'block';")

   sb.Append("objDown.style.display='none';")

   sb.Append("objUp.style.display='block';}")

   sb.Append("else {obj.style.display = 'none';")

   sb.Append("objDown.style.display='block';")

   sb.Append("objUp.style.display='none';}")

   sb.Append("document.getElementById('")

   sb.Append(Me.ClientID + "_hidden')")

   sb.Append(".value=obj.style.display;")

   Dim js As String = sb.ToString()

   'render a hidden field to hold the expanded status

   w.AddAttribute(HtmlTextWriterAttribute.Id, _

       Me.ClientID & "_hidden")

   w.AddAttribute(HtmlTextWriterAttribute.Name, _

       Me.ClientID & "_hidden")

   w.AddAttribute(HtmlTextWriterAttribute.Type, "hidden")

   w.RenderBeginTag(HtmlTextWriterTag.Input)

   w.RenderEndTag()

   'output the VisiPanel header in the form of a table

   w.AddStyleAttribute("Cursor", "hand")

   w.AddAttribute(HtmlTextWriterAttribute.Onclick, js)

   w.AddAttribute(HtmlTextWriterAttribute.Id, _

       Me.ClientID & "_header")

   w.AddAttribute(HtmlTextWriterAttribute.Class, _

       "VisiPanelHeader")

   w.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, _

       MyBase.BorderWidth.ToString)

   w.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, _

       MyBase.BorderStyle.ToString)

   w.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, _

       MyBase.BorderColor.ToKnownColor.ToString)

   w.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor, _

       MyBase.BackColor.ToKnownColor.ToString)

   w.AddStyleAttribute(HtmlTextWriterStyle.Width, _

       MyBase.Width.ToString)

   w.RenderBeginTag(HtmlTextWriterTag.Table) '

   w.RenderBeginTag(HtmlTextWriterTag.Tr) '

   w.RenderBeginTag(HtmlTextWriterTag.Td) '

   'output the VisiPanel header down button

   w.AddAttribute(HtmlTextWriterAttribute.Id, _

       Me.ClientID & "_ButtonDown")

   w.AddAttribute(HtmlTextWriterAttribute.Class, _

       "VisiPanelHeaderButtonDown")

   w.AddStyleAttribute(HtmlTextWriterStyle.FontFamily, _

       "WebDings")

   w.AddAttribute(HtmlTextWriterAttribute.Align, "right")

   w.AddStyleAttribute(HtmlTextWriterStyle.Width, "1%")

   If _Expanded Then w.AddStyleAttribute("display", _

       "none") Else w.AddStyleAttribute("display", "block")

   w.RenderBeginTag(HtmlTextWriterTag.Td) '

   'output the VisiPanel header up button

   w.AddAttribute(HtmlTextWriterAttribute.Id, _

       Me.ClientID & "_ButtonUp")

   w.AddAttribute(HtmlTextWriterAttribute.Class, _

       "VisiPanelHeaderButtonUp")

   w.AddStyleAttribute(HtmlTextWriterStyle.FontFamily, _

       "WebDings")

   w.AddAttribute(HtmlTextWriterAttribute.Align, "right")

   w.AddStyleAttribute(HtmlTextWriterStyle.Width, "1%")

   If _Expanded Then w.AddStyleAttribute("display", _

       "block") Else w.AddStyleAttribute("display", "none")

   w.RenderBeginTag(HtmlTextWriterTag.Td) '

   'close the table tags

   w.RenderEndTag() '

   w.RenderEndTag() '

   w.Write(Me.HeaderText)

   w.RenderEndTag() '

   w.Write(Chr(54)) 'down arrow

   w.RenderEndTag() '

   w.Write(Chr(53)) 'up arrow

   w.RenderEndTag() '

   'specify the visibility of the base panel control

   Dim vis As String

   If _Expanded Then vis = "block" Else vis = "none"

   w.AddStyleAttribute("display", vis)

   w.AddAttribute(HtmlTextWriterAttribute.Class, _

       "VisiPanel")

   'output the color gradient effect for the panel

   If _GradientEndColor.ToKnownColor.ToString <> "0" Then

       w.AddStyleAttribute("FILTER", _

           System.Environment.NewLine & _

           "progid:DXImageTransform.Microsoft.Gradient" & _

           "(startColorstr='" & _

           BackColor.ToKnownColor.ToString & _

           "', endColorstr='" & _

           GradientEndColor.ToKnownColor.ToString & _

           "', gradientType='0')")

   End If

   MyBase.Render(w) 'render the base panel control

 End Sub

End Class

End Listing One

 

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish