C#开发训练营

第3课:ASP.NET(3)——树状结构数据绑定

本课将讨论树状结构数据绑定操作,如菜单和树状视图的数据绑定。

树状数据结构

为了动态绑定树状结构视图,如菜单控件(Menu)、树状视图(TreeView)控件等,我们首先需要约定数据的管理结构,如图1中显示了一个树状结构数据,代码中,这些数据会定义在/app_data/excel/menu.xlsx文件中。

图1

如图1中所示,我们约定的树状结构数据有5个字段,分别是:

  • depth,定义数据节点的深度,确定节点是第几级。
  • value,节点的数据。其中,第一级数据直接使用数字,其它级别的数据使用圆点(.)进行分级。请注意,除了使用数字,这里还可以设置一些有意义的内容,如USER、USER.PWD、USER.LOGIN等。
  • text,节点显示的文本。
  • data1,扩展数据一,可以根据需要使用。
  • data2,扩展数据二,可以根据需要使用。

下面的代码(/app_code/data/base/tNode.cs),我们使用tNode类处理节点数据。

C#
using System;
using System.Collections.Generic;
using System.Data;

public class tNode
{
    // 构造函数
    public tNode()
    {
        Parent = null;
        Children = null;
    }
    // 基本成员
    public int Depth { get; set; }
    public string Value { get; set; }
    public string Text { get; set; }
    public tNode Parent { get; set; }
    public List<tNode> Children { get; set; }
    // 扩展数据
    public string Data1 { get; set; }
    public string Data2 { get; set; }
}

tNode类中定义了节点所需的一些信息属性,包括:

  • Depth,指定节点是第几级。
  • Value,指定节点的数据值。
  • Text,节点的文本。
  • Parent,节点的父节点对象,根节点的此属性应为null。
  • Children,节点的子节点,定义为List<tNode>类型。
  • Data1,扩展数据一。
  • Data2,扩展数据二。

下面的代码(/app_code/data/base/tNode.cs),使用tTree类处理树状数据结构。

C#
// 树状结构
public class tTree
{
    public tNode Root { get; set; }
    //
    public string DepthField { get; set; }
    public string ValueField { get; set; }
    public string TextField { get; set; }
    public string Data1Field { get; set; }
    public string Data2Field { get; set; }
    //
    public tTree()
    {
        Root = null;
        DepthField = "depth";
        ValueField = "value";
        TextField = "text";
        Data1Field = "data1";
        Data2Field = "data2";
    }
    // 从DataTable对象载入数据
    public void ReadDataTable(DataTable tbl)
    {
        if (tbl == null || tbl.Columns.Contains(DepthField) == false ||
            tbl.Columns.Contains(ValueField) == false ||
            tbl.Columns.Contains(TextField) == false ||
            tbl.Columns.Contains(Data1Field) == false ||
            tbl.Columns.Contains(Data2Field) == false)
        {
            Root = null;
        }
        else
        {
            Root = new tNode() { Depth = 0 };
            AddChildren(tbl, Root);
        }
    }

    // 添加所有子节点
    protected void AddChildren(DataTable tbl,tNode parentNode)
    {
        string cond;
        if (parentNode.Depth == 0)
            cond = DepthField + "=1";
        else
            cond = string.Format("{0}={1} and {2} like '{3}.%'",
                DepthField, parentNode.Depth + 1, 
                ValueField, parentNode.Value);
        //
        DataRow[] rows = tbl.Select(cond);
        if (rows.Length == 0) return;
        parentNode.Children = new List<tNode>();
        for(int row = 0; row < rows.Length; row++)
        {
            tNode node = new tNode()
            {
                Value = tStr.GetValue(rows[row][ValueField]),
                Text = tStr.GetValue(rows[row][TextField]),
                Depth = tInt.GetValue(rows[row][DepthField]),
                Data1 = tStr.GetValue(rows[row][Data1Field]),
                Data2 = tStr.GetValue(rows[row][Data2Field])
            };
            node.Parent = parentNode;
            AddChildren(tbl, node);
            parentNode.Children.Add(node);
        }
    }
}

tTree类中,首先定义了一些基本的属性和构造函数,并设置了默认值,如:

  • Root属性,定义为树状结构数据的根节点,默认值为null。
  • DepthField属性,指定深度(级别)数据的字段名,默认值为depth。
  • ValueField属性,指定节点数据值的字段名,默认值为value。
  • TextField属性,指定节点文本内容的字段名,默认值为text。
  • Data1Field属性,指定节点扩展数据一的字段名,默认值为data1。
  • Data2Field属性,指定节点扩展数据二的字段名,默认值为data2。

代码中,ReadDataTable()方法用于从DataTable对象中读取节点数据,并生成树状结构对象,这里DataTable对象中的列名应包含DepthField、ValueField、TextField、Data1Field和Data2Field属性指定的字段名。

AddChildren()方法的功能是通过递归调用,从DataTable对象数据中添加节点的所有子节点。这里,所有节点的根节点是tTree的Root属性,其Depth定义为0,Root的第一级子节点Depth定义为1,以此类推。

在DataTable对象中查询节点的子节点数据行时,使用了DataTable对象的Select()方法,参数指定查询条件,查询结果为DataRow数组。当前节点为根节点(Root属性)时,查询DepthField数据为1的节点数据行,实际查询条件类似“depth=1”。如果不是根节点时,查找DepthField字段等于当前节点下一级的节点,并且ValueField字段数据以当前节点Value属性内容加圆点作为前缀,实际查询条件的格式类似“depth=2 and value like '1.'”。

下面,通过菜单控件和树状视图控件的实际演示tTree类的应用。

菜单数据绑定

菜单数据绑定示例页面为/demo/menu/MenuBindDemo.aspx,下面是HTML部分的定义。

HTML
<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="MenuBindDemo.aspx.cs" 
    Inherits="demo_menu_MenuBindDemo" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:Menu ID="menu1" runat="server" 
            OnMenuItemClick="menu1_MenuItemClick"
             Orientation="Horizontal"></asp:Menu>
        <p>
            <asp:TextBox ID="txt1" runat="server"></asp:TextBox>
        </p>
    </form>
</body>
</html>

页面中定义了一个Menu控件,稍后会编码绑定其菜单项,TextBox控件txt1用于显示点击的菜单项信息。页面的C#代码部分如下(/demo/menu/MenuBindDemo.aspx.cs)。

C#
using System;
using System.Data;
using System.Web.UI.WebControls;

public partial class demo_menu_MenuBindDemo : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack == false)
        {
            BindMenu();
        }
    }

    protected void BindMenu()
    {
        DataTable data = tExcel.ReadExcel(
            tWeb.MapPath("/app_data/excel/menu.xlsx"));
        tTree tree = new tTree();
        tree.ReadDataTable(data);
        if (tree.Root == null) return;
        AddNodes(null,tree.Root);
    }

    protected void AddNodes(MenuItem menuItem, tNode node)
    {
        for (int i = 0; i < node.Children.Count; i++)
        {
            MenuItem item = new MenuItem(
                node.Children[i].Text, node.Children[i].Value);
            //
            if (node.Children[i].Children != null)
            {
                AddNodes(item, node.Children[i]);
            }
            //
            if (menuItem == null) menu1.Items.Add(item);
            else menuItem.ChildItems.Add(item);
        }
    }

    protected void menu1_MenuItemClick(object sender, MenuEventArgs e)
    {
        txt1.Text = e.Item.Text;
    }
}

BindMenu()方法中,首先从/app_data/excel/menu.xlsx文件中读取节点数据,然后通过tTree.ReadDataTable()方法将DataTable对象中的数据读取到tTree对象中,成功读取节点数据后,AddNodes()方法会通过递归调用,并根据节点数据按层次创建菜单项。图2中显示了鼠标悬停于菜单项“菜单三”>>“菜单三C”时的效果。

图2

点击其中的菜单项时,在文本框txt1中会显示菜单的文本内容。

为简化操作,我们在tWebCtr类中封装了Web控件操作的相关代码,菜单数据绑定使用BindMenu()方法,代码如下(/app_code/aspnet/form/tWebCtr.cs)。

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Web.UI.WebControls;

public static class tWebCtr
{
    // 将tTree对象结构绑定到Menu控件
    public static void BindMenu(Menu menu, tTree tree)
    {
        if (tree.Root == null) return;
        AddMenuItems(menu, null, tree.Root);
    }
    //
    private static void AddMenuItems(Menu menu, MenuItem menuItem, 
        tNode node)
    {
        for (int i = 0; i < node.Children.Count; i++)
        {
            MenuItem item = new MenuItem(
                node.Children[i].Text, node.Children[i].Value,
                node.Children[i].Data1,node.Children[i].Data2);
            //
            if (node.Children[i].Children != null)
            {
                AddMenuItems(menu, item, node.Children[i]);
            }
            //
            if (menuItem == null) menu.Items.Add(item);
            else menuItem.ChildItems.Add(item);
        }
    }

}

请注意,代码中MenuItem的Value、Text、ImageUrl和NavigateUrl属性分别由tNode对象的Value、Text、Data1和Data2属性赋值。页面中绑定菜单数据时可以参数如下代码。

C#
protected void BindMenu()
{
    DataTable data = tExcel.ReadExcel(
        tWeb.MapPath("/app_data/excel/menu.xlsx"));
    tTree tree = new tTree();
    tree.ReadDataTable(data);
    tWebCtr.BindMenu(menu1, tree);
}

从数据源构建了tTree对象后,可以直接调用tWebCtr.BindMenu()方法将tTree对象结构绑定到Menu控件中。

树状视图数据绑定

树状视图(TreeView)的数据结构与菜单(Menu)控件相似,我们可以使用tTree、tNode对象完成相同的工作,并且使用相同结构的数据。

下面使用/demo/treenode/TreeNodeBindDemo.aspx页面演示树状视图数据绑定操作,HTML代码部分如下。

HTML
<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="TreeNodeBindDemo.aspx.cs" 
    Inherits="demo_treenode_TreeNodeBindDemo" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:TreeView ID="tv1" runat="server"
             OnSelectedNodeChanged="tv1_SelectedNodeChanged">
        </asp:TreeView>
        <p>
            <asp:TextBox ID="txt1" runat="server"></asp:TextBox>
        </p>
    </form>
</body>
</html>

代码中定义了一个名为tv1的TreeView控件,文本框txt1则用于显示点击节点的信息。

页面的C#代码部分如下(/demo/treenode/TreeNodeBindDemo.aspx.cs)。

C#
using System;
using System.Data;
using System.Web.UI.WebControls;

public partial class demo_treenode_TreeNodeBindDemo : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack == false)
        {
            BindTreeView();
        }
    }

    protected void BindTreeView()
    {
        DataTable data = tExcel.ReadExcel(
            tWeb.MapPath("/app_data/excel/menu.xlsx"));
        tTree tree = new tTree();
        tree.ReadDataTable(data);
        if (tree.Root == null) return;
        AddNodes(null, tree.Root);
    }

    protected void AddNodes(TreeNode treeNode, tNode node)
    {
        for (int i = 0; i < node.Children.Count; i++)
        {
            TreeNode tn = new TreeNode(
                node.Children[i].Text, node.Children[i].Value);
            //
            if (node.Children[i].Children != null)
                AddNodes(tn, node.Children[i]);
            //
            if (treeNode == null) tv1.Nodes.Add(tn);
            else treeNode.ChildNodes.Add(tn);
        }
    }

    protected void tv1_SelectedNodeChanged(object sender, EventArgs e)
    {
        txt1.Text = tv1.SelectedNode.Text;
    }
}

页面显示的默认效果如图3。点击其中一个节点时,会在文本框txt1中显示节点的文本内容。

图3

在tWebCtr类中封装了BindTreeView()方法,其功能是将tTree对象的结构绑定到TreeView控件中,实现代码如下(/app_code/aspnet/form/tWebCtr.cs)。

C#
// 将tTree对象结构绑定到TreeView控件
public static void BindTreeView(TreeView treeView,tTree tree)
{
    if (tree.Root == null) return;
    AddTreeNodes(treeView, null, tree.Root);
}
//
private static void AddTreeNodes(TreeView treeView, 
    TreeNode treeNode, tNode node)
{
    for (int i = 0; i < node.Children.Count; i++)
    {
        TreeNode tn = new TreeNode(
            node.Children[i].Text, node.Children[i].Value,
            node.Children[i].Data1,node.Children[i].Data2,
            "_blank");
        //
        if (node.Children[i].Children != null)
        {
            AddTreeNodes(treeView,tn, node.Children[i]);
        }
        //
        if (treeNode == null) treeView.Nodes.Add(tn);
        else treeNode.ChildNodes.Add(tn);
    }
}

代码中,TreeNode的Value、Text、ImageUrl和NavigateUrl属性分别由tNode对象的Value、Text、Data1和Data2属性赋值。页面中可以参考如下代码绑定TreeView控件数据。

C#
protected void BindTreeView()
{
    DataTable data = tExcel.ReadExcel(
        tWeb.MapPath("/app_data/excel/menu.xlsx"));
    tTree tree = new tTree();
    tree.ReadDataTable(data);
    tWebCtr.BindTreeView(tv1, tree);
}