November 07, 2005 12:11 AM

VisiPanel

Tour the Source Code of a Free, Colorful, Expanding Panel Web Control
DevConnections
Rating: (0)

ControlFreak

LANGUAGES:VB.NET | C#

ASP.NETVERSIONS: 1.x | 2.x

 

VisiPanel

Tour the Source Code of a Free, Colorful, ExpandingPanel Web Control

 

 

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

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: 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:

 

<cc1:VisiPanel id="VisiPanel1"

 runat="server"

 BackColor="Beige"

 GradientEndColor="Tan"

 HeaderText="MyHeader Text">

 Hello World

</cc1:VisiPanel>

 

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 Eye Candy article, then you?re alreadyfamiliar with the DirectX filter technique VisiPanel is using for its colorfulgradient 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 (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-celled HTML table is on top, followed by the inherited Panel control onbottom. The configurable header text is displayed in the first table cell,followed by two Webdings font characters in the final two cells to representarrows. 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 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 <div> 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 runtime and 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 workbecause 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 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 costassociated with instantiating all those objects. For smaller Web sites withless traffic, this likely isn?t a big deal. However, if you?re developingcontrols for a highly scalable Web site, you should be aware that Renderingoutperforms Composition by a significant amount. That?s why I chose Renderinginstead of Composition. Rendering is done by overriding the Render event of thebase server 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("<input type=hidden id=whatever>")

 

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.The mouse 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 for the VisiPanel control is available fordownload.

 

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 athttp://SteveOrr.net or e-mail him at mailto:Steve@Orr.net.

 

Begin Listing One

Imports System.ComponentModel

Imports System.Web.UI

Imports System.Web.UI.WebControls

Imports System.Drawing

 

<DefaultProperty("Text"), _

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

DefaultEvent("OnExpandedChanged")> _

Public Class VisiPanel

   InheritsSystem.Web.UI.WebControls.Panel

 

#Region " Public Properties "

    Private _headerText As String = Me.ID

   <Bindable(True),Category("Appearance")> _

   Property [HeaderText]()As String

       Get

           Return_headerText

       End Get

 

       Set(ByVal Value AsString)

           _headerText =Value

       End Set

   End Property

 

   Private_GradientEndColor As Drawing.Color

   <Bindable(True),Category("Appearance")> _

   Public PropertyGradientEndColor() As Color

       Get

           Return_GradientEndColor

       End Get

       Set(ByVal Value AsColor)

           _GradientEndColor = Value

       End Set

   End Property

 

   Private _Expanded AsBoolean = True

   <Bindable(True),Category("Appearance"), _

   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

           RaiseEvent OnExpandedChanged(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 javascriptshow/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) '<table>

   w.RenderBeginTag(HtmlTextWriterTag.Tr) '<tr>

   w.RenderBeginTag(HtmlTextWriterTag.Td) '<td>

   w.Write(Me.HeaderText)

   w.RenderEndTag() '</td>

 

   '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) '<td>

   w.Write(Chr(54)) 'downarrow

   w.RenderEndTag() '</td>

 

   '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) '<td>

   w.Write(Chr(53)) 'uparrow

   w.RenderEndTag() '</td>

 

   'close the table tags

   w.RenderEndTag() '</tr>

   w.RenderEndTag() '</table>

 

   '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 Sub

End Class

End Listing One

 

 

 

 

Add a Comment

There are no comments to display. Be the first one!
You must log on before posting a comment.

Are you a new visitor? Register Here

advertisement




Comments from the DevConnections Community

Join our community of development pros.

Windows problem

I all, I have a problem on my Windows Vista that began afetr the purchase of an external Hard Disk Freecom. A few days afetr the purchase I discon...

Most Recent Posts

GOOGLE LINKS
SPONSORED LINKS
FEATURED LINKS