As previously mentioned, the default functionality of a Web Part Catalog is pretty limited. By default, Web Part Catalogs do not support paging or sorting. You also cannot drag and drop new Web Parts from a catalog into a Web Part Zone. This section explores several methods of creating fancier catalogs. We'll create a catalog that automatically displays all the Web Parts available in the current application, a catalog that supports drag-and-drop, and a catalog that supports templates. How Catalog Zones Work Three types of parts in the Web Part Framework are related to catalogs: the CatalogZone class, the CatalogPartChrome class, and the CatalogPart class. All three parts participate in the rendering of a catalog. The CatalogZone class creates an instance of the CatalogPartChrome class, and the CatalogPartChrome class renders each CatalogPart control contained in the Catalog Zone. It is important to understand that individual CatalogPart controls, such as the DeclarativeCatalogPart or PageCatalogPart control, don't render anything. They merely act as databases of available catalog items. The CatalogPart class has two important methods: For example, the DeclarativeCatalogPart control derives from the CatalogPart class. This control overrides the GetAvailableWebPartDescriptions() method and returns the list of catalog parts that have been listed in its template. The DeclarativeCatalogPart also overrides the GetWebPart() method to return one of the Web Parts declared in its template. The individual Catalog Part controls contained in a Catalog Zone do not render anything. The CatalogZone control creates an instance of the CatalogPartChrome control and uses this instance to render each of the CatalogPart controls. The CatalogPartChrome control has four important methods: CreateCatalogPartChromeStyle() If you override this method, you can modify the properties of the Style object used when rendering the chrome and catalog part. PerformPreRender() If you override this method, you can execute code before a CatalogPart control is rendered. RenderCatalogPart() If you override this method, then you modify the entire rendering behavior of the chrome. RenderPartContents() If you override this method, then you can render additional content inside the chrome. The CatalogZone class creates an instance of the CatalogPartChrome class and calls its RenderCatalogPart() method to render the selected CatalogPart control. The CatalogZone control itself includes several valuable methods: CreateCatalogParts() Returns the list of catalog parts that the Catalog Zone displays. InvalidateCatalogParts() Resets the collection of catalog parts associated with the Catalog Zone. RenderContents() Renders the entire Catalog Zone. RenderHeader() Renders the header area of a catalog zone. RenderBody() Renders the body area of a catalog zone. RenderFooter() Renders the footer area of a catalog zone. RenderCatalogPartLinks() Renders the list of links to the particular Catalog Parts contained in the Catalog Zone. RenderVerbs() Renders the verbs that appear at the bottom of a catalog zone. The CatalogZoneBase class also includes the following properties: CatalogParts This property automatically calls the CreateCatalogParts() method to create a list of catalog parts. SelectedCatalogPartID This property enables you to get or set the current Catalog Part. If you want to modify the appearance of any Catalog Part, then you must modify the rendering behavior of the Catalog Zone or Catalog Part Chrome that contains the Catalog Part. Creating a Reflection Catalog Part Let's start by creating a new Catalog Part that automatically lists all the Web Part controls contained in the App_Code folder. Because the custom Catalog Part will take advantage of reflection to determine the list of available Web Parts, we'll name this custom Catalog Part the Reflect Catalog Part. Note Reflection refers to the process of retrieving information about .NET Framework typessuch as classes, methods, properties, and attributesat runtime. Two namespaces in the .NET Framework are devoted to reflection: System.Reflection and System.Reflection.Emit. The ReflectCatalogPart control is contained in Listing 30.11. Listing 30.11. ReflectCatalogPart.cs The list of Web Parts defined in the App_Code folder is retrieved in the OnInit() method. The current module is retrieved by grabbing the first module in the currently executing assembly. Next, the FindTypes() method is called with a filter that returns only classes derived from the Web Part class (the filter used by FindTypes() is created with the WebPartFilter() method). The GetAvailableWebPartDescriptions() method returns a collection of WebPartDescription classes. The Catalog Zone calls this method to get the list of Web Parts that it displays. In our implementation of this method, we simply copy the list of Web Parts that we retrieved in the OnInit() method into a WebPartDescriptionCollection class. Finally, the GetWebPart() method returns a Web Part that matches a description. This method is called by the Catalog Zone when a Web Part is added to a Web Part Zone. Again, we take advantage of the collection built in the OnInit() method to return a Web Part that matches the description parameter. You can use the custom ReflectCatalogPart in the page in Listing 30.12. You'll need to switch to Catalog Display Mode by clicking the Catalog link to see the Reflect Catalog Part. Listing 30.12. ShowReflectCatalogPart.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"> 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"> <style type="text/css"> .column { float:left; width:30%; height:200px; margin-right:10px; border:solid 1px black; background-color: white; } .menu { margin:5px 0px; } html { background-color:#eeeeee; } </style> <title>Show Reflect Catalog Part</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" /> <asp:MenuItem Text="Catalog" /> </Items> </asp:Menu> <asp:WebPartZone Css Runat="server" /> <asp:WebPartZone Css Runat="server" /> <asp:CatalogZone Css Runat="server"> <ZoneTemplate> <custom:ReflectCatalogPart Runat="server" /> </ZoneTemplate> </asp:CatalogZone> </form> </body> </html> | When you open the page in Listing 30.12 and click the Catalog link, you'll see all of the Web Parts defined in your App_Code folder listed (see Figure 30.4). The Reflect Catalog Part doesn't display anything unless you add some Web Parts to your App_Code folder. In particular, the control doesn't show Web Parts that you have created with User Controls because these Web Parts are contained in a different assembly. Figure 30.4. Automatically displaying available Web Parts. Creating a Drag-and-Drop Catalog Zone Although the Web Part Framework supports drag-and-drop functionality when you move Web Parts between Web Part Zones, the Web Part Framework does not support drag-and-drop when you want to add new Web Parts to a Web Part Zone from a Catalog Zone. In this section, we'll fix this limitation of the Web Part Framework by creating a custom DragDropCatalogZone control. To create the custom Catalog Zone, you won't have to do as much work as you might expect. You can leverage the existing JavaScript library included with the Web Part Framework. We'll tweak this library so it will work the same way with Catalog Zones as it works with Web Part Zones. First, we need to create a custom Catalog Zone. Listing 30.13 contains the code for the custom DragDropCatalogZone class. Listing 30.13. DragDropCatalogZone.vb [View full width] Imports System Imports System.Web.UI Imports System.Web.UI.WebControls.WebParts Namespace myControls ''' <summary> ''' CatalogZone that supports drag-and-drop ''' adding of Web Parts ''' </summary> Public Class DragDropCatalogZone Inherits CatalogZone ''' <summary> ''' Adds the client-side script for drag-and-drop ''' </summary> Protected Overrides Sub OnPreRender(ByVal e As EventArgs) EnsureID() Dim startupScript As String = String.Format("dragDropCatalogZone.start('{0 }');", Me.ClientID) If Not Page.ClientScript.IsClientScriptIncludeRegistered ("DragDropCatalogZone") Then Page.ClientScript.RegisterClientScriptInclude("DragDropCatalogZone", Page .ResolveUrl("~/ClientScripts/DragDropCatalogZone.js")) Page.ClientScript.RegisterStartupScript(GetType(DragDropCatalogZone), "DragDropCatalogZone", startupScript, True) End If MyBase.OnPreRender(e) End Sub ''' <summary> ''' Utility method to return currently ''' selected Catalog Part ''' </summary> Public ReadOnly Property SelectedCatalogPart() As CatalogPart Get Return Me.CatalogParts(Me.SelectedCatalogPartID) End Get End Property ''' <summary> ''' Override the default postback handler to ''' add support for adding a Web Part to a ''' Web Part Zone ''' </summary> Protected Overrides Sub RaisePostBackEvent(ByVal eventArgument As String) If eventArgument.StartsWith("Add:") Then Dim args() As String = eventArgument.Split(":"c) AddWebPart(args(1), args(2), Int32.Parse(args(3))) End If MyBase.RaisePostBackEvent(eventArgument) End Sub ''' <summary> ''' Use the WebPartManager control to ''' actually add the Web Part ''' </summary> Private Sub AddWebPart(ByVal webPartId As String, ByVal zoneId As String, ByVal zoneIndex As Integer) ' Get Web Part Dim desc As WebPartDescription = SelectedCatalogPart .GetAvailableWebPartDescriptions()(webPartId) Dim webPart As WebPart = SelectedCatalogPart.GetWebPart(desc) ' Get Zone Dim zone As WebPartZoneBase = Me.WebPartManager.Zones(zoneId) ' Add Web Part Me.WebPartManager.AddWebPart(webPart, zone, zoneIndex) End Sub ''' <summary> ''' This Web Part Zone uses the specialized ''' DragDropWebPartChrome ''' </summary> Protected Overrides Function CreateCatalogPartChrome() As CatalogPartChrome Return New DragDropCatalogPartChrome(Me) End Function End Class ''' <summary> ''' This class extends the base CatalogPartChrome ''' class by adding a div element to mark the ''' area of draggable images. ''' </summary> Public Class DragDropCatalogPartChrome Inherits CatalogPartChrome ''' <summary> ''' Add the DIV tag ''' </summary> Public Overrides Sub RenderCatalogPart(ByVal writer As HtmlTextWriter, ByVal catalogPart As CatalogPart) writer.AddAttribute(HtmlTextWriterAttribute.Id, GenerateId()) writer.RenderBeginTag(HtmlTextWriterTag.Div) MyBase.RenderCatalogPart(writer, catalogPart) writer.RenderEndTag() End Sub ''' <summary> ''' Create a unique ID for the DIV tag ''' so we can grab the DIV tag in our ''' client script ''' </summary> Private Function GenerateId() As String Return String.Format("{0}_draggable", Me.Zone.ID) End Function Public Sub New(ByVal zone As CatalogZoneBase) MyBase.New(zone) End Sub End Class End Namespace | The custom Catalog Zone does three things. First, the OnPreRender() method adds a reference to an external JavaScript file named DragDropCatalogZone.js. This file contains the JavaScript code used to support the client-side drag-and-drop functionality. This file is discussed in a moment. The custom Catalog Zone also overrides the base CatalogZone control's RaisePostBackEvent() method. This method is called after a user drops a catalog item into a Web Part Zone. The method adds the new Web Part to the zone with the help of the WebPartManager control's AddWebPart() method. Finally, the custom Catalog Zone overrides the base CreateCatalogPartChrome() method. Notice that the file in Listing 30.13 actually defines two classes: DragDropCatalogZone and DragDropCatalogPartChrome. The DragDropCatalogZone control renders a catalog part by using the custom chrome defined by the DragDropCatalogPartChrome class. This class simply adds a <div> tag around the contents of the CatalogPart. This <div> tag is used in the JavaScript code to identify the draggable catalog items. The JavaScript code used by the custom DragDropCatalogZone control is contained in Listing 30.14. Listing 30.14. DragDropCatalogZone.js [View full width] var dragDropCatalogZone = new function() { this.start = dragDropCatalogZone_start; this.addWebPart = dragDropCatalogZone_addWebPart; } function dragDropCatalogZone_start(catalogZoneId) { // Get Catalog Zone var catalogZone = document.getElementById(catalogZoneId); // Find Element with Draggable Class var draggable = document.getElementById(catalogZoneId + '_draggable'); // Get Images contained in Draggable var images = draggable.getElementsByTagName('img'); // Get Inputs contained in Draggable var inputs = draggable.getElementsByTagName('input'); // Check that Images == Inputs if (images.length != inputs.length) { alert('DragDropCatalogZone:Each catalog item must have a catalog icon'); return; } // Convert images into Web Parts for (var i=0;i<images.length;i++) { var catItem = new WebPart(images[i],images[i]); images[i].webPartId = inputs[i].value; images[i].detachEvent("ondragend", WebPart_OnDragEnd); images[i].attachEvent("ondragend", dragDropCatalogZone.addWebPart); } // Add Drop Handler to WebPartManager __wpm.xCatalogZoneId = catalogZoneId; __wpm.xCompleteAddWebPartDragDrop = dragDropCatalogZone_CompleteAddWebPartDragDrop; } function dragDropCatalogZone_addWebPart() { __wpm.xCompleteAddWebPartDragDrop(); } function dragDropCatalogZone_CompleteAddWebPartDragDrop() { var dragState = this.dragState; this.dragState = null; if (dragState.dropZoneElement != null) { dragState.dropZoneElement.__zone.ToggleDropCues(false, dragState.dropIndex, false); } document.body.detachEvent("ondragover", Zone_OnDragOver); for (var i = 0; i < __wpm.zones.length; i++) { __wpm.zones[i].allowDrop = false; } this.overlayContainerElement.removeChild(this.overlayContainerElement.firstChild); this.overlayContainerElement.style.display = "none"; if ((dragState != null) && (dragState.dropped == true)) { var currentZone = dragState.webPartElement.__webPart.zone; var currentZoneIndex = dragState.webPartElement.__webPart.zoneIndex; if ((currentZone != dragState.dropZoneElement.__zone) || ((currentZoneIndex != dragState.dropIndex) && (currentZoneIndex != (dragState.dropIndex - 1)))) { var eventTarget = this.xCatalogZoneId; var eventArgument = 'Add:' + dragState.webPartElement.webPartId + ':' + dragState.dropZoneElement.__zone.uniqueID + ':' + dragState.dropIndex; this.SubmitPage(eventTarget, eventArgument); } } } | The dragDropCatalogZone_start() method is called immediately after the DragDropCatalogZone control is rendered. This method takes advantage of the <div> tag added to the Catalog Zone by the DragDropCatalogPartChrome class to identify the images and input elements associated with the catalog items. The method converts each of the images in the catalog zone into a Web Part by calling the WebPart() constructor defined in the Web Part Framework JavaScript library. Next, the method overrides the default JavaScript function that is called when a Web Part is dropped into a Web Part Zone. When a user drops a catalog item into a Web Part Zone, the dragDropCatalogZone_CompleteAddWebPartDragDrop() method is called. The dragDropCatalogZone_CompleteAddWebPartDragDrop() method posts the page back to the server by calling the SubmitPage() method. An eventArgument that represents the Web Part ID, Web Part Zone ID, and Web Part Zone index is posted back to the server and processed by the RaisePostBackEventHandler() method. This server method actually adds the Web Part to the selected zone. Listing 30.15 illustrates how you can use the DragDropCatalogZone control in a page. Listing 30.15. ShowDragDropCatalogZone.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"> 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"> <style type="text/css"> .column { float:left; width:30%; height:200px; margin-right:10px; border:solid 1px black; background-color: white; } .menu { margin:5px 0px; } html { background-color:#eeeeee; } </style> <title>Show Drag-and-Drop Catalog 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" /> <asp:MenuItem Text="Catalog" /> </Items> </asp:Menu> <asp:WebPartZone Css Runat="server" /> <asp:WebPartZone Css Runat="server" /> <custom:DragDropCatalogZone Css Runat="server"> <ZoneTemplate> <asp:DeclarativeCatalogPart Runat="server"> <WebPartsTemplate> <asp:Label CatalogIconImageUrl="~/Images/FirstSimplePart.gif" Title="First Simple Part" Description="The first Web Part" Runat="server" /> <asp:Label CatalogIconImageUrl="~/Images/SecondSimplePart.gif" Title="Second Simple Part" Description="The second Web Part" Runat="server" /> </WebPartsTemplate> </asp:DeclarativeCatalogPart> <asp:PageCatalogPart Runat="server" /> </ZoneTemplate> </custom:DragDropCatalogZone> </form> </body> </html> | After you open the page in Listing 30.15, you can view the custom Catalog Zone by clicking the Catalog link. Notice that you can add items from the catalog to the page by dragging the catalog icons onto particular Web Part Zones (see Figure 30.5). Figure 30.5. Dragging and dropping from a catalog. Web Standards Note The DragDropCatalogZone control still renders the normal check boxes for adding items. This is good from an accessibility standpoint. When you implement fancy JavaScript drag-and-drop functionality, you should always implement an equivalent way of doing the same thing from the keyboard. Creating a Templated Catalog Zone The default CatalogZone control has a certain appearance and there is nothing you can do about it. The CatalogZone control does not support paging or sorting. It doesn't even display the descriptions with Web Parts. In this section, we'll fix this limitation of the Web Part Framework by building a custom Templated Catalog Zone. The custom CatalogZone control supports an ItemTemplate. You can take advantage of the ItemTemplate to display catalog items in any way that you please. For example, you can use a GridView control in the ItemTemplate to enable users to sort and page through catalog items. You can also take advantage of the ItemTemplate to modify the default appearance of the Catalog Part menu. Rather than display a list of links to individual Catalog Parts, you can display a drop-down list of Catalog Parts (see Figure 30.6). Figure 30.6. Displaying a drop-down list of catalog parts. The custom Templated Catalog Zone control is contained in Listing 30.16. Listing 30.16. TemplatedCatalogZone.vb [View full width] Imports System Imports System.Web.UI Imports System.Web.UI.WebControls.WebParts Namespace myControls ''' <summary> ''' This control enables you to lay out a ''' Catalog Zone with a template ''' </summary> Public Class TemplatedCatalogZone Inherits CatalogZone Private _itemTemplate As ITemplate Private _item As ItemContainer ''' <summary> ''' Represents the Item Template ''' </summary> <TemplateContainer(GetType(TemplatedCatalogZone))> _ Public Property ItemTemplate() As ITemplate Get Return _itemTemplate End Get Set(ByVal Value As ITemplate) _itemTemplate = Value End Set End Property ''' <summary> ''' Utility method that returns the currently ''' selected Catalog Part ''' </summary> Public ReadOnly Property SelectedCatalogPart() As CatalogPart Get Return CatalogParts(SelectedCatalogPartID) End Get End Property ''' <summary> ''' Returns list of Web Web Part descriptions ''' so that you can use Container.Descriptions ''' in the ItemTemplate ''' </summary> Public ReadOnly Property Descriptions() As WebPartDescriptionCollection Get Return SelectedCatalogPart.GetAvailableWebPartDescriptions() End Get End Property ''' <summary> ''' Returns list of Web Part Zones so that you ''' can use Container.Zones in the ItemTemplate ''' </summary> Public ReadOnly Property Zones() As WebPartZoneCollection Get Return Me.WebPartManager.Zones End Get End Property ''' <summary> ''' Adds a new Web Part to a Web Part Zone ''' </summary> Public Sub AddWebPart(ByVal webPartID As String, ByVal zoneID As String) ' Get Web Part Dim descs As WebPartDescriptionCollection = SelectedCatalogPart .GetAvailableWebPartDescriptions() Dim NewWebPart As WebPart = SelectedCatalogPart.GetWebPart(descs(webPartID)) ' Get Zone Dim zones As WebPartZoneCollection = Me.WebPartManager.Zones Dim selectedZone As WebPartZoneBase = zones(zoneID) ' Add the Web Part Me.WebPartManager.AddWebPart(NewWebPart, selectedZone, 0) End Sub ''' <summary> ''' Extends base method to support the ItemTemplate ''' </summary> Protected Overrides Sub CreateChildControls() MyBase.CreateChildControls() _item = New ItemContainer() ItemTemplate.InstantiateIn(_item) Controls.Add(_item) End Sub ''' <summary> ''' Render the contents of the ItemTemplate ''' </summary> Protected Overrides Sub RenderBody(ByVal writer As HtmlTextWriter) _item.RenderControl(writer) End Sub ''' <summary> ''' Suppress the footer since the same content ''' is contained in our ItemTemplate ''' </summary> Protected Overrides Sub RenderFooter(ByVal writer As HtmlTextWriter) End Sub End Class ''' <summary> ''' This class does nothing but hold the contents ''' of the ItemTemplate so we can render it. ''' </summary> Public Class ItemContainer Inherits Control End Class End Namespace | The TemplatedCatalogZone control in Listing 30.16 inherits from the base CatalogZone class. The derived class exposes an ItemTemplate property that you can use to customize the control's appearance. The ItemTemplate is created in the CreateChildControls() method, and the contents of the ItemTemplate are rendered in the RenderBody() method. Notice that the TemplatedCatalogZone class includes a public AddWebPart() method. This method adds a Web Part with a particular ID to a particular Web Part Zone. The Web Part is added with the help of the WebPartManager class's AddWebPart() method. The page in Listing 30.17 contains the TemplatedCatalogZone control. Listing 30.17. ShowTemplatedCatalogZone.aspx [View full width] <%@ 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"> ''' <summary> ''' Whenever the CatalogZone is opened, we need to ''' rebind to the datasource ''' </summary> Protected Sub WebPartManager1_DisplayModeChanged(ByVal sender As Object, ByVal e As WebPartDisplayModeEventArgs) If WebPartManager1.DisplayMode Is WebPartManager.CatalogDisplayMode Then CatalogZone1.SelectedCatalogPartID = CatalogZone1.CatalogParts(0).ID CatalogZone1.DataBind() End If End Sub ''' <summary> ''' When a user selects a new Catalog Part, ''' we need to rebind the GridView ''' </summary> Protected Sub btnSelectCatalog_Click(ByVal sender As Object, ByVal e As EventArgs) ' Update selected catalog Dim dropCatalogs As DropDownList = CType(CatalogZone1.FindControl("dropCatalogs"), DropDownList) CatalogZone1.SelectedCatalogPartID = dropCatalogs.SelectedValue End Sub ''' <summary> ''' When a user selects a new page, we ''' must rebind the GridView ''' </summary> Protected Sub grdDesc_PageIndexChanging(ByVal sender As Object, ByVal e As GridViewPageEventArgs) Dim grdDesc As GridView = CType(sender, GridView) grdDesc.PageIndex = e.NewPageIndex End Sub ''' <summary> ''' When a user selects a Web Part from a Catalog, we ''' add the new Web Part to the page ''' </summary> Protected Sub grdDesc_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) ' Rebind the GridView to reload the DataKeys Dim grdDesc As GridView = CType(sender, GridView) grdDesc.DataBind() ' Get Selected Web Part Dim webPartID As String = CType(grdDesc.SelectedValue, String) ' Get Selected Zone Dim dropZones As DropDownList = CType(CatalogZone1.FindControl("dropZones"), DropDownList) Dim zoneID As String = dropZones.SelectedValue ' Add the Web Part to the Page CatalogZone1.AddWebPart(webPartID, zoneID) End Sub ''' <summary> ''' If in Catalog Display Mode, bind GridView ''' </summary> Protected Overrides Sub OnPreRenderComplete(ByVal e As EventArgs) If WebPartManager1.DisplayMode Is WebPartManager.CatalogDisplayMode Then Dim grdDesc As GridView = CType(CatalogZone1.FindControl("grdDesc"), GridView) grdDesc.DataBind() End If MyBase.OnPreRenderComplete(e) End Sub 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"> <style type="text/css"> .catalogPartStyle { padding:10px; font:14px Georgia,serif; } .catalogPartStyle fieldset { padding-top:30px; padding-bottom:10px; margin-bottom:10px; } .catalogPartStyle label { font-weight:bold; } .catalogGrid { margin:10px; } .catalogRow td { padding:5px; border-bottom:solid 1px black; } .column { float:left; width:30%; height:200px; margin-right:10px; border:solid 1px black; background-color: white; } .menu { margin:5px 0px; } html { background-color:#eeeeee; } </style> <title>Show Templated Catalog Zone</title> </head> <body> <form runat="server"> <asp:WebPartManager Runat="server" OnDisplayModeChanged="WebPartManager1_DisplayModeChanged" /> <asp:Menu OnMenuItemClick="Menu1_MenuItemClick" Orientation="Horizontal" Css Runat="server"> <Items> <asp:MenuItem Text="Browse" /> <asp:MenuItem Text="Design" /> <asp:MenuItem Text="Catalog" /> </Items> </asp:Menu> <asp:WebPartZone HeaderText="Zone 1" Css Runat="server" /> <asp:WebPartZone HeaderText="Zone 2" Css Runat="server"/> <custom:TemplatedCatalogZone Css Runat="server"> <ItemTemplate> <fieldset> <legend>Select Catalog</legend> <asp:DropDownList DataTextField="Title" DataValueField="ID" DataSource='<%# Container.CatalogParts %>' runat="server"/> <asp:Button Text="select" Tooltip="Select Catalog" OnClick="btnSelectCatalog_Click" runat="server"/> </fieldset> <asp:Label Text="Add to Zone:" AssociatedControl Runat="server"/> <asp:DropDownList DataTextField="HeaderText" DataValueField="ID" DataSource='<%# Container.Zones %>' runat="server" /> <asp:GridView GridLines="none" Css RowStyle-Css DataKeyNames="ID" AllowPaging="true" PageSize="2" ShowHeader="false" EmptyDataText="This catalog is empty" AutoGenerateColumns="false" AutoGenerateSelectButton="true" DataSource='<%# Container.Descriptions %>' OnPageIndexChanging="grdDesc_PageIndexChanging" OnSelectedIndexChanged="grdDesc_SelectedIndexChanged" runat="server"> <Columns> <asp:BoundField DataField="Title" /> <asp:BoundField DataField="Description" /> </Columns> </asp:GridView> </ItemTemplate> <ZoneTemplate> <asp:DeclarativeCatalogPart Title="Declarative Catalog" Runat="server"> <WebPartsTemplate> <asp:Label Title="Label 1" Description="The very first Label" Runat="server" /> <asp:Label Title="Label 2" Description="The second Label" Runat="server" /> <asp:Label Title="Label 3" Description="The very last Label" Runat="server" /> </WebPartsTemplate> </asp:DeclarativeCatalogPart> <asp:PageCatalogPart Title="Page Catalog" Runat="server"/> </ZoneTemplate> </custom:TemplatedCatalogZone> </form> </body> </html> | If you open the page in Listing 30.17 in your browser and click the Catalog link, you can see the custom Templated Catalog Zone. Notice that the custom Catalog Zone supports paging. Furthermore, notice that unlike the default CatalogZone control, the list of Catalog Parts is displayed in a drop-down list. In Listing 30.17, the TemplatedCatalogZone control contains an ItemTemplate that includes two DropDownList controls and a GridView control. The DropDownList controls are used to display a list of Catalog Parts and a list of Web Part Zones. The GridView control is used to display the list of catalog items (the Web Part descriptions). Each of these three controls is bound to a datasource with a databinding expression. For example, the DropDownList control that displays the list of Catalog Parts is declared like this: <asp:DropDownList DataTextField="Title" DataValueField="ID" DataSource='<%# Container.CatalogParts %>' runat="server"/> In this control declaration, Container.CatalogParts refers to the CatalogParts property exposed by the TemplateCatalogControl class. Because this class also exposes a Descriptions and Zones property, you can also use Container.Descriptions and Container.Zones in a databinding expression within the TemplateCatalogZone control's ItemTemplate. Because the controls in the ItemTemplate do not take advantage of declarative databinding, you must undertake the responsibility of binding the controls to their respective data sources. Notice, for instance, that the GridView control is bound to its data source in the OnPreRenderComplete() method defined near the top of the file in Listing 30.17. Note Notice that the GridView is bound to its data source during the PreRenderComplete event. This is the last event that happens before the page is rendered to the browser. You must perform the databinding at this point in the page execution lifecycle because certain Catalog Parts, such as the PageCatalogPart control, don't update their catalog of Web Parts until the PreRender event. The advantage of using the TemplatedCatalogZone control is that you can make a Catalog Zone look like anything you want. However, as Spiderman says, "With great power comes great responsibility." The disadvantage is that you are forced to write all sorts of additional code to bind the data controls to their data sources. |