VisiPanel - 30 Oct 2009

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

Steve C Orr

October 30, 2009

11 Min Read
ITPro Today logo in a gray background | ITPro Today

ControlFreak

LANGUAGES:VB.NET | C#

ASP.NETVERSIONS: 1.x | 2.x

 

VisiPanel

Tour the Source Code of a Free, Colorful, Expanding PanelWeb Control

 

By Steve C. Orr

 

A Web developer can never have too many good navigationcontrols around. Although ASP.NET 2.0 delivers some nice new options, it stilldoesn t provide anything resembling the expanding panel controls that arepopular these days. I haven t seen many free ones around either, so I createdone VisiPanel. You can have it for free, and I ll show you how it all worksunder 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 whenfilled with navigational links, or it can act as a command panel when filledwith other kinds of controls. Figure 1 shows several instances of VisiPanel inaction.

 


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

 

User Friendly

At design time the VisiPanel control acts very much like astandard Panel Web control. This is related to the fact that VisiPanel inheritsfrom the Panel control and extends it with enhanced functionality. Any kind ofcontrol can be dropped into VisiPanel, and its contents can be arranged instandard ways.

 

Beyond the functionality of the base Panel control,VisiPanel adds several properties and one event (see Figure 2). VisiPanel sOnExpandedChanged event is fired if the user has changed the dropdown state ofthe control between postbacks. The HeaderText property manages the textdisplayed in the header portion of the control at run time. The Expandedproperty toggles the initial dropdown state of the control, so it can be openedor closed programmatically. Finally, the GradientEndColor property can becombined with the BackColor property to provide an alluring background gradientcoloring 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

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

GradientEndColor property

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

Figure 2: Beyondthe functionality of the underlying Panel control, VisiPanel adds severalunique properties and one new event.

 

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

 

 runat="server"  BackColor="Beige"  GradientEndColor="Tan"  HeaderText="MyHeader Text">  Hello World

 

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

 

Eye Candy

If you read my EyeCandy article, then you re already familiar with the DirectX filtertechnique VisiPanel is using for its colorful gradient rendering. The followingstyle declaration does the trick:

 

style="display:block;FILTER:

progid:DXImageTransform.Microsoft.Gradient

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

 

Only Internet Explorer supports this technique (althoughother browsers degrade nicely) and it s very picky about syntax details, suchas having white space and carriage returns in all the right places, so it snice to have that functionality wrapped into a control like this that canhandle 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-celledHTML table is on top, followed by the inherited Panel control on bottom. Theconfigurable header text is displayed in the first table cell, followed by twoWebdings font characters in the final two cells to represent arrows. Only oneof these final two cells is ever displayed at a time, depending on the currentexpanded or contracted state of the control.

 


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

 

The VisiPanel code manages the output of the top headertable and adds a few attributes to the underlying Panel s output for cosmeticpurposes.

 

The bottom portion of the control is the (slightlymodified) output of a standard Panel Web control. ASP.NET usually chooses torender the Panel as a standard

HTML tag. The base Panel controlmanages all the child controls, so you ll find no child control management codewithin VisiPanel at all even though this functionality works great at run timeand design time.

 

Figure 4 lists the custom JavaScript code that s renderedto handle the client-side OnClick event of the header table. This code togglesthe visibility of the Panel s output and the arrow table cells. The final linewrites the current dropdown state to a hidden field. Without this line, thecontrol would resort to its default dropdown state every time the page postsback, thereby annoying the user by undoing their action. You might think ofthis as a kind of a home-grown ViewState (standard ViewState wouldn t work becauseit 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 isthe client-side JavaScript code that gets executed when the end user clicks theheader table of the VisiPanel control. It toggles the Display style of thearrow cells and panel, then writes the state to a hidden textbox so server-sidecode will be able to determine the dropdown state upon the next postback.

 

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

 

VisiPanel s constructor (Sub New) sets some appropriatedefaults for the base Panel control, specifying the initial size and some othercosmetic details.

 

Rendering Outperforms Composition

To generate the three-celled header table, I could ve usedComposition. That is, I could have instantiated a Table object and added threeTableCell objects to its TabelRow object. This would generally be done withinthe CreateChildControls event of the server control. Although Composition is agreat way to keep development quick and simple, there is a performance cost associatedwith 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 ahighly scalable Web site, you should be aware that Rendering outperformsComposition by a significant amount. That s why I chose Rendering instead ofComposition. Rendering is done by overriding the Render event of the baseserver control and outputting the HTML in a comparatively manual fashion.

 

Listing One shows the overridden Render event, which makesextensive use of the HTMLTextWriter parameter that I ve abbreviated with thevariable name w . The first code block uses a StringBuilder object toefficiently concatenate together the required JavaScript, such as that listedin Figure 4.

 

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

 

w.Write("")

 

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

 

The next four code blocks of the Render event use similartechniques to generate the header table and the three cells contained within. Themouse cursor style is set to hand to make it evident to the end user thatthis area is clickable. The JavaScript is assigned to the client-side OnClickevent of the table and cosmetic attributes are added to ensure an attractiveoutput. The final two cells are specified to use the Webdings font so the arrowcharacters will show appropriately. Only one of these arrow cells will bedisplayed at a time, depending on the current Expanded state of the control.

 

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

 

Conclusion

What lessons have been learned here? By inheriting andextending the existing Panel control, we were able to implement a lot offunctionality with surprisingly little code. DirectX filters can be used tospruce up the UI of nearly any existing control. A little JavaScript can go along way toward improving the performance of Web controls. Renderingoutperforms Composition, even though Composition is a somewhat simpler approachfrom a development perspective.

 

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

 

The source code forthe VisiPanel control is available for download.

 

Steve C. Orr is anMCSD and a Microsoft MVP in ASP.NET. He s been developing software solutionsfor leading companies in the Seattlearea for more than a decade. When he s not busy designing software systems orwriting about them, he can often be found loitering at local user groups andhabitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.netor 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   InheritsSystem.Web.UI.WebControls.Panel#Region " Public Properties "    Private _headerText AsString = Me.ID    _   Property [HeaderText]()As String       Get           Return_headerText       End Get       Set(ByVal Value AsString)            _headerText =Value       End Set   End Property   Private_GradientEndColor As Drawing.Color    _   Public PropertyGradientEndColor() As Color       Get           Return_GradientEndColor       End Get       Set(ByVal Value AsColor)            _GradientEndColor = Value       End Set   End Property   Private _Expanded As Boolean= True      DefaultValue("1")> _   Public PropertyExpanded() As Boolean       Get           Return_Expanded       End Get       Set(ByVal Value AsBoolean)            _Expanded =Value       End Set   End Property#End Region#Region " Public Events "    Public EventOnExpandedChanged(ByVal sender _   As System.Object, ByVale As System.EventArgs)    Protected Overrides SubOnInit(ByVal e As _   System.EventArgs)      If Page.IsPostBackThen       'Determine if theuser expanded or contracted       'the VisiPanel andfire an       'OnExpandedChangedevent if they did       Dim vis As String =Page.Request(Me.ClientID & _           "_hidden").ToString().ToLower       If (Not vis IsNothing) Then         If vis ="none" AndAlso _Expanded <> False Then           _Expanded =False           RaiseEventOnExpandedChanged(Me, Nothing)          End If         If vis ="block" AndAlso _Expanded <> True Then           _Expanded = True           RaiseEventOnExpandedChanged(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 = NewUnit(150, UnitType.Pixel)    MyBase.Height = NewUnit(75, UnitType.Pixel)    MyBase.BorderColor =Color.Black End Sub Protected Overrides SubRender(ByVal w As _ System.Web.UI.HtmlTextWriter)     'build the javascript show/hide code   Dim sb As NewSystem.Text.StringBuilder   sb.Append("varobj= document.getElementById('")    sb.Append(Me.ClientID +"');")    sb.Append("varobjDown= document.getElementById('")    sb.Append(Me.ClientID +"_ButtonDown');")    sb.Append("varobjUp= 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 fieldto 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 VisiPanelheader 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) '    w.Write(Me.HeaderText)    w.RenderEndTag() '    'output the VisiPanelheader 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 Thenw.AddStyleAttribute("display", _       "none")Else w.AddStyleAttribute("display", "block")    w.RenderBeginTag(HtmlTextWriterTag.Td) '    w.Write(Chr(54)) 'downarrow   w.RenderEndTag() '    'output the VisiPanelheader 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 Thenw.AddStyleAttribute("display", _       "block")Else w.AddStyleAttribute("display", "none")    w.RenderBeginTag(HtmlTextWriterTag.Td) '    w.Write(Chr(53)) 'uparrow   w.RenderEndTag() '    'close the table tags   w.RenderEndTag() '    w.RenderEndTag() '    'specify the visibilityof 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 colorgradient 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 SubEnd ClassEnd Listing One      

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like