In this chapter, you learn how to use the SiteMapPath
, Menu
, and TreeView
controls. All three of these controls can be used to enable users to navigate your website. Furthermore, the Menu
and TreeView
controls can be used independently of website navigation. You can bind these two controls to other data sources such as XML documents or database data.
This chapter explores different methods of binding the Menu
and TreeView
controls to different data sources and shows you how to format the rendered output of both of these controls. You also learn how to take advantage of AJAX when working with the TreeView
control.
In the final section of this chapter, we build a SqlHierarchicalDataSource
control, which enables you to bind controls such as the TreeView
and Menu
controls to hierarchical database data.
Before you learn about the navigation controls, you first need to understand Site Maps. All three navigation controls use Site Maps to retrieve navigation information. A Site Map enables you to represent the navigational relationships between the pages in an application, independent of the actual physical relationship between pages as stored in the file system.
Site Maps use the provider model. In the next chapter, you learn how to create custom Site Map providers to store Site Maps in custom data stores such as database tables. The examples in this chapter take advantage of the default XML Site Map provider, which enables you to store a Site Map in an XML file.
By default, the navigation controls assume the existence of an XML file named Web.sitemap
, which is located in the root of your application.
For example, Listing 17.1 contains a simple Site Map.
Example 17.1. Web.sitemap
<?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode url="~/Default.aspx" title="Home" description="The home page of the Website"> <!-- Product Nodes --> <siteMapNode title="Products" description="Website products"> <siteMapNode url="~/Products/FirstProduct.aspx" title="First Product" description="The first product" /> <siteMapNode url="~/Products/SecondProduct.aspx" title="Second Product" description="The second product" /> </siteMapNode> <!-- Services Nodes --> <siteMapNode title="Services" description="Website services"> <siteMapNode url="~/Service/FirstService.aspx" title="First Service" description="The first service" /> <siteMapNode url="~/Products/SecondService.aspx" title="Second Service" description="The second service" /> </siteMapNode> </siteMapNode> </siteMapNode> </siteMap>
A Site Map file contains <siteMapNode>
elements. There can be only one top-level node. In the case of Listing 17.1, the top-level node represents the website’s homepage.
A <siteMapNode>
supports three main attributes:
title
—. A brief title that you want to associate with a node.
description
—. A longer description that you want to associate with a node.
url
—. A URL that points to a page or other resource.
Notice that the url
attribute is not required. Both the Products and Services nodes do not include a url
attribute because these nodes do not represent pages to which you can navigate.
Each <siteMapNode>
can contain any number of child nodes. In Listing 17.1, both the Products and Services nodes include two child nodes.
The Site Map in Listing 17.1 represents a website that has the following folder and page structure:
Default.aspx Products FirstProduct.aspx SecondProduct.aspx Services FirstService.aspx SecondService.aspx
The navigational structure of a website as represented by a Site Map is not required to have any relationship to the navigational structure of a website as stored in the file system. You can create any relationship between the nodes in a Site Map that you want.
The SiteMapPath
control enables you to navigate easily to any parent page of the current page. It displays the standard bread crumb trail that you see on many popular websites (see Figure 17.1).
You can use the SiteMapPath
control simply by declaring the control in a page. The control automatically uses the Web.sitemap
file located in the root of your application. For example, the page in Listing 17.2 includes the SiteMapPath
control (see Figure 17.2).
Example 17.2. UsingSiteMapPath/DisplaySiteMapPath.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>Display SiteMapPath</title> </head> <body> <form id="form1" runat="server"> <div> <asp:SiteMapPath id="SiteMapPath1" Runat="server" /> <hr /> <h1>Displaying a SiteMapPath Control</h1> </div> </form> </body> </html>
Notice that you can click the Home link rendered by the SiteMapPath
control to navigate to the website’s home page.
The SiteMapPath
uses both the title
and description
attributes from the <siteMapNode>
elements contained in the Web.sitemap
file. The title
attribute is used for the node (link) text, and the description
attribute is used for the node tool tip.
Typically, you do not add a SiteMapPath
control to individual pages in your website. If you add a SiteMapPath
control to a Master Page, then you can display the SiteMapPath
control automatically on every page. To learn more about Master Pages, see Chapter 5, “Designing Websites with Master Pages.”
The SiteMapPath
control supports the following properties:
ParentLevelsDisplay
—. Enables you to limit the number of parent nodes displayed. By default, a SiteMapPath
control displays all the parent nodes.
PathDirection
—. Enables you to reverse the order of the links displayed by the SiteMapPath
control. Possible values are RootToCurrent
(the default) or CurrentToRoot
.
PathSeparator
—. Enables you to specify the character used to separate the nodes displayed by the SiteMapPath
control. The default value is >
.
RenderCurrentNodeAsLink
—. Enables you to render the SiteMapPath
node that represents the current page as a link. By default, the current node is not rendered as a link.
ShowToolTips
—. Enables you to disable the display of tool tips.
SiteMapProvider
—. Enables you to specify the name of an alternate Site Map provider to use with the SiteMapPath
control.
SkipLinkText
—. Enables you to specify more specific text for skipping the links displayed by the SiteMapPath
control. The default value for this property is Skip Navigation Links
.
All the navigation controls automatically render a skip navigation link to meet accessibility requirements. The skip navigation link is read by a screen reader, but it is not displayed in a normal browser.
If you are interacting with a web page through a screen reader, you don’t want to hear the list of navigation links each and every time you open a page. (It is the equivalent of listening to a phone menu every time you open a page.) The skip navigation link enables users of screen readers to skip the repetitive reading of links.
You can use either styles or templates to format the SiteMapPath
control.
The control supports the following Style objects:
CurrentNodeStyle
—. Formats the SiteMapPath
node that represents the current page.
NodeStyle
—. Formats every node rendered by the SiteMapPath
control.
PathSeparatorStyle
—. Formats the text displayed between each SiteMapPath
node.
RootNodeStyle
—. Formats the root (first) node rendered by the SiteMapPath
control.
For example, the page in Listing 17.3 takes advantage of all four Style properties to modify the default appearance of the SiteMapPath
control (see Figure 17.3).
Example 17.3. UsingSiteMapPath/SiteMapPathStyle.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> .siteMapPath { font:20px Comic Sans MS,Serif; } .currentNodeStyle { font-weight:bold; } .nodeStyle { text-decoration:none; } .pathSeparatorStyle { background-color:yellow; margin:10px; border:Solid 1px black; } .rootNodeStyle { text-decoration:none; } </style> <title>SiteMapPath Style</title> </head> <body> <form id="form1" runat="server"> <div> <asp:SiteMapPath id="SiteMapPath1" CssClass="siteMapPath" CurrentNodeStyle-CssClass="currentNodeStyle" NodeStyle-CssClass="nodeStyle" PathSeparatorStyle-CssClass="pathSeparatorStyle" RootNodeStyle-CssClass="rootNodeStyle" Runat="server" /> <hr /> <h1>SiteMapPath Style</h1> </div> </form> </body> </html>
Furthermore, you can use templates with the SiteMapPath
control to format the appearance of the control (and change its behavior). The SiteMapPath
control supports the following templates:
CurrentNodeTemplate
—. Template for the SiteMapPath
node that represents the current page.
NodeTemplate
—. Template for each SiteMapPath
node that is not the current or root node.
PathSeparatorTemplate
—. Template for the text displayed between each SiteMapPath
node.
RootNodeTemplate
—. Template for the root (first) node rendered by the SiteMapPath
control.
For example, the SiteMapPath
control in Listing 17.4 includes a NodeTemplate
. The NodeTemplate
includes a HyperLink
control that displays the current SiteMapPath
node. The template also displays a count of the child nodes of the current node (see Figure 17.4).
Example 17.4. UsingSiteMapPath/SiteMapPathTemplates.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>SiteMapPath Templates</title> </head> <body> <form id="form1" runat="server"> <div> <asp:SiteMapPath id="SiteMapPath1" Runat="server"> <NodeTemplate> <asp:HyperLink id="lnkPage" Text='<%# Eval("Title") %>' NavigateUrl='<%# Eval("Url") %>' ToolTip='<%# Eval("Description") %>' Runat="server" /> [<%# Eval("ChildNodes.Count") %>] </NodeTemplate> </asp:SiteMapPath> <hr /> <h1>SiteMapPath Templates</h1> </div> </form> </body> </html>
Within a template, the data item represents a SiteMapNode
. Therefore, you can refer to any of the properties of the SiteMapNode
class in a databinding expression.
The Menu
control enables you to create two types of menus. You can use the Menu
control to create the left-column menu that appears in many websites. In other words, you can use the Menu
control to display a vertical list of links.
You also can use the Menu
control to create a menu that more closely resembles the drop-down menus that appear in traditional desktop applications. In this case, the Menu
control renders a horizontal list of links.
Unlike the SiteMapPath
control, the Menu
control can represent other types of data than Site Map data. Technically, you can bind a Menu
control to any data source that implements the IHiearchicalDataSource
or IHiearchicalEnumerable
interface.
In this section, you learn how to create different types of menus with the Menu
control. First, you learn how to add menu items declaratively to a Menu
control. Next, we discuss how the Menu
control can be used with the MultiView
control to display a tabbed page.
You also examine how you can bind the Menu
control to different types of data sources. You learn how to use the Menu
control with Site Map data, XML data, and database data.
You can display a menu with the Menu
control by adding one or more Menu Item
objects to its Items
property. For example, the page in Listing 17.5 uses a Menu
control to create a simple vertical menu (see Figure 17.5).
Example 17.5. MenuHyperLink.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>Menu HyperLink</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" Runat="server"> <Items> <asp:MenuItem Text="Products" NavigateUrl="Products.aspx" /> <asp:MenuItem Text="Services" NavigateUrl="Services.aspx"> <asp:MenuItem Text="Training" NavigateUrl="Training.aspx" /> <asp:MenuItem Text="Consulting" NavigateUrl="Consulting.aspx" /> </asp:MenuItem> </Items> </asp:Menu> </div> </form> </body> </html>
The Menu
in Listing 17.5 is created from MenuItem
objects. Each menu item in Listing 17.5 contains a link to another page.
Notice that MenuItem
objects can be nested. The second MenuItem
object—Services—includes two child MenuItem
objects. When you hover your mouse over a parent menu item, the child menu items are displayed.
Each MenuItem
in Listing 17.5 includes a Text
and NavigateUrl
property. Rather than use a MenuItem
to link to a new page, you also can use a MenuItem
to link back to the same page. In other words, each MenuItem
can act like a Linkbutton
control instead of a HyperLink
control.
For example, each MenuItem
object in Listing 17.6 includes a Text
and Value
property. When you click a menu item, the same page is reloaded and the value of the selected menu item is displayed (see Figure 17.6).
Example 17.6. MenuLinkButton.aspx
<%@ Page Language="VB" %> <!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) lblMessage.Text = "You selected " & Menu1.SelectedValue End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>Menu LinkButton</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" OnMenuItemClick="Menu1_MenuItemClick" Runat="server"> <Items> <asp:MenuItem Text="Products Page" Value="Products" /> <asp:MenuItem Text="Services Page" Value="Services"> <asp:MenuItem Text="Training Page" Value="Training" /> <asp:MenuItem Text="Consulting Page" Value="Consulting" /> </asp:MenuItem> </Items> </asp:Menu> <hr /> <asp:Label id="lblMessage" EnableViewState="false" Runat="server" /> </div> </form> </body> </html>
Notice that the page includes a MenuItemClick
event handler. When you click a MenuItem
(and the MenuItem
does not have a NavigateUrl
property), the MenuItemClick
event is raised.
In Listing 17.6, the MenuItemClick
handler displays the value of the selected MenuItem in a Label
control.
When the Menu
control is used with the MultiView
control, you can create tabbed pages. You use the Menu
control to display the tabs, and the MultiView
control to display the content that corresponds to the selected tab.
For example, the page in Listing 17.7 displays three tabs (see Figure 17.7).
Example 17.7. MenuTabStrip.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> Protected Sub menuTabs_MenuItemClick(ByVal sender As Object, ByVal e As MenuEventArgs) multiTabs.ActiveViewIndex = Int32.Parse(menuTabs.SelectedValue) End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> html { background-color:silver; } .menuTabs { position:relative; top:1px; left:10px; } .tab { border:Solid 1px black; border-bottom:none; padding:0px 10px; background-color:#eeeeee; } .selectedTab { border:Solid 1px black; border-bottom:Solid 1px white; padding:0px 10px; background-color:white; } .tabBody { border:Solid 1px black; padding:20px; background-color:white; } </style> <title>Menu Tab Strip</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="menuTabs" CssClass="menuTabs" StaticMenuItemStyle-CssClass="tab" StaticSelectedStyle-CssClass="selectedTab" Orientation="Horizontal" OnMenuItemClick="menuTabs_MenuItemClick" Runat="server"> <Items> <asp:MenuItem Text="Tab 1" Value="0" Selected="true" /> <asp:MenuItem Text="Tab 2" Value="1"/> <asp:MenuItem Text="Tab 3" Value="2" /> </Items> </asp:Menu> <div class="tabBody"> <asp:MultiView id="multiTabs" ActiveViewIndex="0" Runat="server"> <asp:View ID="view1" runat="server"> Contents of first tab </asp:View> <asp:View ID="view2" runat="server"> Contents of second tab </asp:View> <asp:View ID="view3" runat="server"> Contents of third tab </asp:View> </asp:MultiView> </div> </div> </form> </body> </html>
After you open the page in Listing 17.7 and click a tab, the MenuItemClick
event is raised. The MenuItemClick
event handler changes the ActiveViewIndex
property of the MultiView
control to display the content of the selected tab.
The Menu
control in Listing 17.7 is pushed down one pixel and pushed right 10 pixels to hide the border between the selected tab and the contents of the tab. (The Menu
control has a relative position.) Notice that the style rule for the selected tab includes a white bottom border. This trick works in Internet Explorer 6, Firefox 1, and Opera 8.
Like the SiteMapPath
control, you can use the Menu
control with a Site Map. Users can click menu items to navigate to particular pages in your website.
Unlike the SiteMapPath
control, however, the Menu
control does not automatically bind to a Site Map. You must explicitly bind the Menu
control to a SiteMapDataSource
control to display nodes from a Site Map.
For example, the page in Listing 17.8 contains a menu that contains links to all the pages in a website (see Figure 17.8).
Example 17.8. UsingMenu/MenuSiteMap.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>Menu SiteMap</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" DataSourceID="srcSiteMap" Runat="server" /> <asp:SiteMapDataSource id="srcSiteMap" Runat="server" /> </div> </form> </body> </html>
When you initially open the page in Listing 17.8, the only menu item that appears is the link to the Home page. If you hover your mouse over this link, links to additional pages are displayed.
Normally, you do not want the Home link to be displayed in a navigation menu. Instead, you want to display the second level of menu items. You can use the ShowStartingNode
property of the SiteMapDataSource
control to hide the topmost node in a Site Map.
For example, the page in Listing 17.9 uses a Menu
control that renders a standard left-column navigational menu (see Figure 17.9).
Example 17.9. UsingMenu/MenuNavigate.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> html { background-color:silver; } .navigation { float:left; width:280px; height:500px; padding:20px; background-color:#eeeeee; } .content { float:left; width:550px; height:500px; padding:20px; background-color:white; } .menuItem { border:Outset 1px black; background-color:Gray; font:14px Arial; color:White; padding:8px; } </style> <title>Menu Navigate</title> </head> <body> <form id="form1" runat="server"> <div class="navigation"> <asp:Menu id="Menu1" DataSourceID="srcSiteMap" StaticMenuItemStyle-CssClass="menuItem" DynamicMenuItemStyle-CssClass="menuItem" Runat="server" /> <asp:SiteMapDataSource id="srcSiteMap" ShowStartingNode="false" Runat="server" /> </div> <div class="content"> <h1>Displaying a Website menu with the Menu control</h1> </div> </form> </body> </html>
When you open the page in Listing 17.9, the second-level nodes from the Site Map are initially displayed. Furthermore, the Menu
control is styled to appear more like a traditional website navigation menu.
As an alternative to binding a Menu
control to a SiteMapDataSource
control, you can bind the control to an XML document by using the XmlDataSource
control. For example, suppose that you have the XML file in Listing 17.10.
The page in Listing 17.11 displays the contents of Listing 17.10 by using an XmlDataSource
control to represent the XML document.
Example 17.11. MenuXML.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>Menu XML</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" DataSourceID="srcMenu" Runat="server" /> <asp:XmlDataSource id="srcMenu" DataFile="Menu.xml" Runat="server" /> </div> </form> </body> </html>
When using the XmlDataSource
control, you can use the XPath
property to supply an xpath
query that restricts the nodes returned by the XmlDataSource
. You also can use either the Transform
or TransformFile
property to apply an XSLT Style Sheet to the XML document and transform the nodes returned by the XmlDataSource
.
The XML file in Listing 17.10 is very simple. The nodes do not contain any attributes. When you bind the Menu
control to the XML file, the ToString()
method is called on each XML file node.
You also can bind the Menu
control to more complex XML documents. For example, the item nodes in the XML document in Listing 17.12 include two attributes: text and price.
Example 17.12. MenuComplex.xml
<?xml version="1.0" encoding="utf-8" ?> <menu> <category text="appetizer"> <item text="soup" price="12.56" /> <item text="cheese" price="17.23" /> </category> <category text="entree"> <item text="duck" price="89.21" /> <item text="chicken" price="34.56" /> </category> <category text="dessert"> <item text="cake" price="23.43" /> <item text="pie" price="115.46" /> </category> </menu>
When you bind to the XML document in Listing 17.12, you must specify one or more menu item bindings. The menu item bindings specify the relationship between node attributes and the menu items displayed by the Menu
control.
The Menu
control in Listing 17.13 includes MenuItemBinding
subtags (see Figure 17.10).
Example 17.13. MenuXMLComplex.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>Menu XML Complex</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" DataSourceID="srcMenu" Runat="server"> <DataBindings> <asp:MenuItemBinding DataMember="category" TextField="text" /> <asp:MenuItemBinding DataMember="item" TextField="text" ValueField="price" /> </DataBindings> </asp:Menu> <asp:XmlDataSource id="srcMenu" DataFile="MenuComplex.xml" Runat="server" /> </div> </form> </body> </html>
Notice that the Menu
control includes a <DataBindings>
element. This element includes two MenuItemBinding
subtags. The first subtag represents the relationship between the category nodes in the XML file and the menu items. The second subtag represents the relationship between the item nodes in the XML file and the menu items.
You can’t bind a Menu
control directly to database data. Neither the SqlDataSource
nor ObjectDataSource
controls implement the IHierachicalDataSource
interface. Therefore, if you want to represent database data with the Menu
control, then you need to perform some more work.
One option is to create your own SqlHiearachicalDataSource
control. You can do this either by deriving from the base HiearchicalDataSourceControl
class or implementing the IHiearchicalDataSource
interface. We’ll take this approach in the final section of this chapter, when we create a custom SqlHierarchicalDataSource
control.
A second option is to build the menu items programmatically in the Menu
control. This is the approach that is followed here.
Imagine that you want to represent the contents of the following database table with a Menu
control:
CategoryId | ParentId | Name |
---|---|---|
1 | null | Beverages |
2 | null | Fruit |
3 | 1 | Milk |
4 | 1 | Juice |
4 | Apple Juice | |
6 | 4 | Orange Juice |
7 | 2 | Apples |
8 | 2 | Pears |
This database table represents product categories. The categories are nested with the help of the ParentId column. For example, the Orange Juice category is nested below the Juice category, and the Juice category is nested below the Beverages category.
The page in Listing 17.14 illustrates how you can display this database table with a Menu
control (see Figure 17.11).
Example 17.14. MenuDatabase.aspx
<%@ Page Language="VB" %> <%@ Import Namespace="System.Web.Configuration" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> ''' <summary> ''' Only populate the menu when the page first loads ''' </summary> Private Sub Page_Load() If Not Page.IsPostBack Then PopulateMenu() End If End Sub ''' <summary> ''' Get the data from the database and create the top-level ''' menu items ''' </summary> Private Sub PopulateMenu() Dim menuData As DataTable = GetMenuData() AddTopMenuItems(menuData) End Sub ''' <summary> ''' Use a DataAdapter and DataTable to grab the database data ''' </summary> ''' <returns></returns> Private Function GetMenuData() As DataTable ' Get Categories table Dim selectCommand As String = "SELECT CategoryId,ParentId,Name FROM Categories" Dim conString As String = WebConfigurationManager.ConnectionStrings("Categories").ConnectionString Dim dad As SqlDataAdapter = New SqlDataAdapter(selectCommand, conString) Dim dtblCategories As DataTable = New DataTable() dad.Fill(dtblCategories) Return dtblCategories End Function ''' <summary> ''' Filter the data to get only the rows that have a ''' null ParentID (these are the top-level menu items) ''' </summary> Private Sub AddTopMenuItems(ByVal menuData As DataTable) Dim view As DataView = New DataView(menuData) view.RowFilter = "ParentID IS NULL" Dim row As DataRowView For Each row In view Dim NewMenuItem As MenuItem = New MenuItem(row("Name").ToString(), row("CategoryId").ToString()) Menu1.Items.Add(NewMenuItem) AddChildMenuItems(menuData, NewMenuItem) Next End Sub ''' <summary> ''' Recursively add child menu items by filtering by ParentID ''' </summary> Private Sub AddChildMenuItems(ByVal menuData As DataTable, ByVal parentMenuItem As MenuItem) Dim view As DataView = New DataView(menuData) view.RowFilter = "ParentID=" + parentMenuItem.Value Dim row As DataRowView For Each row In view Dim NewMenuItem As MenuItem = New MenuItem(row("Name").ToString(), row("CategoryId").ToString()) parentMenuItem.ChildItems.Add(NewMenuItem) AddChildMenuItems(menuData, NewMenuItem) Next End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> .menuItem { border:Solid 1px black; width:100px; padding:2px; background-color:#eeeeee; } .menuItem a { color:blue; } .grid { margin-top:10px; } .grid td, .grid th { padding:10px; } </style> <title>Menu Database</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" Orientation="horizontal" StaticMenuItemStyle-CssClass="menuItem" DynamicMenuItemStyle-CssClass="menuItem" Runat="server" /> <asp:GridView id="grdProducts" DataSourceID="srcProducts" CssClass="grid" AutoGenerateColumns="false" Runat="server"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product" /> <asp:BoundField DataField="Price" HeaderText="Price" DataFormatString="{0:c}" /> </Columns> </asp:GridView> <asp:SqlDataSource id="srcProducts" ConnectionString="<%$ ConnectionStrings:Categories %>" SelectCommand="SELECT ProductName,Price FROM Products WHERE CategoryId=@CategoryId" Runat="server"> <SelectParameters> <asp:ControlParameter Name="CategoryId" ControlID="Menu1" /> </SelectParameters> </asp:SqlDataSource> </div> </form> </body> </html>
The menu items are added to the Menu
control in the PopulateMenu()
method. This method first grabs a DataTable that contains the contents of the Categories database table. Next, it creates a menu item for each row that does not have a parent row (each row where the ParentId column has the value null
).
The child menu items for each menu item are added recursively. The ParentId column is used to filter the contents of the Categories DataTable.
The page in Listing 17.14 also includes a GridView control that displays a list of products that match the category selected in the menu. The GridView is bound to a SqlDataSource
control, which includes a ControlParameter
that filters the products based on the selected menu item.
The Menu
control supports an abundance of properties that can be used to format the appearance of the control. Many of these properties have an effect on static menu items, and many of these properties have an effect on dynamic menu items. Static menu items are menu items that always appear. Dynamic menu items are menu items that appear only when you hover your mouse over another menu item.
First, the Menu
control supports the following general properties related to formatting:
DisappearAfter
—. Enables you to specify the amount of time, in milliseconds, that a dynamic menu item is displayed after a user moves the mouse away from the menu item.
DynamicBottomSeparatorImageUrl
—. Enables you to specify the URL to an image that appears under each dynamic menu item.
DynamicEnableDefaultPopOutImage
—. Enables you to disable the image (triangle) that indicates that a dynamic menu item has child menu items.
DynamicHorizontalOffset
—. Enables you to specify the number of pixels that a dynamic menu item is shifted relative to its parent menu item.
DynamicItemFormatString
—. Enables you to format the text displayed in a dynamic menu item.
DynamicPopOutImageTextFormatString
—. Enables you to format the alt text displayed for the popout image.
DynamicPopOutImageUrl
—. Enables you to specify the URL for the dynamic popout image. (By default, a triangle is displayed.)
DynamicTopSeparatorImageUrl
—. Enables you to specify the URL to an image that appears above each dynamic menu item.
DynamicVerticalOffset
—. Enables you to specify the number of pixels that a dynamic menu item is shifted relative to its parent menu item.
ItemWrap
—. Enables you to specify whether the text in menu items should wrap.
MaximumDynamicDisplayLevels
—. Enables you to specify the maximum number of levels of dynamic menu items to display.
Orientation
—. Enables you to display a menu horizontally or vertically (the default value is Vertical).
ScollDownImageUrl
—. Enables you to specify the URL to an image that is displayed and that enables you to scroll down through menu items.
ScrollDownText
—. Enables you to specify alt text for the ScrollDown image.
ScrollUpImageUrl
—. Enables you to specify the URL to an image that is displayed and that enables you to scroll up through menu items.
ScrollUpText
—. Enables you to specify alt text for the ScrollUp image.
SkipLinkText
—. Enables you to modify the text displayed by the skip link. (The skip link enables blind users to skip past the contents of a menu.)
StaticBottomSeparatorImageUrl
—. Enables you to specify the URL to an image that appears below each static menu item.
StaticDisplayLevels
—. Enables you to specify the number of static levels of menu items to display.
StaticEnableDefaultPopOutImage
—. Enables you to disable the default popout image that indicates that a menu item has child menu items.
StaticItemFormatString
—. Enables you to format the text displayed in each static menu item.
StaticImagePopOutFormatString
—. Enables you to specify the alt text displayed by the popout image.
StaticPopOutImageUrl
—. Enables you to specify the URL for the popout image.
StaticSubMenuIndent
—. Enables you to specify the number of pixels that a static menu item is indented relative to its parent menu item.
StaticTopSeparatorImageUrl
—. Enables you to specify the URL to an image that appears above each static menu item.
Target
—. Enables you to specify the window in which a new page opens when you click a menu item.
This list includes several interesting properties. For example, notice that you can specify images for scrolling up and down through a list of menu items. These images appear when you constrain the height of either the static or dynamic menu.
The Menu
control also exposes several Style objects. You can use these Style objects as hooks to which you can attach Cascading Style Sheet classes:
DynamicHoverStyle
—. Style applied to a dynamic menu item when you hover your mouse over it.
DynamicMenuItemStyle
—. Style applied to each dynamic menu item.
DynamicMenuStyle
—. Style applied to the container tag for the dynamic menu.
DynamicSelectedStyle
—. Style applied to the selected dynamic menu item.
StaticHoverStyle
—. Style applied to a static menu item when you hover your mouse over it.
StaticMenuItemStyle
—. Style applied to each static menu item.
StaticMenuStyle
—. Style applied to the container tag for the static menu.
StaticSelectedStyle
—. Style applied to the selected static menu item.
Furthermore, you can apply styles to menu items based on their level in the menu. For example, you might want the font size to get progressively smaller depending on how deeply nested a menu item is within a menu. You can use three properties of the Menu
control to format menu items, depending on their level:
LevelMenuItemStyles
—. Contains a collection of MenuItemStyle
controls, which correspond to different menu levels.
LevelSelectedStyles
—. Contains a collection of MenuItemStyle
controls, which correspond to different menu levels of selected menu items.
LevelSubMenuStyles
—. Contains a collection of MenuItemStyle
controls, which correspond to different menu levels of static menu items.
For example, the page in Listing 17.15 illustrates how you can apply different formatting to menu items that appear at different menu levels (see Figure 17.12).
Example 17.15. MenuLevelStyles.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> .menuLevel1 { font:40px Arial,Sans-Serif; } .menuLevel2 { font:20px Arial,Sans-Serif; } .menuLevel3 { font:10px Arial,Sans-Serif; } </style> <title>Menu Level Styles</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" Runat="server"> <LevelMenuItemStyles> <asp:MenuItemStyle CssClass="menuLevel1" /> <asp:MenuItemStyle CssClass="menuLevel2" /> <asp:MenuItemStyle CssClass="menuLevel3" /> </LevelMenuItemStyles> <Items> <asp:MenuItem Text="Produce"> <asp:MenuItem Text="Apples" /> <asp:MenuItem Text="Oranges" /> </asp:MenuItem> <asp:MenuItem Text="Beverages"> <asp:MenuItem Text="Soda"> <asp:MenuItem Text="Coke" /> <asp:MenuItem Text="Pepsi" /> </asp:MenuItem> </asp:MenuItem> </Items> </asp:Menu> </div> </form> </body> </html>
The MenuItemStyle
controls are applied to the menu level that corresponds to their order of declaration. The first MenuItemStyle
is applied to the first menu level, the second MenuItemStyle
is applied to the second menu level, and so on.
Finally, the MenuItem
class itself includes several useful formatting properties:
ImageUrl
—. Enables you to specify the URL for an image that is displayed next to a menu item.
PopOutImageUrl
—. Enables you to specify the URL for an image that is displayed when a menu item contains child menu items.
SeparatorImageUrl
—. Enables you to specify the URL for an image that appears below a menu item.
Selectable
—. Enables you to prevent users from selecting (clicking) a menu item.
Selected
—. Enables you to specify whether a menu item is selected.
Target
—. Enables you to specify the name of the window that opens when you click a menu item.
For example, the page in Listing 17.16 displays a menu that resembles a traditional desktop application menu (see Figure 17.13).
Example 17.16. MenuDesktop.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> .staticMenuItem { color:black; border:solid 1px black; padding:2px 4px; } .menuHover { color:white; background-color:blue; } .dynamicMenuItem { color:black; padding:2px 4px; } .dynamicMenu { border:Solid 1px black; filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray', Positive='true')" } </style> <title>Menu Desktop</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" Orientation="Horizontal" StaticMenuItemStyle-CssClass="staticMenuItem" StaticHoverStyle-CssClass="menuHover" DynamicHoverStyle-CssClass="menuHover" DynamicMenuItemStyle-CssClass="dynamicMenuItem" DynamicMenuStyle-CssClass="dynamicMenu" Runat="server"> <Items> <asp:MenuItem Text="File" Selectable="false"> <asp:MenuItem Text="Save" /> <asp:MenuItem Text="Open" /> </asp:MenuItem> <asp:MenuItem Text="Format" Selectable="false"> <asp:MenuItem Text="Bold" ImageUrl="Images/Bold.gif" /> <asp:MenuItem Text="Italic" ImageUrl="Images/Italic.gif" /> <asp:MenuItem Text="Underline" ImageUrl="Images/Underline.gif" SeparatorImageUrl="Images/Divider.gif" /> <asp:MenuItem Text="Left Align" ImageUrl="Images/JustifyLeft.gif" /> <asp:MenuItem Text="Center Align" ImageUrl="Images/JustifyCenter.gif" /> <asp:MenuItem Text="Right Align" ImageUrl="Images/JustifyRight.gif" /> </asp:MenuItem> </Items> </asp:Menu> </div> </form> </body> </html>
The Menu
control supports templates. You can use templates to completely customize the appearance of the Menu
control.
The Menu
control supports the following two templates:
DynamicItemTemplate
—. Template applied to dynamic menu items.
StaticItemTemplate
—. Template applied to static menu items.
The page in Listing 17.17 uses both templates to display menu items. The templates display a count of child items for each menu item (see Figure 17.14).
Example 17.17. MenuTemplates.aspx
<%@ Page Language="VB" %> <!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) lblMessage.Text = Menu1.SelectedValue End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> .menuItem { color:black; border:Solid 1px Gray; background-color:#c9c9c9; padding:2px 5px; } </style> <title>Menu Templates</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" OnMenuItemClick="Menu1_MenuItemClick" Orientation="Horizontal" StaticMenuItemStyle-CssClass="menuItem" DynamicMenuItemStyle-CssClass="menuItem" Runat="server"> <StaticItemTemplate> <%# Eval("Text") %> (<%# Eval("ChildItems.Count") %>) </StaticItemTemplate> <DynamicItemTemplate> <%# Eval("Text") %> (<%# Eval("ChildItems.Count") %>) </DynamicItemTemplate> <Items> <asp:MenuItem Text="Produce"> <asp:MenuItem Text="Apples" /> <asp:MenuItem Text="Oranges" /> </asp:MenuItem> <asp:MenuItem Text="Beverages"> <asp:MenuItem Text="Soda"> <asp:MenuItem Text="Coke" /> <asp:MenuItem Text="Pepsi" /> </asp:MenuItem> </asp:MenuItem> </Items> </asp:Menu> <hr /> <asp:Label id="lblMessage" EnableViewState="false" Runat="server" /> </div> </form> </body> </html>
Notice that you do not need to create LinkButton controls in the templates. The content of the template is wrapped in a link automatically when it is appropriate.
The TreeView
control is very similar to the Menu
control. Like the Menu
control, you can use the TreeView
control to display hierarchical data. The TreeView
control binds to any data source that implements the IHierarchicalDataSource
or IHiearchicalEnumerable
interface.
In this section, you learn how to add items declaratively to the TreeView
control. You also learn how to bind a TreeView
control to hierarchical data sources such as the SiteMapDataSource
and XmlDataSource
controls.
You also see how you can use the TreeView
control with database data. A TreeView
is built programmatically from database data.
Finally, you learn how you can use AJAX with the TreeView
control to display large sets of data efficiently. By taking advantage of AJAX, you can update a TreeView
without posting a page back to the server.
A TreeView
control is made up of TreeNode
objects. You can build a TreeView
control by declaring TreeNode
objects in the TreeView
control’s Items collection.
For example, Listing 17.18 contains a TreeView
which renders a nested set of links to pages (see Figure 17.15).
Example 17.18. TreeViewDeclare.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>TreeView Declare</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TreeView id="TreeView1" Runat="server"> <Nodes> <asp:TreeNode Text="Home" NavigateUrl="~/Default.aspx"> <asp:TreeNode Text="Products"> <asp:TreeNode Text="First Product" NavigateUrl="~/Products/FirstProduct.aspx" /> <asp:TreeNode Text="Second Product" NavigateUrl="~/Products/SecondProduct.aspx" /> </asp:TreeNode> <asp:TreeNode Text="Services"> <asp:TreeNode Text="First Service" NavigateUrl="~/Services/FirstService.aspx" /> <asp:TreeNode Text="Second Service" NavigateUrl="~/Services/SecondService.aspx" /> </asp:TreeNode> </asp:TreeNode> </Nodes> </asp:TreeView> </div> </form> </body> </html>
Some of the TreeNode
s in Listing 17.18 include a Text
property, and some of the TreeNode
s include both a Text
and NavigateUrl
property. You can click the TreeNode
s that include a NavigateUrl
property to link to a new page.
You also can associate a Value
property with a TreeNode
. This is useful when you want to post back to the same page. For example, the page in Listing 17.19 enables you to display the value of the selected TreeNode
in a Label control (see Figure 17.16).
Example 17.19. TreeViewValue.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> Protected Sub TreeView1_SelectedNodeChanged(ByVal sender As Object, ByVal e As EventArgs) lblMessage.Text = TreeView1.SelectedValue End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> html { background-color:silver; } .content { float:left; width:350px; height:500px; padding:20px; margin:10px; background-color:white; } </style> <title>TreeView Value</title> </head> <body> <form id="form1" runat="server"> <div class="content"> <asp:TreeView id="TreeView1" OnSelectedNodeChanged="TreeView1_SelectedNodeChanged" Runat="server" > <Nodes> <asp:TreeNode Text="Home" Value="Home"> <asp:TreeNode Text="Products"> <asp:TreeNode Text="First Product" Value="FirstProduct" /> <asp:TreeNode Text="Second Product" Value="SecondProduct" /> </asp:TreeNode> <asp:TreeNode Text="Services"> <asp:TreeNode Text="First Service" Value="FirstService" /> <asp:TreeNode Text="Second Service" Value="SecondService" /> </asp:TreeNode> </asp:TreeNode> </Nodes> </asp:TreeView> </div> <div class="content"> You selected: <asp:Label id="lblMessage" EnableViewState="false" Runat="server" /> </div> </form> </body> </html>
Notice that the page in Listing 17.19 includes a SelectedNodeChanged
event handler. When you select a new node, the SelectedNodeChanged
event handler displays the value of the selected TreeNode
in a Label control.
You can display check boxes next to each node in a TreeView
control by assigning a value to the ShowCheckBoxes
property. This property accepts the following values:
You can use a bitwise combination of these values when specifying the nodes to display with check boxes.
The page in Listing 17.20 illustrates the ShowCheckBoxes
property (see Figure 17.17).
Example 17.20. TreeViewCheckBoxes.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> Protected Sub btnSubscribe_Click(ByVal sender As Object, ByVal e As EventArgs) For Each node As TreeNode In TreeView1.CheckedNodes bltSubscribed.Items.Add(node.Text) Next End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>TreeView CheckBoxes</title> </head> <body> <form id="form1" runat="server"> <div> Select the Newsgroups which you would like to join: <br /> <asp:TreeView id="TreeView1" ShowCheckBoxes="Leaf" Runat="server"> <Nodes> <asp:TreeNode Text="Programming"> <asp:TreeNode Text="ASP.NET" /> <asp:TreeNode Text="JAVA" /> <asp:TreeNode Text="Cold Fusion" /> </asp:TreeNode> <asp:TreeNode Text="Sports"> <asp:TreeNode Text="Baseball" /> <asp:TreeNode Text="Hockey" /> <asp:TreeNode Text="Football" /> </asp:TreeNode> </Nodes> </asp:TreeView> <br /> <asp:Button id="btnSubscribe" Text="Subscribe" OnClick="btnSubscribe_Click" Runat="server" /> <hr /> You selected: <asp:BulletedList id="bltSubscribed" EnableViewState="false" Runat="server" /> </div> </form> </body> </html>
The page in Listing 17.20 displays nested newsgroups. You can subscribe to the newsgroups by clicking the Subscribe button.
When you click the Subscribe button, the CheckedNodes
property is used to return a list of all of the checked TreeNode
s. This list is displayed in a BulletedList control.
You can use a TreeView
control as a navigation element in your pages by binding the TreeView
to a Site Map. The page in Listing 17.21 demonstrates how you can bind a TreeView
to a SiteMapDataSource
control (see Figure 17.18).
Example 17.21. UsingTreeView/TreeViewSiteMap.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>TreeView Site Map</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TreeView id="TreeView1" DataSourceID="srcSiteMap" Runat="server" /> <asp:SiteMapDataSource id="srcSiteMap" Runat="server" /> </div> </form> </body> </html>
When you open the page in Listing 17.21, all the nodes from the Site Map are displayed automatically in the TreeView
control. By default, the SiteMapDataSource
uses the XmlSiteMapProvider
, which represents a file named Web.sitemap
located at the root of an application.
You can add a TreeView
and SiteMapDataSource
control to a Master Page to show the TreeView
in multiple pages. To learn more about Master Pages, see Chapter 5, “Designing Websites with Master Pages.”
Because an XmlDataSource
control returns hierarchical data, you can bind a TreeView
directly to an XmlDataSource
. For example, imagine that you need to display the XML document contained in Listing 17.22.
The page in Listing 17.23 illustrates how you can display the contents of this XML document with a TreeView
control.
Example 17.23. TreeViewXml.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>TreeView XML</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TreeView id="TreeView1" DataSourceID="srcMovies" Runat="server" /> <asp:XmlDataSource id="srcMovies" DataFile="~/Movies.xml" Runat="server" /> </div> </form> </body> </html>
The Movies.xml
document in Listing 17.22 is extremely simple. The elements do not include any attributes. You can display more complicated XML documents with the TreeView
control by declaring one or more TreeNodeBinding
elements.
For example, the nodes in the XML document in Listing 17.24 include id
and text
attributes.
Example 17.24. MoviesComplex.xml
<?xml version="1.0" encoding="utf-8" ?> <movies> <category id="category1" text="Action"> <movie id="movie1" text="Star Wars" /> <movie id="movie2" text="Independence Day" /> </category> <category id="category2" text="Horror"> <movie id="movie3" text="Jaws" /> <movie id="movie4" text="Nightmare Before Christmas" /> </category> </movies>
The page in Listing 17.25 displays the contents of the XML document in Listing 17.24.
Example 17.25. TreeViewXMLComplex.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>TreeView XML Complex</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TreeView id="TreeView1" DataSourceID="srcMovies" Runat="server"> <DataBindings> <asp:TreeNodeBinding DataMember="category" TextField="text" ValueField="id" /> <asp:TreeNodeBinding DataMember="movie" TextField="text" ValueField="id" /> </DataBindings> </asp:TreeView> <asp:XmlDataSource id="srcMovies" DataFile="~/MoviesComplex.xml" Runat="server" /> </div> </form> </body> </html>
The TreeView
in Listing 17.25 includes a DataBindings
subtag. This tag includes two TreeNodeBinding
elements. The first TreeNodeBinding
specifies the relationship between <category>
nodes in the XML document and TreeView
nodes. The second TreeNodeBinding
specifies the relationship between <movie>
nodes and TreeView
nodes.
You cannot bind a TreeView
control directly to a SqlDataSource
or ObjectDataSource
control because neither of these two controls expose hierarchical data. If you want to display database data with the TreeView
control then you have a choice: create a custom SqlHierarchicalDataSource
control or programmatically bind the TreeView
to the database data.
The hard option is to build a SQL hierarchical DataSource
control. You can do this by deriving a new control from the base HierarchicalDataSourceControl
class or by implementing the IHierarchicalDataSource
interface. We explore this option in the final section of this chapter.
The second option is to build the TreeView
control programmatically from a set of database records. This is the approach that we will follow in this section.
Imagine that you have a database table that looks like this:
MessageId | ParentId | Subject |
---|---|---|
1 | null | How do you use the |
2 | null | What is the |
3 | 1 | RE:How do you use the |
4 | 1 | RE:How do you use the |
5 | 2 | RE:What is the |
6 | 5 | RE:RE:What is the |
This database table represents a discussion forum. The relationship between the messages is determined by the ParentId
column. The messages that have a null ParentID
represent the threads, and the other messages represent replies to the threads.
The page in Listing 17.26 uses a TreeView
control to display the contents of the Discuss database table (see Figure 17.19).
Example 17.26. TreeViewDatabase.aspx
<%@ Page Language="VB" %> <%@ Import Namespace="System.Web.Configuration" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> ''' <summary> ''' Only populate the TreeView when the page first loads ''' </summary> Private Sub Page_Load() If Not Page.IsPostBack Then PopulateTreeView() End If End Sub ''' <summary> ''' Get the data from the database and create the top-level ''' TreeView items ''' </summary> Private Sub PopulateTreeView() Dim treeViewData As DataTable = GetTreeViewData() AddTopTreeViewNodes(treeViewData) End Sub ''' <summary> ''' Use a DataAdapter and DataTable to grab the database data ''' </summary> ''' <returns></returns> Private Function GetTreeViewData() As DataTable ' Get Discuss table Dim selectCommand As String = "SELECT MessageId,ParentId,Subject FROM Discuss" Dim conString As String = WebConfigurationManager.ConnectionStrings("Discuss").ConnectionString Dim dad As SqlDataAdapter = New SqlDataAdapter(selectCommand, conString) Dim dtblDiscuss As DataTable = New DataTable() dad.Fill(dtblDiscuss) Return dtblDiscuss End Function ''' <summary> ''' Filter the data to get only the rows that have a ''' null ParentID (these are the top-level TreeView items) ''' </summary> Private Sub AddTopTreeViewNodes(ByVal treeViewData As DataTable) Dim view As DataView = New DataView(treeViewData) view.RowFilter = "ParentID IS NULL" Dim row As DataRowView For Each row In view Dim NewNode As TreeNode = New TreeNode(row("Subject").ToString(), row("MessageId").ToString()) TreeView1.Nodes.Add(NewNode) AddChildTreeViewNodes(treeViewData, NewNode) Next End Sub ''' <summary> ''' Recursively add child TreeView items by filtering by ParentID ''' </summary> Private Sub AddChildTreeViewNodes(ByVal treeViewData As DataTable, ByVal parentTreeViewNode As TreeNode) Dim view As DataView = New DataView(treeViewData) view.RowFilter = "ParentID=" + parentTreeViewNode.Value Dim row As DataRowView For Each row In view Dim NewNode As TreeNode = New TreeNode(row("Subject").ToString(), row("MessageId").ToString()) parentTreeViewNode.ChildNodes.Add(NewNode) AddChildTreeViewNodes(treeViewData, NewNode) Next End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> </style> <title>TreeView Database</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TreeView id="TreeView1" Runat="server" /> </div> </form> </body> </html>
The page in Listing 17.26 filters the contents of the Discuss database table by its ParentID column. First, the top-level nodes are added to the TreeView
. Next, the child nodes are recursively added to the TreeView
with the help of the AddChildTreeViewNodes()
method.
You can use the TreeView
control even when working with a large set of data. For example, the Microsoft MSDN website (msdn.Microsoft.com) has links to thousands of articles. This website uses a tree view to display the nested links to the articles.
Because thousands of articles are hosted at the MSDN website, not all the tree nodes are downloaded to the browser when you open a page. Instead, additional nodes are downloaded to your browser only when you expand a particular node.
You can use a feature named Populate On Demand with the TreeView
control. When you enable the PopulateOnDemand
property for a Tree node, child nodes are not added to the parent node until the parent node is expanded.
For example, the page in Listing 17.27 contains an infinitely expanding TreeView
. Each time you expand a Tree node, five new child nodes are displayed. Each time you expand a child node, five more child nodes are displayed, and so on (see Figure 17.20).
Example 17.27. TreeView
PopulateOnDemand.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> Private Sub TreeView1_TreeNodePopulate(ByVal s As Object, ByVal e As TreeNodeEventArgs) For i As Integer = 0 To 4 Dim NewNode As New TreeNode() NewNode.Text = String.Format("{0}.{1}", e.Node.Text, i) NewNode.PopulateOnDemand = True e.Node.ChildNodes.Add(NewNode) Next End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>TreeView Populate On Demand</title> </head> <body> <form id="form1" runat="server"> <div> <%=DateTime.Now.ToString("T") %> <hr /> <asp:TreeView ID="TreeView1" ExpandDepth="0" OnTreeNodePopulate="TreeView1_TreeNodePopulate" Runat="server"> <Nodes> <asp:TreeNode PopulateOnDemand="true" Text="Node 0" /> </Nodes> </asp:TreeView> </div> </form> </body> </html>
The TreeView
in Listing 17.27 includes a single statically declared TreeNode
. Notice that this TreeNode
includes a PopulateOnDemand
property that is set to the value True
.
Additionally, the TreeView
control itself includes a TreeNodePopulate
event handler. When you expand a TreeNode
that has its PopulateOnDemand
property enabled, the TreeNodePopulate
event handler executes. In the case of Listing 17.27, the event handler adds five new TreeNode
s to the TreeNode
that was expanded.
When you use the Populate On Demand feature with a modern browser (Internet Explorer 6, Firefox 1, Opera 8), the page containing the TreeView
is not posted back to the server when you expand a TreeNode
. Instead, the browser uses AJAX (Asynchronous JavaScript and XML) to communicate with the web server. The additional TreeNode
s are retrieved from the server, without performing a postback.
The page in Listing 17.27 displays the current time when you open the page. Notice that the time is not updated when you expand a particular TreeNode
. The time is not updated because the only content in the page that is updated when you expand a node is the TreeView
content. AJAX can have a dramatic impact on performance because it does not require the entire page to be re-rendered each time you expand a TreeNode
.
If, for some reason, you don’t want to use AJAX with Populate On Demand, you can assign the value False
to the TreeView
control’s PopulateNodesFromClient
property.
The page in Listing 17.28 contains a more realistic sample of using Populate On Demand and AJAX. This page uses a TreeView
control to display the contents of the Discuss database table (see Figure 17.21).
Example 17.28. TreeViewAJAX.aspx
<%@ Page Language="VB" %> <%@ Import Namespace="System.Web.Configuration" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> ''' <summary> ''' Only populate the TreeView when the page first loads ''' </summary> Private Sub Page_Load() If Not Page.IsPostBack Then PopulateTopNodes() End If End Sub ''' <summary> ''' Get the top-level nodes (nodes with a null ParentId) ''' </summary> Private Sub PopulateTopNodes() Dim selectCommand As String = "SELECT MessageId,ParentId,Subject FROM Discuss WHERE ParentId IS NULL" Dim conString As String = WebConfigurationManager.ConnectionStrings("Discuss").ConnectionString Dim dad As New SqlDataAdapter(selectCommand, conString) Dim dtblMessages As New DataTable() dad.Fill(dtblMessages) For Each row As DataRow In dtblMessages.Rows Dim NewNode As New TreeNode(row("Subject").ToString(), row("MessageId").ToString()) NewNode.PopulateOnDemand = True TreeView1.Nodes.Add(NewNode) Next End Sub ''' <summary> ''' Get the child nodes of the expanded node ''' </summary> Protected Sub TreeView1_TreeNodePopulate(ByVal sender As Object, ByVal e As TreeNodeEventArgs) Dim selectCommand As String = "SELECT MessageId,ParentId,Subject FROM Discuss WHERE ParentId=@ParentId" Dim conString As String = WebConfigurationManager.ConnectionStrings("Discuss").ConnectionString Dim dad As New SqlDataAdapter(selectCommand, conString) dad.SelectCommand.Parameters.AddWithValue("@ParentId", e.Node.Value) Dim dtblMessages As New DataTable() dad.Fill(dtblMessages) For Each row As DataRow In dtblMessages.Rows Dim NewNode As New TreeNode(row("Subject").ToString(), row("MessageId").ToString()) NewNode.PopulateOnDemand = True e.Node.ChildNodes.Add(NewNode) Next End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> </style> <title>TreeView AJAX</title> </head> <body> <form id="form1" runat="server"> <div> <%= DateTime.Now.ToString("T") %> <hr /> <asp:TreeView id="TreeView1" ExpandDepth="0" OnTreeNodePopulate="TreeView1_TreeNodePopulate" Runat="server" /> </div> </form> </body> </html>
When the page in Listing 17.28 first opens, only the first-level message subjects are displayed. These messages are retrieved by the PopulateTopNodes()
method.
When you expand a thread, the matching replies are retrieved for the thread. These replies are retrieved in the TreeView1_TreeNodePopulate()
event handler.
The TreeView
in Listing 17.28 performs well even when working with a large set of data. At any time, only the child messages of a message are retrieved from the database. At no time are all the messages retrieved from the database.
When the page is used with a modern browser, AJAX is used to retrieve the messages from the web server. The page does not need to be posted back to the web server when you expand a particular message thread.
The TreeView
control supports an abundance of properties that have an effect on how the TreeView
is formatted.
Here are some of the more useful properties of a TreeView
control, which modify its appearance (this is not a complete list):
CollapseImageToolTip
—. Enables you to specify the title attribute for the collapse image.
CollapseImageUrl
—. Enables you to specify a URL to an image for the collapse image.
ExpandDepth
—. Enables you to specify the number of TreeNode
levels to display initially.
ExpandImageToolTip
—. Enables you to specify the title attribute for the expand image.
ExpandImageUrl
—. Enables you to specify the URL to an image for the expand image.
ImageSet
—. Enables you to specify a set of images to use with the TreeView
control.
LineImagesFolder
—. Enables you to specify a folder that contains line images.
MaxDataBindDepth
—. Enables you to specify the maximum levels of TreeView
levels to display when binding to a data source.
NodeIndent
—. Enables you to specify the number of pixels to indent a child Tree node.
NodeWrap
—. Enables you to specify whether or not text is wrapped in a Tree node.
NoExpandImageUrl
—. Enables you to specify the URL to an image for the NoExpand
image (typically, an invisible spacer image).
ShowCheckBoxes
—. Enables you to display check boxes next to each Tree node. Possible values are All
, Leaf
, None
, Parent
, and Root
.
ShowExpandCollapse
—. Enables you to disable the expand and collapse icons that appear next to each expandable node.
ShowLines
—. Enables you to show connecting lines between Tree nodes.
SkipLinkText
—. Enables you to specify the text used for skipping the contents of the TreeView
control. (The Skip Link contains hidden text that is accessible only to users of assistive devices.)
Target
—. Enables you to specify the name of the window that opens when you navigate to a URL with the TreeView
control.
The two most interesting properties in this list are the ImageSet
and the ShowLines
properties. You can set the ImageSet
property to any of the following values to modify the images displayed by the TreeView
control:
Arrows
BulletedList
BulletedList2
BulletedList3
BulletedList4
Contacts
Custom
Events
Faq
Inbox
Msdn
News
Simple
Simple2
WindowsHelp
XPFileExplorer
The ShowLines
property causes connecting line images to be rendered between TreeView
nodes. Displaying lines between Tree nodes can make it easier to visually discern the nested relationships between nodes. If you want to create custom lines, you can specify a value for the LinesImagesFolder
property.
Visual Web Developer includes a TreeView
Line Image Generator that enables you to create custom connecting lines. You can open this tool in Design view by selecting the TreeView
control and opening the Tasks dialog box and selecting Customize Line Images.
The page in Listing 17.29 illustrates how to use both the ImageSet
and ShowLines
properties (see Figure 17.22).
Example 17.29. TreeViewImageSet.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>TreeView ImageSet</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TreeView id="TreeView1" ImageSet="XPFileExplorer" ShowLines="true" Runat="server"> <Nodes> <asp:TreeNode Text="Home"> <asp:TreeNode Text="Products"> <asp:TreeNode Text="First Product" /> <asp:TreeNode Text="Second Product" /> </asp:TreeNode> <asp:TreeNode Text="Services"> <asp:TreeNode Text="First Service" /> <asp:TreeNode Text="Second Service" /> </asp:TreeNode> </asp:TreeNode> </Nodes> </asp:TreeView> </div> </form> </body> </html>
The TreeNode
object itself also supports several properties that have an effect on the appearance of its containing TreeView
. Here is a list of the most useful properties of the TreeNode
object:
Checked
—. Enables you to check the check box that appears next to the Tree node.
Expanded
—. Enables you to initially expand a node.
ImageToolTip
—. Enables you to associate alt text with a Tree node image.
ImageUrl
—. Enables you to specify an image that appears next to a Tree node.
NavigateUrl
—. Enables you to specify the URL to which the current Tree node links.
SelectAction
—. Enables you to specify the action that occurs when you click a Tree node. Possible values are Expand
, None
, Select
, or SelectExpand
.
Selected
—. Enables you to specify whether the current Tree node is selected.
ShowCheckBox
—. Enables you to display a check box for the current Tree node.
Target
—. Enables you to specify the name of the window that opens when you navigate to a URL.
ToolTip
—. Enables you to specify a title attribute for the current Tree node.
You can style the TreeView
control by attaching Cascading Style Sheet classes to the Style
object exposed by the TreeView
control. The TreeView
control supports the following Style
objects:
HoverNodeStyle
—. Style applied to a Tree node when you hover your mouse over a node.
LeafNodeStyle
—. Style applied to leaf Tree nodes (Tree nodes without child nodes).
NodeStyle
—. Style applied to Tree nodes by default.
ParentNodeStyle
—. Style applied to parent nodes (Tree nodes with child nodes).
RootNodeStyle
—. Style applied to root nodes (Tree nodes with no parent nodes).
SelectedNodeStyle
—. Style applied to the selected node.
For example, the page in Listing 17.30 uses several of these Style objects to format a TreeView
control (see Figure 17.23).
Example 17.30. TreeViewStyles.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> .treeNode { color:blue; font:14px Arial, Sans-Serif; } .rootNode { font-size:18px; width:100%; border-bottom:Solid 1px black; } .leafNode { border:Dotted 2px black; padding:4px; background-color:#eeeeee; font-weight:bold; } </style> <title>TreeView Styles</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TreeView id="TreeView1" NodeStyle-CssClass="treeNode" RootNodeStyle-CssClass="rootNode" LeafNodeStyle-CssClass="leafNode" Runat="server"> <Nodes> <asp:TreeNode Text="Home"> <asp:TreeNode Text="Products"> <asp:TreeNode Text="First Product" /> <asp:TreeNode Text="Second Product" /> </asp:TreeNode> <asp:TreeNode Text="Services"> <asp:TreeNode Text="First Service" /> <asp:TreeNode Text="Second Service" /> </asp:TreeNode> </asp:TreeNode> </Nodes> </asp:TreeView> </div> </form> </body> </html>
Furthermore, you can apply styles to particular Tree node levels by taking advantage of the TreeView
control’s LevelStyles
property. The page in Listing 17.31 uses the LevelStyles
property to format first level nodes differently than second level nodes and third level nodes (see Figure 17.24).
Example 17.31. TreeViewLevelStyles.aspx
<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> .nodeLevel1 { font:40px Arial,Sans-Serif; } .nodeLevel2 { font:20px Arial,Sans-Serif; } .nodeLevel3 { font:10px Arial,Sans-Serif; } </style> <title>TreeView Level Styles</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TreeView id="TreeView1" Runat="server"> <LevelStyles> <asp:TreeNodeStyle CssClass="nodeLevel1" /> <asp:TreeNodeStyle CssClass="nodeLevel2" /> <asp:TreeNodeStyle CssClass="nodeLevel3" /> </LevelStyles> <Nodes> <asp:TreeNode Text="Home"> <asp:TreeNode Text="Products"> <asp:TreeNode Text="First Product" /> <asp:TreeNode Text="Second Product" /> </asp:TreeNode> <asp:TreeNode Text="Services"> <asp:TreeNode Text="First Service" /> <asp:TreeNode Text="Second Service" /> </asp:TreeNode> </asp:TreeNode> </Nodes> </asp:TreeView> </div> </form> </body> </html>
In this final section of this chapter, we build a SqlHierarchicalDataSource
control. This custom control enables you to declaratively and (thus) easily bind controls such as the Menu
and TreeView
controls to data retrieved from a database.
The code samples in this section can be found in the SqlHierarchicalDataSourceVB
and SqlHierarchicalDataSourceCS
applications on the CD.
The page in Listing 17.32 illustrates how you can use the SqlHierarchicalDataSource
control to bind a Menu
control to a database table that contains nested categories.
Example 17.32. ShowMenu.aspx
<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="AspNetUnleashed" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> Sub Menu1_MenuItemClick(sender As Object, e As MenuEventArgs) lblSelected.Text = Menu1.SelectedValue End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <style type="text/css"> .menu { border:solid 1px black; padding:4px; } </style> <title>Show Menu</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Menu id="Menu1" DataSourceId="srcCategories" OnMenuItemClick="Menu1_MenuItemClick" Orientation="Horizontal" DynamicMenuStyle-CssClass="menu" Runat="server"> <DataBindings> <asp:MenuItemBinding TextField="Name" ValueField="Name" /> </DataBindings> </asp:Menu> <custom:SqlHierarchicalDataSource id="srcCategories" ConnectionString='<%$ ConnectionStrings:Categories %>' DataKeyName="CategoryId" DataParentKeyName="ParentId" SelectCommand="SELECT CategoryId, ParentId, Name FROM Categories" Runat="server" /> <hr /> <asp:Label id="lblSelected" Runat="server" /> </div> </form> </body> </html>
When you open the page in Listing 17.32, all the rows from the Categories table are displayed in the Menu
control.
Notice that the SqlHierarchicalDataSource
control includes two properties: DataKeyName
and DataParentKeyName
. The DataKeyName
property represents the name of a database column that contains a unique value for each database table row. The DataParentKeyName
column represents the name of a database column that relates each row to its parent row.
Furthermore, notice that the Menu
control includes a MenuItemBinding
, which associates the database Name column with the Menu
item Text
property, and the Name column with the Menu
item Value
property.
You also can use the SqlHierarchicalDataSource
control when working with the TreeView
control. The page in Listing 17.33 displays all the rows from the Discuss database table in a TreeView
control.
Example 17.33. ShowTreeView.aspx
<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="AspNetUnleashed" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> Sub TreeView1_SelectedNodeChanged(sender As object, e As EventArgs) lblSelected.Text = TreeView1.SelectedValue End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>Show TreeView</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TreeView id="TreeView1" DataSourceID="srcDiscuss" OnSelectedNodeChanged="TreeView1_SelectedNodeChanged" ImageSet="News" Runat="server"> <DataBindings> <asp:TreeNodeBinding TextField="Subject" ValueField="MessageId" /> </DataBindings> </asp:TreeView> <custom:SqlHierarchicalDataSource id="srcDiscuss" ConnectionString='<%$ ConnectionStrings:Discuss %>' DataKeyName="MessageId" DataParentKeyName="ParentId" SelectCommand="SELECT MessageId,ParentId,Subject FROM Discuss" Runat="server" /> <hr /> You selected message number: <asp:Label id="lblSelected" Runat="server" /> </div> </form> </body> </html>
When you open the page in Listing 17.33, the contents of the Discuss database table are displayed in the TreeView
control.
All the code for the SqlHierarchicalDataSource
control is included on the CD that accompanies this book. The control is composed out of five separate classes:
SqlHierarchicalDataSource
—. This class represents the actual control. It inherits from the base SqlDataSource
control and implements the IHierarchicalDataSource
interface.
SqlHierarchicalDataSourceView
—. This class represents the hierarchical data returned by the control. It inherits from the base HierarchicalDataSourceView
class.
SqlHierarchicalEnumerable
—. This class represents a collection of SqlNodes
.
SqlNode
—. This class represents a particular database row from the data source. It includes methods for retrieving child and parent rows.
SqlNodePropertyDescriptor
—. This class inherits from the base PropertyDescriptor
class. It converts the database columns represented by a SqlNode
into class properties so that you can bind to the columns using TreeView
and Menu
control DataBindings.
In this chapter, you learned how to use the SiteMapPath
, Menu
, and TreeView
Controls. First, you learned how to use the SiteMapPath
control to display a breadcrumb trail. You learned how to format the SiteMapPath
control with styles and templates.
Next, you explored the Menu
control. You learned how to create both vertical and horizontal menus. You also learned how you can bind a Menu
control to different data sources such as Site Maps, XML documents, and database data.
The TreeView
control was also discussed. You learned how to display check boxes with a TreeView
control. You also learned how to bind a TreeView
control to different data sources such as Site Maps, XML documents, and database data. You also learned how to display a large set of Tree nodes efficiently by using AJAX and the TreeView
control.
Finally, we created a custom SqlHierarchicalDataSource
control that enables you to easily bind controls such as the Menu
and TreeView
controls to hierarchical database data.