![]() |
|
|
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
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.
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.
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:
<cc1:VisiPanel id="VisiPanel1" runat="server" BackColor="Beige" GradientEndColor="Tan" HeaderText="My Header Text"> Hello World </cc1:VisiPanel>
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.
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 <div> 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("<input type=hidden id=whatever>")
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
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 Inherits System.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 As String) _headerText = Value End Set End Property
Private _GradientEndColor As Drawing.Color <Bindable(True), Category("Appearance")> _ 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 <Bindable(True), Category("Appearance"), _ 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) '<table> w.RenderBeginTag(HtmlTextWriterTag.Tr) '<tr> w.RenderBeginTag(HtmlTextWriterTag.Td) '<td> w.Write(Me.HeaderText) w.RenderEndTag() '</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) '<td> w.Write(Chr(54)) 'down arrow w.RenderEndTag() '</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) '<td> w.Write(Chr(53)) 'up arrow w.RenderEndTag() '</td>
'close the table tags w.RenderEndTag() '</tr> w.RenderEndTag() '</table>
'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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||