Creating Custom Web Part Zones


The WebPartZone control is responsible for rendering all the Web Parts contained in a zone. Therefore, if you want to modify the appearance of a Web Part Zone, or modify the appearance of the Web Parts that appear in a Web Part Zone, then you need to create a custom Web Part Zone.

In this section, we create three custom Web Part Zones: a Photo Web Part Zone, a Multi-Column Web Part Zone, and a Menu Web Part Zone.

How Web Part Zones Work

Three types of parts included in the Web Part Framework are related to Web Part Zones: the WebPartZone, WebPartChrome, and WebPart controls.

The WebPartZone control is derived from the WebPartZoneBase class. This class includes one important MustOverride (abstract) method named GetInitialWebParts(). The GetInitialWebParts() method returns the Web Parts that the Web Part Zone initially displays before a user personalizes the zone.

For example, the standard WebPartZone control overrides the GetInitialWebParts() method and returns the collection of Web Parts contained in its ZoneTemplate. In other words, the WebPartZone control overrides the WebPartZoneBase control to support declaratively listing a set of initial Web Parts in a ZoneTemplate.

You can override the GetInitialWebParts() method and return any set of Web Parts that you please. For example, you could get the initial list of Web Parts from a database table, a Web service, or generate a random collection of Web Parts.

Before a Web Part Zone renders its Web Parts, the Web Part Zone creates an instance of the WebPartChrome class. The Web Part Chrome contains the standard elements which appear around each Web Part in a Web Part Zone. The default chrome includes the Web Part title bar and menu.

If you want to modify the Web Part Chrome, then you need to derive a new class from the base WebPartChrome class and associate your new chrome with a Web Part Zone. You can associate a custom WebPartChrome class with a Web Part Zone by overriding the WebPartZoneBase class's CreateWebPartChrome() method.

After the Web Part Zone gets an instance of the WebPartChrome class from its CreateWebPartChrome() method, the Web Part Zone uses the class to render each of its Web Parts. The WebPartChrome class includes a method named RenderWebPart(), which renders a particular Web Part that uses the chrome.

When all is said and done, you must interact with three classes to modify the appearance of a Web Part Zone. The WebPartZone control uses the WebPartChrome class to render individual WebPart controls.

Creating a Photo Web Part Zone

In this section, we create a custom Web Part Zone, which automatically displays a list of photos from a folder. Each photo is converted automatically into a Web Part so that you can re-arrange the photos on a page (see Figure 30.1).

Figure 30.1. The Photo Web Part Zone.


To create a Photo Web Part Zone, you need to override the GetInitialWebParts() method. By default, this method retrieves a list of initial Web Parts from the ZoneTemplate contained in a Web Part Zone. In the modified version of this method, the list of Web Parts is obtained from a Photo folder.

The PhotoWebPartZone control is contained in Listing 30.1.

Listing 30.1. PhotoWebPartZone.vb

Imports System Imports System.IO Imports System.Collections.Generic Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Namespace myControls     Public Class PhotoWebPartZone         Inherits WebPartZoneBase         Dim _photoFolderUrl As String = "~/Photos"         ''' <summary>         ''' Represents the URL for the folder that         ''' contains the photos.         ''' </summary>         Public Property PhotoFolderUrl() As String             Get                 Return _photoFolderUrl             End Get             Set(ByVal Value As String)                 _photoFolderUrl = Value             End Set         End Property         ''' <summary>         ''' Get the initial Web Parts from the Photo Folder         ''' </summary>         Protected Overrides Function GetInitialWebParts() As WebPartCollection             ' Don't do anything when displayed in Designer             If (Me.DesignMode) Then                 Return New WebPartCollection()             End If             ' Get the WebPartManager control             Dim wpm As WebPartManager = WebPartManager.GetCurrentWebPartManager(Page)             ' Create a WebPart collection             Dim photos As New List(Of WebPart)()             ' Get the list of photos             Dim photoDir As DirectoryInfo = New DirectoryInfo(Page.MapPath(_photoFolderUrl))             Dim files() As FileInfo = photoDir.GetFiles()             Dim file As FileInfo             For Each file In files                 Dim photo As Image = New Image()                 photo.ID = Path.GetFileNameWithoutExtension(file.Name)                 photo.ImageUrl = Path.Combine(_photoFolderUrl, file.Name)                 photo.Width = Unit.Pixel(200)                 photo.Attributes.Add("Title", file.Name)                 photo.AlternateText = file.Name + " Photo"                 photos.Add(wpm.CreateWebPart(photo))             Next             ' Return WebPartCollection             Return New WebPartCollection(photos)         End Function     End Class End Namespace 

The bulk of the code in Listing 30.1 is contained in the GetInitialWebParts() method. This method grabs a list of all the files located in the Photo folder. Next, the method creates a new ASP.NET Image control for each of the photos. Each Image control is wrapped in a Generic Web Part with the help of the WebPartManager control's CreateWebPart() method. Finally, the collection of Web Parts is returned.

The page in Listing 30.2 includes the PhotoWebPartZone control.

Listing 30.2. PhotoGallery.aspx

<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server">     Sub Page_Load()         WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Photo Gallery</title> </head> <body>     <form  runat="server">     <div>     <asp:WebPartManager                  Runat="server" />     <custom:PhotoWebPartZone                  PhotoFolderUrl="~/Photos"         LayoutOrientation="Horizontal"         runat="server" />     <asp:WebPartZone                  LayoutOrientation="Horizontal"         runat="server" />     </div>     </form> </body> </html> 

The page in Listing 30.2 contains two Web Part Zones. The first Web Part Zone is the custom Photo Web Part Zone. The second zone is a standard Web Part Zone.

When you first open the page, all the photos contained in the Photos folder are displayed in the Photo Web Part Zone. Because the page is set to be in Design Display Mode by default in the Page_Load() method, you can rearrange the photos immediately after opening the page.

The GetInitialWebParts() method returns the list of Web Parts that a zone displays before the page has been personalized. You can re-arrange the photos in any way that you please and the Web Part Framework automatically saves your personalization data.

Notice that you can move photos between the custom Photo Web Part Zone and the standard Web Part Zone. If you close the page and return in the future, all the photos will retain their positions.

Creating a Multi-Column Web Part Zone

By default, a Web Part Zone displays the Web Parts that it contains in only one of two orientations: Horizontal or Vertical. You can select a particular orientation with the WebPartZone control's LayoutOrientation property.

However, there are situations in which you might want to display Web Parts with a more complicated layout. For example, in the previous section, we created a Photo Web Part Zone. It would be nice if we could display the photos in a certain number of repeating columns.

In this section, we build a Multi-Column Web Part Zone. This Web Part Zone includes a RepeatColumns property. When you declare the Web Part Zone, you can use this property to set the number of columns of Web Parts to display (see Figure 30.2).

Figure 30.2. Displaying Web Parts in multiple columns.


To create this custom Web Part Zone, you need to override the default rendering behavior of the WebPartZone class. The WebPartZone class includes several methods related to rendering its content including:

  • RenderContents() Renders the entire contents of the Web Part Zone.

  • RenderHeader() Renders the header of the Web Part Zone.

  • RenderBody() Renders the body of the Web Part Zone.

  • RenderDropCue() Renders the drop cues that appear when you move Web Parts.

  • RenderFooter() Renders the footer of the Web Part Zone.

Web Standards Note

In an ideal world, Web Part Zones and Web Parts would not use HTML tables for layout. We would use Cascading Style Sheets for layout and use HTML tables only for their intended purpose: displaying tabular information. Unfortunately, we do not live in an ideal world. Microsoft is committed to supporting older browsers and older browsers do not provide good support for Cascading Style Sheets. So we're stuck with HTML tables.


To create a Multi-Column Web Part Zone, you need to override the RenderBody() method. The code for the MultiColumnWebPartZone control is contained in Listing 30.3.

Listing 30.3. MultiColumnWebPartZone.vb

Imports System Imports System.Web.UI Imports System.Web.UI.WebControls.WebParts Namespace myControls     ''' <summary>     ''' Displays Web Parts in multiple columns     ''' </summary>     Public Class MultiColumnWebPartZone         Inherits WebPartZone         Private _repeatColumns As Integer = 2         ''' <summary>         ''' The number of columns to display         ''' </summary>         Public Property RepeatColumns() As Integer             Get                 Return _repeatColumns             End Get             Set(ByVal Value As Integer)                 _repeatColumns = Value             End Set         End Property         ''' <summary>         ''' Overrides default Web Part Zone rendering         ''' in Browse Display Mode         ''' </summary>         Protected Overrides Sub RenderBody(ByVal writer As HtmlTextWriter)             If Me.DesignMode Then                 MyBase.RenderBody(writer)             ElseIf Me.WebPartManager.DisplayMode Is WebPartManager.BrowseDisplayMode Then                 RenderMultiColumnBody(writer)             Else                 MyBase.RenderBody(writer)             End If         End Sub         ''' <summary>         ''' Renders Web Parts in multiple columns by iterating         ''' through the Web Parts collection         ''' </summary>         Private Sub RenderMultiColumnBody(ByVal writer As HtmlTextWriter)             ' Create the Web Part Chrome             Dim chrome As WebPartChrome = Me.CreateWebPartChrome()             ' Create the opening Table Tag             writer.AddAttribute("border", "1")             writer.RenderBeginTag(HtmlTextWriterTag.Table)             writer.RenderBeginTag(HtmlTextWriterTag.Tr)             ' Render each Web Part             Dim counter As Integer = 1             Dim part As WebPart             For Each part In Me.WebParts                 writer.RenderBeginTag(HtmlTextWriterTag.Td)                 chrome.RenderWebPart(writer, part)                 writer.RenderEndTag()                 ' Add a Tr when counter = RepeatColumns                 If counter = _repeatColumns Then                     writer.RenderEndTag() ' Close Tr                     writer.RenderBeginTag(HtmlTextWriterTag.Tr)                     counter = 0                 End If                 counter = counter + 1             Next             ' Close Table Tag             writer.RenderEndTag()             writer.RenderEndTag()         End Sub     End Class End Namespace 

The class contained in Listing 30.3 inherits from the base WebPartZone class. The MultiColumnWebPartZone control overrides the base class's RenderBody() method to render the zone's Web Parts in a multi-column table.

Notice that a multi-column table is rendered only when the page is in Browse Display mode. When the page is in any other display mode or the control is displayed in a designer, the base RenderBody() method is called and the Web Parts are rendered the normal way.

Note

It would be nice if the Web Parts could be rendered in a multi-column table in Design Mode as well as in Browse Mode. Unfortunately, the JavaScript that renders the drop cues for moving Web Parts assumes that a Web Part Zone is rendered either vertically or horizontally. To fix this problem, you would have to rewrite the JavaScript library used by the Web Part Framework.


The RenderMultiColumnBody() method performs all the work of rendering the multi-column table. First, the method grabs the Web Part Chrome that is used when rendering each Web Part. Each of the Web Parts contained in the zone are rendered by calling the RenderWebPart() method of the WebPartChrome class (otherwise, the Web Parts would appear without their title bars and menus).

The actual list of Web Parts that the zone renders is retrieved from the base WebPartZone control's WebParts property. Be aware that this property, unlike the GetInitialWebParts() method, returns the list of Web Parts that the Web Part Zone displays after personalization.

You can experiment with the MultiPartWebPartZone control with the page in Listing 30.4.

Listing 30.4. ShowMultiColumnWebPartZone.aspx

<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" Assembly="__code" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server">     Protected Sub Menu1_MenuItemClick(ByVal sender As Object, ByVal e As MenuEventArgs)         WebPartManager1.DisplayMode = WebPartManager1.DisplayModes(e.Item.Text)     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show MultiColumn Web Part Zone</title> </head> <body>     <form  runat="server">     <asp:WebPartManager                  Runat="server" />     <asp:Menu                  OnMenuItemClick="Menu1_MenuItemClick"         Orientation="Horizontal"         Css         Runat="server">         <Items>         <asp:MenuItem Text="Browse" />         <asp:MenuItem Text="Design" />         </Items>     </asp:Menu>     <custom:MultiColumnWebPartZone                  RepeatColumns="2"         runat="server">         <ZoneTemplate>         <asp:Label                          Text="hello 1"             runat="server" />         <asp:Label                          Text="hello 2"             runat="server" />         <asp:Label                          Text="hello 3"             runat="server" />         <asp:Label                          Text="hello 4"             runat="server" />         <asp:Label                          Text="hello 5"             runat="server" />         </ZoneTemplate>     </custom:MultiColumnWebPartZone>     </form> </body> </html> 

The page in Listing 30.4 uses the MultiColumnWebPartZone control to render its one and only Web Part Zone. The MultiColumnWebPartZone control's RepeatColumns property is set to display a two-column table. When you open the page in your browser, you will see the page in Figure 30.2.

Creating a Menu Web Part Zone

By default, Web Part controls display very simple menus. You can use the WebPartVerbRenderMode property of the WebPartZone class to display one of two types of menus: a menu that appears in a single drop-down list or a menu that appears as a list of static links in the title bar.

In this section, we create fancier menus for Web Parts. We add the necessary functionality to the Web Part Framework to make it possible to create mulltiple drop-down menus for a single Web Part. We also add support for creating dividers between menu items (see Figure 30.3).

Figure 30.3. Displaying fancy Web Part menus.


To create fancy menus, three of the standard classes in the Web Part Framework must be modified:

  • WebPartVerb This class represents a menu item. It needs to be extended to support nested menus and menu dividers.

  • WebPartChrome This class represents the chrome that is rendered around a Web Part. To create fancy menus, you have to completely override the default rendering behavior of this class.

  • WebPartZone This class represents the Web Part Zone which hosts a set of Web Parts. You need to modify this class so you can display a custom Web Part Chrome.

Let's start by modifying the WebPartVerb class. The modified version of this class, named MenuWebPartVerb, is contained in Listing 30.5.

Listing 30.5. MenuWebPartVerb.vb

[View full width]

Imports System Imports System.Web.UI.WebControls.WebParts ''' <summary> ''' Extends the base WebPartVerb class ''' with support for nested menus and menu ''' dividers. ''' </summary> Public Class MenuWebPartVerb     Inherits WebPartVerb     Private _parentVerbId As String = String.Empty     Private _hasDivider As Boolean = False     ''' <summary>     ''' Enables you to nest one menu beneath another     ''' </summary>     Public Property parentVerbId() As String         Get             Return _parentVerbId         End Get         Set(ByVal Value As String)             _parentVerbId = Value         End Set     End Property     ''' <summary>     ''' This property enables you to render a divider     ''' above the menu item.     ''' </summary>     Public Property hasDivider() As Boolean         Get             Return _hasDivider         End Get         Set(ByVal Value As Boolean)             _hasDivider = Value         End Set     End Property     ''' <summary>     ''' We need to call the base class constructors     ''' in our derived class     ''' </summary>     Public Sub New(ByVal id As String, ByVal clientClickHandler As String)         MyBase.New(id, clientClickHandler)     End Sub     Public Sub New(ByVal id As String, ByVal serverClickHandler As WebPartEventHandler)         MyBase.New(id, serverClickHandler)     End Sub     Public Sub New(ByVal id As String, ByVal serverClickHandler As WebPartEventHandler,  ByVal clientClickHandler As String)         MyBase.New(id, serverClickHandler, clientClickHandler)     End Sub End Class 

In Listing 30.5, the base WebPartVerb class is extended with two new properties: ParentId and HasDivider. The parentId property enables you to nest menu items. For example, all the menu items that appear beneath the File menu will have the ID of the File menu as their ParentId.

The HasDivider property enables you to display a menu divider above a menu item. When you set this property to the value true, an HTML <hr> tag is rendered above the current menu item.

The bulk of the code for the fancy menus is contained in Listing 30.6, which contains the custom Web Part Chrome that renders the fancy menus.

Listing 30.6. MenuWebPartChrome.vb

[View full width]

Imports System Imports System.Collections.Generic Imports System.Web.UI Imports System.Web.UI.WebControls.WebParts Namespace myControls     ''' <summary>     ''' WebPartChrome, which includes multiple drop-down     ''' menus and menu dividers     ''' </summary>     Public Class MenuWebPartChrome         Inherits WebPartChrome         ''' <summary>         ''' Required Constructor         ''' </summary>         Public Sub New(ByVal zone As WebPartZone, ByVal manager As WebPartManager)             MyBase.New(zone, manager)         End Sub         ''' <summary>         ''' The main method for rendering a Web Part.         ''' Here, we take over responsibility for rendering         ''' the title and menu         ''' </summary>         Public Overrides Sub RenderWebPart(ByVal writer As HtmlTextWriter, ByVal webPart  As WebPart)             ' Render an enclosing Div             writer.AddAttribute(HtmlTextWriterAttribute.Id, Me.GetWebPartChromeClientID (webPart))             writer.AddAttribute(HtmlTextWriterAttribute.Class, "menuWebPartZone_chrome")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             ' Render the title bar             RenderTitleBar(writer, webPart)             ' Render the Web Part             MyBase.RenderPartContents(writer, webPart)             ' Close the enclosing Div             writer.RenderEndTag() ' Close main DIV         End Sub         ''' <summary>         ''' Renders the title bar area of the chrome.         ''' This is the part that a user can drag         ''' </summary>         Private Sub RenderTitleBar(ByVal writer As HtmlTextWriter, ByVal webPart As WebPart)             ' Render the menu             RenderMenu(writer, webPart)             ' Create a break             writer.AddStyleAttribute("clear", "all")             writer.RenderBeginTag(HtmlTextWriterTag.Br)             ' Render the title bar             writer.AddAttribute(HtmlTextWriterAttribute.Id, Me.GetWebPartTitleClientID (webPart))             writer.AddAttribute(HtmlTextWriterAttribute.Class, "menuWebPartZone_chromeTitle")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             writer.Write(webPart.DisplayTitle)             writer.RenderEndTag() ' Close title DIV         End Sub         ''' <summary>         ''' Renders the menus (possibly nested)         ''' </summary>         Private Sub RenderMenu(ByVal writer As HtmlTextWriter, ByVal webPart As WebPart)             writer.AddStyleAttribute("display", "inline")             writer.AddAttribute(HtmlTextWriterAttribute.Class, "menuWebPartZone_menu")             writer.RenderBeginTag(HtmlTextWriterTag.Ul)             ' Get the top-level menu items that are not hidden             Dim topLevelVerbs As WebPartVerbCollection = GetChildVerbs(webPart.Verbs,  String.Empty)             For Each verb As MenuWebPartVerb In topLevelVerbs                 writer.AddStyleAttribute("float", "left")                 writer.AddAttribute("onmouseover", "menuWebPartZone.showMenu(this)")                 writer.RenderBeginTag(HtmlTextWriterTag.Li)                 RenderMenuRecurse(writer, verb, webPart)                 writer.RenderEndTag()             Next             writer.RenderEndTag() ' Close Ul         End Sub         ''' <summary>         ''' The main method for rendering the subMenus.         ''' This method is called recursively so you         ''' can show infinitely nested menus.         ''' </summary>         Private Sub RenderMenuRecurse(ByVal writer As HtmlTextWriter, ByVal verb As  MenuWebPartVerb, ByVal webPart As WebPart)             Dim childVerbs As WebPartVerbCollection = GetChildVerbs(WebPart.Verbs, verb.ID)             If (childVerbs.Count > 0) Then                 ' Renders a menu item that is not a link                 RenderHeaderMenuItem(writer, verb)                 writer.AddAttribute(HtmlTextWriterAttribute.Class,  "menuWebPartZone_popupMenu")                 writer.AddStyleAttribute("position", "absolute")                 writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none")                 writer.RenderBeginTag(HtmlTextWriterTag.Ul)                 For Each childVerb As MenuWebPartVerb In childVerbs                     writer.AddAttribute("onmouseover", "menuWebPartZone.showMenu(this)")                     writer.RenderBeginTag(HtmlTextWriterTag.Li)                     RenderMenuRecurse(writer, childVerb, webPart)                     writer.RenderEndTag()                 Next                 writer.RenderEndTag() ' Close UL             Else                 ' Renders a link menu item                 RenderLinkMenuItem(writer, verb, webPart)             End If         End Sub         ''' <summary>         ''' Renders a menu item that is not a link.         ''' When a user clicks this menu item, it         ''' expands sub-menu items.         ''' </summary>         Private Sub RenderHeaderMenuItem(ByVal writer As HtmlTextWriter, ByVal verb As  MenuWebPartVerb)             ' Render divider             RenderMenuDivider(writer, verb)             ' Render encloding Div             writer.AddAttribute(HtmlTextWriterAttribute.Class, "menuWebPartZone_menuItem")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             ' Render verb icon             RenderMenuIcon(writer, verb)             ' Render the verb text             writer.Write(verb.Text)             writer.RenderEndTag() ' Close Div         End Sub         ''' <summary>         ''' Renders a menu item that causes a postback.         ''' </summary>         Private Sub RenderLinkMenuItem(ByVal writer As HtmlTextWriter, ByVal verb As  MenuWebPartVerb, ByVal webPart As WebPart)             ' Render divider             RenderMenuDivider(writer, verb)             ' Render Enclosing Div             writer.AddAttribute(HtmlTextWriterAttribute.Class, "menuWebPartZone_menuItem")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             ' Render verb icon             RenderMenuIcon(writer, verb)             ' Render the verb link             Dim eventArg As String = String.Format("partverb:{0}:{1}", verb.ID, WebPart.ID)             Dim eventRef As String = Me.Zone.Page.ClientScript.GetPostBackClientHyperlink (Me.Zone, eventArg)             writer.AddAttribute(HtmlTextWriterAttribute.Href, eventRef)             writer.RenderBeginTag(HtmlTextWriterTag.A)             writer.Write(verb.Text)             writer.RenderEndTag()             writer.RenderEndTag() ' Close Div         End Sub         ''' <summary>         ''' If a menu item has an icon then show it.         ''' </summary>         ''' <remarks>         ''' Notice that we set empty ALT text for         ''' accessibility reasons.         ''' </remarks>         Private Sub RenderMenuIcon(ByVal writer As HtmlTextWriter, ByVal verb As WebPartVerb)             If verb.ImageUrl <> String.Empty Then                 writer.AddAttribute(HtmlTextWriterAttribute.Src, Me.Zone.Page.ResolveUrl (verb.ImageUrl))                 writer.AddAttribute(HtmlTextWriterAttribute.Alt, String.Empty)                 writer.AddAttribute(HtmlTextWriterAttribute.Align, "middle")                 writer.RenderBeginTag(HtmlTextWriterTag.Img)                 writer.RenderEndTag()             End If         End Sub         ''' <summary>         ''' If a menu should display a divider above it,         ''' then show it with an hr tag.         ''' </summary>         Private Sub RenderMenuDivider(ByVal writer As HtmlTextWriter, ByVal verb As  MenuWebPartVerb)             If verb.hasDivider Then                 writer.RenderBeginTag(HtmlTextWriterTag.Hr)                 writer.RenderEndTag()             End If         End Sub         ''' <summary>         ''' Returns all the verbs that have a certain         ''' parent verb         ''' </summary>         Private Function GetChildVerbs(ByVal verbs As WebPartVerbCollection, ByVal  parentId As String) As WebPartVerbCollection             Dim children As New List(Of WebPartVerb)()             For Each verb As MenuWebPartVerb In verbs                 If verb.parentVerbId = parentId Then                     children.Add(verb)                 End If             Next             Return New WebPartVerbCollection(children)         End Function     End Class End Namespace 

The WebPartChrome class is responsible for rendering the outer chrome displayed around each Web Part in a Web Part Zone. This outer chrome includes the title bar and menu rendered for each Web Part.

In Listing 30.6, we override the RenderWebPart() method. When you override this method, you must take complete responsibility for rendering the entire contents of the Web Part Chrome.

The RenderWebPart() method does two things. First, it renders the chrome's title bar. The title bar contains the area that a user selects when dragging a Web Part from one zone to another when a page is in Design Display mode. The custom Web Part Chrome gets this functionality for free because the base WebPartChrome class's GetWebPartTitleClientID() is used to render the right ID for the title bar. The JavaScript library used by the Web Part Framework automatically detects this ID and enables drag-and-drop support.

Web Standards Note

The default WebPartChrome class renders an HTML table to create the chrome around a Web Part. In the custom MenuWebPartChrome class, we use a <div> tag instead. This choice makes sense from a standards perspective and requires less code. Unfortunately, using a div element instead of a table element breaks all the default formatting properties included with the WebPartZone control. If you want to format the custom MenuWebPartChrome class, you need to use Cascading Style Sheets.


Next, in the RenderWebPart() method, the Web Part contained within the Web Part Chrome is rendered. The Web Part is rendered with the help of the base WebPartChrome class's RenderPartContents() method.

The bulk of the code in Listing 30.6 is devoted to rendering the fancy menus. The RenderTitleBar() method calls the RenderMenu() method to build the custom menu.

The fancy menus are created with a set of nested unordered lists (HTML <ul> tags). The second-level menus are hidden by default with the display:none Cascading Style Sheet rule. A client-side onmouseover event handler is added to each list item so that a submenu is displayed when you hover your mouse over a list item.

The links rendered in the menu require additional explanation. The menu links are rendered by the RenderLinkMenuItem() method. When you click a menu item link, the server-side method that corresponds to the menu item is executed by the Web Part.

The GetPostBackClientHyperlink() method is called to retrieve the necessary JavaScript for invoking the server-side menu event. This method returns a string that contains JavaScript code for posting an argument back to the server. The argument must be in a special format to invoke the correct server-side menu click handler in the Web Part.

The argument passed back to the server must have three parts, separated by colons:

  • partverb This string indicates that a Web Part verb has been clicked.

  • Verb ID The ID of the Web Part verb that a user clicked.

  • Web Part ID The ID of the Web Part that contains the server-side method to execute.

By following this special format, you can leverage the existing Web Part Framework support for firing off the correct server-side method when you click a particular menu item.

To use a custom Web Part Chrome, you need to create a custom Web Part Zone. The MenuWebPartZone is contained in Listing 30.7.

Listing 30.7. MenuWebPartZone.vb

[View full width]

Imports System Imports System.Web.UI.WebControls.WebParts Namespace myControls     ''' <summary>     ''' Web Part Zone that displays fancy nested menus     ''' </summary>     Public Class MenuWebPartZone         Inherits WebPartZone         ''' <summary>         ''' Register the client-script for the menus.         ''' </summary>         Protected Overrides Sub OnPreRender(ByVal e As EventArgs)             If Not Page.ClientScript.IsClientScriptIncludeRegistered("MenuWebPartZone") Then                 Page.ClientScript.RegisterClientScriptInclude("MenuWebPartZone", Page .ResolveUrl("~/ClientScripts/MenuWebPartZone.js"))             End If             MyBase.OnPreRender(e)         End Sub         ''' <summary>         ''' Create special Web Part chrome that contains the menus         ''' </summary>         Protected Overrides Function CreateWebPartChrome() As WebPartChrome             Return New MenuWebPartChrome(Me, Me.WebPartManager)         End Function     End Class End Namespace 

The custom Menu Web Part Zone overrides two methods of the base WebPartZone class.

First, it overrides the CreateWebPartChrome() method to substitute the custom Web Part Chrome. In this method, what's returned is simply an instance of the MenuWebPartChrome class.

Second, the OnPreRender() method is overridden so that you can include a link to a JavaScript library, named MenuWebPartZone.js, which contains the client-side code for the fancy menus. The MenuWebPartZone.js library is contained in Listing 30.8.

Listing 30.8. MenuWebPartZone.js

var menuWebPartZone = new function()     {         this.showMenu = menuWebPartZone_showMenu;     } function menuWebPartZone_showMenu(el) {     // Get ul elements     var subMenus = el.getElementsByTagName('UL');     // If there are ul elements, show the first one     if (subMenus.length > 0)     {         subMenus[0].style.display = '';         // Set up function to hide ul element again         el.onmouseout = function(e)             {                 subMenus[0].style.display = 'none';             }     } } 

When you hover your mouse over a menu item, the menuWebPartZone_showMenu() method is called. This JavaScript method finds the first HTML <ul> tag contained under the current list item and displays it. Next, the method adds a onmouseout handler to hide the submenu when the user moves the mouse away from the menu item.

Web Standards Note

The JavaScript menu library works well in the case of Internet Explorer 6 and Opera 8. Unfortunately, it doesn't work so well with Firefox 1. Firefox does not reliably fire the onmouseout handler, so open menus tend to get stuck.


We are finally in a position to try out the fancy menus. First, a new Web Part needs to be created that takes advantage of them. The TextEditorPart in Listing 30.9 displays both a File and Edit menu.

Listing 30.9. TextEditorPart.ascx

<%@ Control Language="VB" ClassName="TextEditorPart" %> <%@ Implements Interface="System.Web.UI.WebControls.WebParts.IWebActionable" %> <%@ Import Namespace="System.Collections.Generic" %> <script runat="server">     ''' <summary>     ''' Create the menu     ''' </summary>     Public ReadOnly Property Verbs() As WebPartVerbCollection Implements IWebActionable.Verbs         Get             Dim myVerbs As New List(Of WebPartVerb)()             ' Create File menu             Dim fileVerb As New MenuWebPartVerb("file", AddressOf doMenuAction)             fileVerb.Text = "File"             myVerbs.Add(fileVerb)             Dim NewVerb As New MenuWebPartVerb("new", AddressOf doMenuAction)             NewVerb.Text = "New"             NewVerb.parentVerbId = fileVerb.ID             myVerbs.Add(NewVerb)             ' Create Edit menu             Dim editVerb As New MenuWebPartVerb("edit", AddressOf doMenuAction)             editVerb.Text = "Edit"             myVerbs.Add(editVerb)             Dim copyVerb As New MenuWebPartVerb("copy", AddressOf doMenuAction)             copyVerb.Text = "Copy"             copyVerb.parentVerbId = editVerb.ID             myVerbs.Add(copyVerb)             Dim pasteVerb As New MenuWebPartVerb("pasted", AddressOf doMenuAction)             pasteVerb.Text = "Paste"             pasteVerb.parentVerbId = editVerb.ID             myVerbs.Add(pasteVerb)             Dim boldVerb As New MenuWebPartVerb("bold", AddressOf doMenuAction)             boldVerb.Text = "Bold"             boldVerb.ImageUrl = "~/Icons/Bold.gif"             boldVerb.hasDivider = True             boldVerb.parentVerbId = editVerb.ID             myVerbs.Add(boldVerb)             Dim italicVerb As New MenuWebPartVerb("italic", AddressOf doMenuAction)             italicVerb.Text = "Italic"             italicVerb.ImageUrl = "~/Icons/Italic.gif"             italicVerb.parentVerbId = editVerb.ID             myVerbs.Add(italicVerb)             ' Return the menu             Return New WebPartVerbCollection(myVerbs)         End Get     End Property     ''' <summary>     ''' The server-side method that is invoked when you     ''' click a menu item     ''' </summary>     Public Sub doMenuAction(ByVal s As Object, ByVal e As WebPartEventArgs)         Dim verb As MenuWebPartVerb = CType(s, MenuWebPartVerb)         lblAction.Text = String.Format("{0} clicked!", verb.Text)     End Sub </script> <div style="padding:10px"> Select a menu item from the menu above the title bar. <br /> <asp:Label          EnableViewState="false"     Runat="server" />  </div> 

The menu is created by the TextEditorPart control's Verbs property. This property is a member of the IWebActionable interface (notice that the user control implements this interface with the directive at the top of the file).

All the menu items are wired to the same server-side method. If you click any of the menu items, the doMenuAction() method executes and reports the ID of the menu item clicked. You could, of course, wire each menu item to a different server-side method.

You can use the TextEditorPart in the page in Listing 30.10.

Listing 30.10. ShowMenuWebPartZone.aspx

<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" Assembly="__code" %> <%@ Register TagPrefix="user" TagName="TextEditorPart"   src="/books/3/444/1/html/2/~/TextEditorPart.ascx" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server">     Protected Sub Menu1_MenuItemClick(ByVal sender As Object, ByVal e As MenuEventArgs)         WebPartManager1.DisplayMode = WebPartManager1.DisplayModes(e.Item.Text)     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show Menu Web Part Zone</title>     <style type="text/css">         .menuWebPartZone_chrome         {             border:solid 2px black;             width:300px;             height:200px;         }         .menuWebPartZone_chromeTitle         {             background-color:silver;             padding:3px;             font:bold 16px Arial,sans-serif;         }         .menuWebPartZone_menuItem         {             cursor:hand;         }         .menuWebPartZone_menu         {             font:12px Arial, sans-serif;         }         .menuWebPartZone_menu li         {             margin-left:4px;             margin-right:10px;             margin-top:3px;             list-style-type:none;         }         .menuWebPartZone_menu ul         {             padding:0px;             margin:0px;             background-color:#eeeeee;             border:solid 1px black;             width:100px;         }         .menuWebPartZone_menu a         {             color:blue;             text-decoration:none;         }     </style> </head> <body>     <form  runat="server">     <asp:WebPartManager                  runat="server" />     <asp:Menu                  OnMenuItemClick="Menu1_MenuItemClick"         Orientation="Horizontal"         Css         Runat="server">         <Items>         <asp:MenuItem Text="Browse" />         <asp:MenuItem Text="Design" />         </Items>     </asp:Menu>     <custom:MenuWebPartZone                  Runat="server">         <ZoneTemplate>         <user:TextEditorPart                          Title="Text Editor Part"             Description="Enables you to edit text"             runat="server" />         </ZoneTemplate>     </custom:MenuWebPartZone>     <custom:MenuWebPartZone                  Runat="server" />     </form> </body> </html> 

After you open the page in Listing 30.10 in your Web browser, you can hover your mouse over the File and Edit menu items displayed by the TextEditorWebPart and see the sub-menus. If you select a menu option, the page posts back to the server and executes the doMenuAction() method. This method simply reports the name of the menu item clicked in a Label control.

Notice that the ShowMenuWebPartZone.aspx page contains several style sheet rules. These rules are used to define the background color, size, and general appearance of the menus. The page also includes style sheet rules that determine several aspects of the appearance of the Web Part Chrome, such as the style of the chrome's title bar and border.




ASP. NET 2.0 Unleashed
ASP.NET 2.0 Unleashed
ISBN: 0672328232
EAN: 2147483647
Year: 2006
Pages: 276

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net