树/组织结构






4.73/5 (12投票s)
为 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";