Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
588C H A P T E R 1 6 ■ W E B S I T E N AV I G AT I O N
is true (the default), the TreeView performs a client-side callback to retrieve the nodes it needs from your event, without posting back the entire page. If PopulateNodesFromClient is false, or if it’s true but the TreeView detects that the current browser doesn’t appear to support client callbacks, the TreeView triggers a normal postback to get the same result. The only difference is that the entire page will be refreshed in the browser, generating a less seamless interface. (It also allows other page events to fire, such as control change events.)
■Note Chapter 29 has more information about how client callbacks work and how you can use them directly. However, the TreeView support is particularly nice because it hides the underlying model, allowing you to write an ordinary .NET event handler.
You can use the populate-on-demand feature with the previous example. Instead of filling the whole tree when the page loads, you would begin by adding just the category nodes and setting them to populate on demand:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DataTable dtCategories = GetCategories();
// Loop through the category records. foreach (DataRow row in dtCategories.Rows)
{
TreeNode nodeCategory = new TreeNode( row["CategoryName"].ToString(), row["CategoryID"].ToString());
//Use the populate-on-demand feature for this
//node's children. nodeCategory.PopulateOnDemand = true;
//Make sure the node is collapsed at first,
//so it's not populated immediately. nodeCategory.Collapse(); TreeView1.Nodes.Add(nodeCategory);
}
}
}
Now you need to react to the TreeNodePopulate event to fill a category when it’s expanded. In this example, only the on-populate nodes are categories. However, if there were several types, you would check the TreeNode.Depth to determine what type of node is being expanded.
protected void TreeView1_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
int categoryID = Int32.Parse(e.Node.Value); DataTable dtProducts = GetProducts(categoryID);
// Loop through the product records. foreach (DataRow row in dtProducts.Rows)
{
//Use the constructor that requires just text
//and a nondisplayed value.
C H A P T E R 1 6 ■ W E B S I T E N AV I G AT I O N |
589 |
TreeNode nodeProduct = new TreeNode( row["ProductName"].ToString(), row["ProductID"].ToString());
e.Node.ChildNodes.Add(nodeProduct);
}
}
A given node is populated on-demand only once. After that, the values remain available on the client, and no callback is performed if the same node is collapsed and expanded.
■Note The client-side callback feature can be tampered with, just like any posted data. For example, a malicious user could create a page that requests you to fill in an arbitrary CategoryID, even if you haven’t added the category to the tree. If you are displaying a subset of data from a table that contains sensitive information, you should validate that the node is allowed before populating it.
TreeView Styles
The TreeView has a fine-grained style model that lets you completely control its appearance. Each style applies to a type of node. Styles are represented by the TreeNodeStyle class, which derives from the more conventional Style class.
As with other rich controls, the styles give you options to set background and foreground colors, fonts, and borders. Additionally, the TreeNodeStyle class adds the node-specific style properties shown in Table 16-10. These properties deal with the node image and the spacing around a node.
Table 16-10. TreeNodeStyle Added Properties
Property |
Description |
ImageUrl |
The URL for the image shown next to the node |
NodeSpacing |
The space (in pixels) between the current node and the node above and |
|
below |
VerticalPadding |
The space (in pixels) between the top and bottom of the node text and |
|
border around the text |
HorizontalPadding |
The space (in pixels) between the left and right of the node text and |
|
border around the text |
ChildNodesPadding |
The space (in pixels) between the last child node of an expanded parent |
|
node and the following sibling node |
|
|
■Tip The TreeView does not support associating two pictures with a node, one for the collapsed state and one for the expanded state. However, you can change the appearance of the collapse/expand buttons rendered next to every node.
Because a TreeView is rendered using an HTML table, you can set the padding of various elements to control the spacing around text, between nodes, and so on. One other property that comes into play is TreeView.NodeIndent, which sets the number of pixels of indentation (from the left) in each subsequent level of the tree hierarchy. Figure 16-16 shows how these settings apply to a single node.
590 |
C H A P T E R 1 6 ■ W E B S I T E N AV I G AT I O N |
Figure 16-16. Node spacing
The TreeView also allows you to configure some of its internal rendering through higher-level properties. You can turn off the node lines in a tree using the TreeView.ShowExpandCollapse property. You can also use the CollapseImageUrl and ExpandImageUrl properties to set the expanded and collapsed indicators of the TreeView (usually represented by plus and minus icons) and the BlankImageUrl property to set what’s displayed next to nodes that have no children. Finally, you can show check boxes next to every node (set TreeView.ShowCheckBoxes to true) or individual nodes (see TreeNode.ShowCheckBox). You can determine if a given node is checked by examining the TreeNode.Checked property.
Applying Styles to Node Types
The TreeView allows you to individually control the styles for different types of nodes—for example, root nodes, nodes that contain other nodes, selected nodes, and so on.
To apply node style settings to all the nodes of a tree, you can use the TreeView.NodeStyle property. You can isolate individual regions of the TreeView using a more specific style, as listed in Table 16-11.
C H A P T E R 1 6 ■ W E B S I T E N AV I G AT I O N |
591 |
Table 16-11. TreeView Style Properties
Property |
Description |
NodeStyle |
Applies to all nodes. |
RootNodeStyle |
Applies only to the first-level (root) nodes. |
ParentNodeStyle |
Applies to any node that contains other nodes, except root nodes. |
LeafNodeStyle |
Applies to any node that doesn’t contain child nodes and isn’t a root node. |
SelectedNodeStyle |
Applies to the currently selected node. |
HoverNodeStyle |
Applies to the node the user is hovering over with the mouse. These |
|
settings are applied only in up-level clients that support the necessary |
|
dynamic script. |
|
|
Styles are listed in this table in order of most general to most specific. That means the SelectedNodeStyle style settings override any conflicting settings in a RootNodeStyle, for example. (If you don’t want a node to be selectable, set the TreeNode.SelectAction to None.) However, the RootNodeStyle, ParentNodeStyle, and LeafNodeStyle settings never conflict, because the definitions for root, parent, and leaf nodes are mutually exclusive. You can’t have a node that is simultaneously a parent and a root node, for example—the TreeView simply designates this as a root node.
Applying Styles to Node Levels
Being able to apply styles to different types of nodes is interesting, but a more useful feature is being able to apply styles based on the node level. That’s because most trees use a rigid hierarchy (for example, the first level of nodes represents categories, the second level represents products, the third represents orders, and so on). In this case, it’s not so important to determine whether a node has children. Instead, it’s important to determine the node’s depth.
The only problem is that a TreeView can have a theoretically unlimited number of node levels. Thus, it doesn’t make sense to expose properties such as FirstLevelStyle, SecondLevelStyle, and so on. Instead, the TreeView has a LevelStyles collection that can have as many entries as you want. The level is inferred from the position of the style in the collection, so the first entry is considered the root level, the second entry is the second node level, and so on. For this system to work, you must follow the same order, and you must include an empty style placeholder if you want to skip a level without changing the formatting.
For example, here’s a TreeView that doesn’t use any indenting but instead differentiates levels by applying different amounts of spacing and different fonts:
<asp:TreeView runat="server" HoverNodeStyle-Font-Underline="true" ShowExpandCollapse="false" NodeIndent="0">
<LevelStyles>
<asp:TreeNodeStyle ChildNodesPadding="10" Font-Bold Font-Size="12pt" ForeColor="DarkGreen"/>
<asp:TreeNodeStyle ChildNodesPadding="5" Font-Bold Font-Size="10pt" /> <asp:TreeNodeStyle ChildNodesPadding="5" Font-UnderLine Font-Size="10pt" />
</LevelStyles>
...
</asp:TreeView>
If you apply this to the category and product list shown in earlier examples, you’ll see a page like the one shown in Figure 16-17.
592 C H A P T E R 1 6 ■ W E B S I T E N AV I G AT I O N
Figure 16-17. A nonindented TreeView
TreeView Themes
Using the right combination of style settings can dramatically transform your TreeView. However, for those less artistically inclined (or those who don’t have the right set of images handy), it’s comforting to know that Microsoft has made many classic designs available in a skin file. This skin file includes formatting settings and links to graphics that allow you to implement many common TreeView designs. Using these themes, you can easily adapt the TreeView to display anything from logged errors to an MSN Messenger contact list.
As with any skin file, you can apply these settings to a TreeView simply by attaching the skin file to the page and setting the TreeView.SkinID property to the skin you want to use. (See Chapter 15 for the full details.) Visual Studio makes this even easier—just click the Auto Format link in the smart tag, and you’ll be able to choose from one of several built-in skins. Figure 16-18 shows some of your options.
Menu Control
The new ASP.NET 2.0 Menu control is another rich control that supports hierarchical data. Like the TreeView, you can bind the Menu to a data source, or you can fill it by hand (declaratively or programmatically) using MenuItem objects.
The MenuItem class isn’t quite as rich as the TreeNode class—for example, MenuItem objects don’t support check boxes or the ability to programmatically set their expanded/collapsed state. However, they still have many similar properties, including those for setting images, determining whether the item is selectable, and specifying a target link. Table 16-12 has the defaults.
C H A P T E R 1 6 ■ W E B S I T E N AV I G AT I O N |
593 |
Figure 16-18. Different looks for a TreeView
Table 16-12. MenuItem Properties
Property |
Description |
Text |
The text displayed in the menu for this item (when displayed). |
ToolTip |
The tooltip text that appears when you hover over the menu item. |
Value |
Stores a nondisplayed value with additional data about the menu item |
|
(such as a unique ID you’ll use when handling click events to identify the |
|
node or look up more information). |
NavigateUrl |
If set, when this node is clicked, it automatically forwards the user to this |
|
URL. Otherwise, you’ll need to react to the Menu.MenuItemClick event to |
|
decide what action you want to perform. |
Target |
If the NavigateUrl property is set, this sets the target window or frame for |
|
the link. If Target isn’t set, the new page is opened in the current browser |
|
window. The Menu also exposes a Target property, which you can set to |
|
apply a default target for all MenuItem instances. |
Selectable |
If false, this item can’t be selected. Usually you’ll set this to false only if the |
|
item is a subheading that contains selectable child items. |
ImageUrl |
If set, it’s the image that’s displayed next to the menu item (on the right of |
|
the text). By default, no image is used. |
PopOutImageUrl |
The image that’s displayed next to the menu item (on the right) if it |
|
contains subitems. By default, this is a small solid arrow. |
SeparatorImageUrl |
The image that’s displayed immediately underneath this menu item, to |
|
separate it from the following item. |
ImageToolTip |
The tooltip text for the image displayed next to the node. |
|
|
594 C H A P T E R 1 6 ■ W E B S I T E N AV I G AT I O N
You can walk over the structure of a Menu control in much the same way as the structure of a TreeView. The Menu contains a collection of MenuItem objects in the Items property, and each MenuItem has a ChildItems collection that contains nested items. For example, you could adapt the previous example that used the TreeView to display a list of categories and products by simply changing a few class names. Here’s the code you need, with the surprisingly few changes highlighted:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DataSet ds = GetProductsAndCategories();
// Loop through the category records.
foreach (DataRow row in ds.Tables["Categories"].Rows)
{
//Use the constructor that requires just text
//and a nondisplayed value.
MenuItem itemCategory = new MenuItem( row["CategoryName"].ToString(), row["CategoryID"].ToString());
Menu1.Items.Add(itemCategory);
//Get the children (products) for this parent (category). DataRow[] childRows = row.GetChildRows(ds.Relations[0]);
//Loop through all the products in this category. foreach (DataRow childRow in childRows)
{
MenuItem itemProduct = new MenuItem( childRow["ProductName"].ToString(), childRow["ProductID"].ToString());
itemCategory.ChildItems.Add(itemProduct);
}
}
}
}
protected void Menu1_MenuItemClick(object sender,
System.Web.UI.WebControls.MenuEventArgs e)
{
if (Menu1.SelectedItem.Depth == 0)
{
lblInfo.Text = "You selected Category ID: ";
}
else if (Menu1.SelectedItem.Depth == 1)
{
lblInfo.Text = "You selected Product ID: ";
}
lblInfo.Text += Menu1.SelectedItem.Value;
}
Figure 16-19 shows the result.
C H A P T E R 1 6 ■ W E B S I T E N AV I G AT I O N |
595 |
Figure 16-19. Displaying a menu with information from a database
Overall, the Menu and TreeView controls expose strikingly similar programming models, even though they render themselves quite differently. They also have a similar style-based formatting model. But a few noteworthy differences exist:
•The Menu displays a single submenu. The TreeView can expand an arbitrary number of node branches at a time.
•The Menu displays a root level of links in the page. All other items are displayed using fly-out menus that appear over any other content on the page. The TreeView shows all its items inline in the page.
•TreeView supports on-demand filling and client callbacks. The Menu does not.
•The Menu supports templates. The TreeView does not.
•The TreeView supports check boxes for any node. The Menu does not.
•The Menu supports horizontal and vertical layouts, depending on the Orientation property. The TreeView supports only vertical layout.
Menu Styles
The Menu control provides an overwhelming number of styles. Like the TreeView, the Menu derives a custom class from the Style base class—in fact, it derives two (MenuStyle and MenuItemStyle). These styles add spacing properties (ItemSpacing, HorizontalPadding, and VerticalPadding). However, you can’t set menu item images through the style, because there is no ImageUrl property.
Much like the TreeView, the Menu supports defining different menu styles for different menu levels. However, the key distinction that the Menu control encourages you to adopt is between static items (the root level items that are displayed in the page when it’s first generated) and dynamic items (the items in fly-out menus that are added when the user moves the mouse over a portion of the menu). In most websites, there is a definite difference in the styling of these two elements. To support this, the Menu class defines two parallel sets of styles, one that applies to static items and one that applies to dynamic items, as shown in Table 16-13.
C H A P T E R 1 6 ■ W E B S I T E N AV I G AT I O N |
597 |
Figure 16-20. A menu with two static levels
Interestingly, whether you fill the Menu class declaratively or programmatically, you can still use a template. From the template’s point of view, you’re always binding to a MenuItem object. That means your template always needs to extract the value for the item from the MenuItem.Text property, as shown here:
<asp:Menu ID="Menu1" runat="server"> <StaticItemTemplate>
<%# Eval("Text") %> </StaticItemTemplate>
</asp:Menu>
One reason you might want to use the template features of the Menu is to show multiple pieces of information from a data object. For example, you might want to show both the title and the description from the SiteMapNode for this item (rather than just the title). Unfortunately, that’s
not possible. The problem is that the Menu binds directly to the MenuItem object. The MenuItem object does expose a DataItem property, but by the time it’s being added into the menu, that DataItem no longer has the reference to the SiteMapNode that was used to populate it. So, you’re mostly out of luck.
If you’re really desperate, you can write a custom method in your class that looks up the SiteMapNode based on its URL. This is extra work that should be unnecessary, but it does get the job done of making the description information available to the menu item template.
private string matchingDescription = "";
protected string GetDescriptionFromTitle(string title)
{
// This assumes there's only one node with this tile. SiteMapNode node = SiteMap.RootNode; SearchNodes(node, title);
return matchingDescription;
}
