65.9K
CodeProject 正在变化。 阅读更多。
Home

树/组织结构

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (12投票s)

2013年7月3日

CPOL

5分钟阅读

viewsIcon

50532

为 Web 应用程序创建 HTML 树形或组织结构。

引言

树形结构是一种以图形方式表示结构层次关系的方法。ASP.NET 提供了 Tree 控件来表示层次结构,但它是一个非常基础的控件,不具备图形显示能力。一目了然地理解节点之间的关系非常容易。

本文提供了有关如何创建树形结构的详细信息。我创建了一个示例代码,您可以直接使用或根据需要进行自定义。

背景

树形结构是通过基本的 HTML 表格和 Div 控件以及基本样式创建的,因此所有浏览器都支持。HTML 表格单元格包含节点名称和显示节点关系的线条。这些线条基本上是用 DIV 标签创建的。线条的宽度是根据其子节点计算的。

代码详情

基本数据要求

我的数据存储在 SQL Server 中,并使用 SQL 查询来获取数据。下表包含用于创建树形结构的示例数据。在此示例中,ID 列具有唯一值,PID(父 ID)列与 ID 列相关。Name 列不言自明,即树中的节点名称。

以下是用于创建层次结构的表结构。

ID 名称 PID
1 专员 0
2 首席会计官 1
4 副专员 1
6 行政官员 1
7 房产官员 1
8 会计主管 2
9 会计主管 2
10 副专员1 4
11 副专员2 4
12 药物司 10
13 项目司 10
14 资本项目 10
16 Korba 项目 11
17 Admin 项目 11
18 官员1 9
19 官员2 9

列说明

ID:表示树中每个节点的唯一 ID。

Name:节点的名称

PID:节点的父 ID(此与 ID 列相关)。

每个树只有一个父节点,在我的示例中,父节点的 PID 值为零。

代码详情

如何使用

步骤 1:将以下样式和 DIV 标签添加到您的 aspx 页面。这些样式用于在浏览器中呈现树。

ASPX 页面中用于层次结构的样式

<style type="text/css">

.vertical
{   position:relative;
    width: 2px;
    height: 25px;
    left:50%; 
    background-color: #000000;
}


table 
{
   vertical-align:top;
   text-align:center;
    border-collapse: collapse;
}
table td { border:0;   vertical-align:top;   text-align:center; word-wrap: break-word;
}

</style>//

ASPX 页面代码 - Div 将容纳树结构。

//
<body>
    <form id="form1" runat="server">
    <div id="abc" runat="server"></div>
    </form>
</body> 

步骤 2:将以下代码添加到 ASPX 页面的 cs 文件中。Page_load 函数的第一行创建“OrgTree”类的实例,代码的第二行调用“CreateTreeHtml()”函数来生成应用程序。

protected void Page_Load(object sender, EventArgs e)
{
    OrgTree orgTree = new OrgTree();
    //Assing output of the CreateTreeHtml function to Div
    abc.InnerHtml = orgTree.CreateTreeHtml();
}

步骤 3:将类“OrgTree”添加到您的项目中,并修改连接字符串以指向您的数据库。如有必要,请更改表名。您将在下一节中找到有关此类的更多详细信息。

运行您的应用程序,您将在屏幕上看到树形结构。

工作原理核心功能

“OrgTree”类包含创建树形结构的核心逻辑。此类包含两个主要函数,其余四个函数是创建树的辅助函数。我将在后续章节中详细解释每个函数。

1. CreateTreeHtml:这是一个公共函数,向外部公开以创建树形结构。此函数内部调用另一个函数 CreateLevelNode,该函数将父节点 ID 作为输入。在此示例中,我传入了值 1,因为在我的示例中父节点 ID 是 1。

public string CreateTreeHtml()
{
    // CreateLevelNode funtion accept the Root node id. In this example the root node id is 1.
    return CreateLevelNode(1);
} 

2. CreateLevelNode:这是返回层次结构 HTML 字符串的核心函数。此函数被递归调用以创建整个树形结构。第一步创建父节点,然后创建子节点。每个子节点再次调用此函数来创建其子节点。此过程持续进行,直到所有叶节点都被创建。基本逻辑是创建一个列数等于子节点数的表。在每个表中创建额外的表行以绘制水平线。这条水平线实际上是一个高度为 1 像素的 HTML DIV 标签,当这个 DIV 在浏览器中渲染时,它看起来就像一条直线。

同样,对于每个节点,都会创建一个垂直向下连接到每个父节点的线条,这个垂直线条也是一个宽度为 1 像素的 DIV 标签。

private string CreateLevelNode(int PID)
{
    
    string finalHdiv;
    StringBuilder sbmainTbl = new StringBuilder();
    DataTable dtparent = GetParentNodeDetails(PID);
    
    //Caclulate with of tabele cell.     
    int leafNodeCount = GetLeafNodeCount(PID);
    if (leafNodeCount == 0)
        leafNodeCount = 1;//avoid divide by 0 error
    int tdWidth = 100 / leafNodeCount;

    // Get the difference in width to create blank node.
    int tdWidthDiff = 100 - leafNodeCount * tdWidth;

    sbmainTbl.AppendFormat("{0}", tabletag);

    // Add parent Node Text .
    sbmainTbl.AppendFormat("<tr><td>{0}</td></tr>", 
          CellText(dtparent.Rows[0]["Name"].ToString()));

    // Get the child node Name
    DataTable dt = GetChildNodeDetails(int.Parse(dtparent.Rows[0]["ID"].ToString()));
    int childNodecount = dt.Rows.Count;

    //Draw Horizontal and Vertical line if child nore are more than one.
    if (childNodecount > 1)
    {
        if (childNodecount > 0)
            sbmainTbl.AppendFormat("<tr><td>{0}</td></tr>", VDiv);

        if (childNodecount == 0)
            finalHdiv = string.Format(HDiv, 0, 0);
        else
            finalHdiv = HLineWidth(PID);// string.Format(HDiv, 100 - 100 / 
              childNodecount, 100 / (2 * childNodecount));
        sbmainTbl.AppendFormat("<tr><td>{0}</td></tr>", finalHdiv);
    }

    sbmainTbl.AppendFormat("<tr><td>");

    //Create Vertical line below parent Node.
    sbmainTbl.AppendFormat("{0}<tr>", tabletag);
    for (int i = 0; i < childNodecount; i++)
    {
        int leafNodeCountchild = GetLeafNodeCount(int.Parse(dt.Rows[i]["ID"].ToString()));
        if (leafNodeCountchild > 0)
            sbmainTbl.AppendFormat("<td style=\"width:{0}%\" >{1}</td>", 
                                   leafNodeCountchild * tdWidth, VDiv);
        else
            sbmainTbl.AppendFormat("<td>{1}</td>", VDiv);
        // sbmainTbl.AppendFormat("<td style=\"width:{0}%\" >{1}</td>", tdWidth, VDiv);
    }
    //Create empty Cell.
    if (tdWidthDiff > 0)
    {
        sbmainTbl.AppendFormat("<td style=\"width:{0}%\" ></td>", tdWidthDiff);
    }
    sbmainTbl.Append("</tr>");

    sbmainTbl.Append("<tr>");
    for (int i = 0; i < childNodecount; i++)
    {
        //Create Child Node Table.
        sbmainTbl.AppendFormat("<td>{0}</td>", 
          CreateLevelNode(int.Parse(dt.Rows[i]["ID"].ToString())));
    }
    //Create empty Cell.
    if (tdWidthDiff > 0)
    {
        sbmainTbl.AppendFormat("<td style=\"width:{0}%\" ></td>", tdWidthDiff);
    }
    sbmainTbl.Append("</tr></table>");
    sbmainTbl.AppendFormat("</td></tr></table>", tabletag);
    return sbmainTbl.ToString();
}

3. HLineWidth:此函数计算水平线的宽度并设置绘制此线的左偏移点。由于树中的子节点数量不固定,因此线的宽度是根据子节点的数量计算的。如果只有一个子节点,则无需绘制水平线;如果节点没有任何子节点,则不执行任何操作。假设有三个子节点,则水平线的宽度将是第一个节点宽度的一半 + 第二个节点的宽度 + 最后一个节点宽度的一半。偏移点将是第一个子节点的宽度的一半。

private string HLineWidth(int PID)
{
    //This function calculate the width of Horizontal line 


    float HlineWidth = 0;

    int leafNodeCount = GetLeafNodeCount(PID);
    if (leafNodeCount == 0)
        leafNodeCount = 1;//avoid divide by 0 error
    float tdWidth = 100 / leafNodeCount;
    float tdWidthDiff = 100 - tdWidth * leafNodeCount;
    DataTable dt = GetChildNodeDetails(PID);
    int childNodecount = dt.Rows.Count;

    float offset = 0;
               
    for (int i = 0; i < childNodecount; i++)
    {
        int leafNodeCountchild = GetLeafNodeCount(
            int.Parse(dt.Rows[i]["ID"].ToString()));
        if (i == 0)
        {
            offset = leafNodeCountchild * tdWidth / 2;
            HlineWidth = HlineWidth + offset;                   
        }
        else if (i == (childNodecount - 1))
        {
            HlineWidth = HlineWidth + leafNodeCountchild * tdWidth / 2;                   
        }
        else
        {
            HlineWidth = HlineWidth + leafNodeCountchild * tdWidth;                   
        }
    }

    return string.Format(HDiv, HlineWidth , offset);
}

4. CellText:这是一个非常简单的函数,只是将父节点名称添加到 DIV 标签内。此函数特意创建,用于为节点提供颜色和背景颜色。您也可以自定义它来创建超链接或在此节点上添加自定义功能。

private string CellText(string Celltxt)
{
    // Set the Node text here. You can customize this as per your requirement.
    return string.Format("<div style=\"display:block; " + 
      "word-wrap: break-word; width: 99%;\">{0}</div>", Celltxt);
}

5. GetChildNodeDetails:此函数仅连接到数据库并获取下一级别的子节点详细信息。您必须更改连接字符串以指向您的数据库以获取所需数据。

private DataTable GetChildNodeDetails(int parentId)
{
    // Get the Current level child node details (ID , Name, PID)
    SqlConnection con = new SqlConnection();
    con.ConnectionString = ConnectionString;
    SqlCommand cmd = new SqlCommand(
      "select * from tree where pid= " + parentId.ToString(), con);
    //create the DataAdapter & DataSet
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);
    con.Close();
    return ds.Tables[0];
}

6. GetParentNodeDetails:此函数仅获取当前节点名称及其 ID。此 ID 将用于在后续调用中获取其子节点。此函数返回 HTML 层次结构表。

private DataTable GetParentNodeDetails(int ID)
{
    // Get the Parent Node Name and ID
    SqlConnection con = new SqlConnection();
    con.ConnectionString = ConnectionString;
    SqlCommand cmd = new SqlCommand("select * from tree where id= " + ID.ToString(), con);
    //create the DataAdapter & DataSet
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);
    con.Close();
    return ds.Tables[0];
} 

7. GetLeafNodeCount:这是最后一个函数,仅返回父节点的叶级别节点计数。

private int GetLeafNodeCount(int ID)
{
    // This function return the Leaf node count.
    string query = string.Format(@"WITH Node (ID, name,PID)
                                AS  (
                                    SELECT     tree.ID, tree.name , tree.PID
                                    FROM       tree
                                    WHERE      ID ={0}
                                    UNION ALL
                                    SELECT     tree.ID, tree.name, tree.PID
                                    FROM       tree
                                    INNER JOIN Node
                                    ON         tree.PID = Node.ID
                                    )
                                SELECT  ID, name,PID FROM   Node
                                where  ID not in (SELECT  PID FROM   Node)
                                ", ID);

    SqlConnection con = new SqlConnection();
    con.ConnectionString = ConnectionString;
    SqlCommand cmd = new SqlCommand(query, con);
    //create the DataAdapter & DataSet
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);
    con.Close();
    return ds.Tables[0].Rows.Count;

} 

类级别变量:以下是上述函数使用的变量

//tableTag variable define the table with style for the Hierarchy structure.
string tabletag = "<table class =\"tbl\" border=\"0\" " + 
  "cellpadding=\"0\" cellspacing=\"0\" style=\" " + 
  "table-layout: fixed;   width:100% ;vertical-align:top; text-align:center\" >";
//HDiv variable Show horizontal  line .
string HDiv = " <div style=\"position:relative; background-color:" + 
  " #000000;width: {0}%; left:{1}%;  height: 2px;\"></div>";
//HDiv variable Show horizontal  line .
string VDiv = " <div class=\"vertical\"></div>";

String ConnectionString = @"Data Source=.\;Initial Catalog=TestDB;Integrated Security=True";
© . All rights reserved.