2013年3月29日 星期五

TreeView 控制項

使用靜態資料建構 TreeView

使用靜態資料,只要在 <Nodes> 標籤中加入 <asp:TreeNode> 項目即可。

<asp:TreeView ID="TreeView2" runat="server" ShowLines="True" >
    <Nodes>
        <asp:TreeNode Text="1" Value="1">
            <asp:TreeNode Text="1.1" Value="1.1">
                <asp:TreeNode Text="1.1.1" Value="1.1.1"></asp:TreeNode>
                <asp:TreeNode Text="1.1.2" Value="1.1.2"></asp:TreeNode>
            </asp:TreeNode>
            <asp:TreeNode Text="1.2" Value="1.2">
                <asp:TreeNode Text="1.2.1" Value="1.2.1"></asp:TreeNode>
                <asp:TreeNode Text="1.2.2" Value="1.2.2">
                    <asp:TreeNode Text="1.2.2.1" Value="1.2.2.1"></asp:TreeNode>
                </asp:TreeNode>
                <asp:TreeNode Text="1.2.3" Value="1.2.3"></asp:TreeNode>
            </asp:TreeNode>
        </asp:TreeNode>
    </Nodes>
</asp:TreeView>

使用遞迴動態建構 TreeView

靜態資料必須一開始就知道資料,才做的出來,如果 Tree 的資料是存在資料庫中,你也可以使用遞迴由程式動態建構出所有的樹狀節點。

protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            //取得資料,回傳格式 ( OrgID, OrgName, ID, ParentID )
            DataTable dtOrg = RjCommonDB.GetOrg();

            //DataTable 必須放在 DataSet 中,才可以建立關聯
            DataSet dsOrg = new DataSet();
            dsOrg.Tables.Add(dtOrg);

            //將 DataRelation 加進 DataSet 中
            DataRelation drOrg = new DataRelation("OrgRelation", dsOrg.Tables[0].Columns["ID"], dsOrg.Tables[0].Columns["ParentId"]);
            dsOrg.Relations.Add(drOrg);

            foreach (DataRow row in dsOrg.Tables[0].Rows)
            {
                if (row["ParentID"].ToString() == "")
                {
                    TreeNode treeNode = CreateNode(row["OrgID"].ToString(), row["OrgName"].ToString(), int.Parse(row["ChildCount"].ToString()) > 0);
                    TreeView4.Nodes.Add(treeNode);
                    PopulateTree(row, treeNode);
                }
            }
        }
    }

    // 建立子節點
    private void PopulateTree(DataRow dataRow, TreeNode parentNode)
    {
        foreach (DataRow row in dataRow.GetChildRows("OrgRelation"))
        {
            TreeNode treeNode = CreateNode(row["OrgID"].ToString(), row["OrgName"].ToString(), int.Parse(row["ChildCount"].ToString()) > 0);
            parentNode.ChildNodes.Add(treeNode);
            PopulateTree(row, treeNode);  //Recursively build the tree
        }
    }
    ///節點屬性設定
    private TreeNode CreateNode(string nodeValue, string nodeText, bool expanded)
    {
        TreeNode node = new TreeNode(); ;
        node.Text = nodeText;
        node.Value = nodeValue;
        node.Expanded = expanded;
        return node;
    }

繫結到 HierarchicalDataSourceControl

TreeView 控制項是一個階層資料繫結控制項 ( HierarchicalDataBoundControl ),若要繫結資料,則必須透過階層資料來源控制項 (HierarchicalDataSourceControl)。 例如: XmlDataSourceSiteMapDataSource 控制項。 下面範例使用 XmlDataSource 將資料繫結到 TreeView 控制項。

假設下面 XML 文件是我們要繫結的資料,首先要知道的是,每一個標籤都表示一個節點,所以共有9個節點。

<?xml version="1.0" encoding="utf-8"?>
<總公司>
    <Company CompanyId="C1" CompanyName="Microsof Inc.">
      <Employee CompanyId="C1" EmployeeId="E01" FirstName="Joe" >Lin</Employee>
      <Employee CompanyId="C1" EmployeeId="E02" FirstName="Mary">Huang</Employee>
      <Employee CompanyId="C1" EmployeeId="E03" FirstName="Sam">Huang</Employee>
    </Company>
    <Company CompanyId="C2" CompanyName="Apple Inc.">
      <Employee CompanyId="C2" EmployeeId="E04" FirstName="Sue">Chen</Employee>
      <Employee CompanyId="C2" EmployeeId="E05" FirstName="Tom">Li</Employee>
      <Employee CompanyId="C2" EmployeeId="E06" FirstName="Mike">Wang</Employee>
    </Company>
</總公司>

再來要知道的是,假設有個標籤的樣式如下:

<Item FieldA='ABC' FieldB='XYZ'>甲乙丙</Item>

1) 若希望在節點上顯示 ABC ,則必須設定 TextFieldText 屬性。例如: TextField="FieldA" 或 Text="FieldA" 。
2) 若希望在節點上顯示 甲乙丙 ,則必須將 TextField 設定成 #InnerText 。例如: TextField="#InnerText" 。
3) 若必須將 XYZ 回傳,則必須設定 ValueFieldValue 屬性 屬性。例如: ValueField="FieldB" 或 Value="FieldB" 。
4) 要載入 TreeView 的項目必須使用 DataMember 屬性指定,例如:DataMember="Company" 。

<asp:XmlDataSource ID="XmlDataSource3" runat="server" DataFile="CompanyEmployee2.xml" />
    <asp:TreeView ID="TreeView1" runat="server" 
            DataSourceID="XmlDataSource3" 
            OnSelectedNodeChanged="TreeView1_SelectedNodeChanged">
        <DataBindings>
            <asp:TreeNodeBinding DataMember="Company" TextField="CompanyName" ValueField="CompanyId" />
            <asp:TreeNodeBinding DataMember="Employee" TextField="#InnerText" ValueField="EmployeeId" />
        </DataBindings>
    </asp:TreeView>
</ContentTemplate>
protected void TreeView1_SelectedNodeChanged(object sender, EventArgs e)
{
    //取得 SelectdNode 的 ParentNode
    TreeNode node = TreeView1.SelectedNode;
    int level = node.Depth;
    TreeNode PNode = node.Parent;

    myMessage.ShowAjax(this, 
        "You clicked tree node \r\n" 
        + TreeView1.SelectedNode.Text + "\r\n" 
        + TreeView1.SelectedNode.Value + "\r\n\"
        + "Its parent node is " + PNode.Text 
        );
}

繫結到實作 IHierarchicalDataSource 的自訂物件

若要將某個物件資料直接丟給 TreeView 繫結,則該資料必須實作 IHierarchicalDataSource 介面。 底下自訂類別就是一個實作 IHierarchicalDataSource 的範例。 使用時只要將該物件資料指定給 DataSource 屬性,再叫用 DataBind 方法即可。

<asp:TreeView ID="TreeView3" runat="server" OnSelectedNodeChanged="TreeView3_SelectedNodeChanged">
        <DataBindings>
        <asp:TreeNodeBinding 
            DataMember = "System.Data.DataRowView" 
            TextField  = "OrgName" 
            ValueField = "OrgID" />
        </DataBindings>
    </asp:TreeView>
protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            //這個 DataTable 至少要包含三個欄位, ID, ParentID, Text
            // ID : 節點唯一識別碼
            // ParentID : 父層節點識別碼
            // Text : 節點名稱
            // 至於節點的Value值,可以使用另外一個欄位,也可以使用ID欄位
            DataTable dtOrg = RjCommonDB.GetOrg();

            TreeView3.DataSource = new MyHierarchicalDataSource(dtOrg, "ID", "ParentID", "DE9C5DF3-48D4-4D81-8A8F-320B94DEEB04");
            TreeView3.DataBind();
        }
    }
/// <summary>
    /// 實作階層式資料來源
    /// </summary>
    public class MyHierarchicalDataSource : IHierarchicalDataSource
    {
        //實作事件
        public event EventHandler DataSourceChanged;

        //實作方法
        /// <summary>
        /// 依路徑,回傳該路徑下節點的 View
        /// </summary>
        /// <param name="viewPath">階層式路徑</param>
        /// <returns></returns>
        public HierarchicalDataSourceView GetHierarchicalView(string viewPath)
        {
            return new MyHierarchicalDataSourceView(this, viewPath);
        }

        readonly DataView dvOriginal;    // 將傳進來的 dataset 轉成 dataview
        public string idColumnName { get; private set; }
        public string pidColumnName { get; private set; }
        public string rootValue { get; private set; }

        /// <summary>
        /// 初始化 MyHierarchicalDataSource 類別的新執行個體
        /// </summary>
        /// <param name="dataSet"></param>
        /// <param name="idColumnName">識別碼欄位名稱</param>
        /// <param name="pidColumnName">父層欄位名稱</param>
        /// <param name="rootValue">根的值</param>
        public MyHierarchicalDataSource(DataTable dtOriginal, string idColumnName, string pidColumnName, string rootValue)
        {
            this.dvOriginal = dtOriginal.DefaultView;
            this.idColumnName = idColumnName;
            this.pidColumnName = pidColumnName;
            this.rootValue = rootValue;
        }

        // 取得 childNode 的 viewPath
        internal string GetChildrenViewPath(string viewPath, DataRowView row)
        {
            return viewPath + "\\" + row[idColumnName].ToString();
        }

        // 取得 parentNode 的 viewPath
        internal string GetParentViewPath(string viewPath)
        {
            return viewPath.Substring(0, viewPath.LastIndexOf("\\"));
        }

        // 取得某一節點的 Parent 節點
        internal DataRowView GetParentRow(DataRowView row)
        {
            dvOriginal.RowFilter = GetFilter(idColumnName, row[pidColumnName].ToString()); 
            DataRowView parentRow = dvOriginal[0];
            dvOriginal.RowFilter = "";
            return parentRow;
        }

        // 判斷某一節點是否有子節點
        internal bool HasChildren(DataRowView row)
        {
            dvOriginal.RowFilter = GetFilter(idColumnName, row[pidColumnName].ToString());
            bool hasChildren = dvOriginal.Count > 0;
            dvOriginal.RowFilter = "";
            return hasChildren;
        }

        internal string GetFilter(string columnName, object columnValue)
        {
            if (columnValue is string)
                return String.Format("[{0}] = '{1}'", columnName, columnValue.ToString().Replace("'", "''"));
            else
                return String.Format("[{0}] = {1}", columnName, columnValue);
        }

        #region private classes that implement further interfaces

        /// <summary>
        /// 以階層式資料結構,表示 System.Web.UI.HierarchicalDataSourceControl 控制項的節點或節點集合的資料檢視
        /// </summary>
        private class MyHierarchicalDataSourceView : HierarchicalDataSourceView
        {
            public string viewPath { get; private set; }
            public MyHierarchicalDataSource hiDataSource { get; private set; }
            public MyHierarchicalDataSourceView(MyHierarchicalDataSource hiDataSource, string viewPath)
            {
                this.hiDataSource = hiDataSource;
                this.viewPath = viewPath;
            }

            /// <summary>
            /// 取得檢視中的所有資料項目清單。
            /// </summary>
            /// <returns>可列舉階層式集合的介面</returns>
            public override IHierarchicalEnumerable Select()
            {
                return new HierarchicalEnumerable(hiDataSource, viewPath);
            }
        }

        /// <summary>
        /// 可列舉的階層式集合
        /// </summary>
        private class HierarchicalEnumerable : IHierarchicalEnumerable
        {
            public string viewPath { get; private set; }
            public MyHierarchicalDataSource hiDataSource { get; private set; }
            public HierarchicalEnumerable(MyHierarchicalDataSource hiDataSource, string viewPath)
            {
                this.hiDataSource = hiDataSource;
                this.viewPath = viewPath;
            }

            //傳回指定列舉項目的階層式結構的資料項目
            public IHierarchyData GetHierarchyData(object enumeratedItem)
            {
                DataRowView row = (DataRowView)enumeratedItem;
                return new HierarchyData(hiDataSource, viewPath, row);
            }

            //傳回會逐一查看集合的列舉程式
            public IEnumerator GetEnumerator()
            {
                if (viewPath == "") // find root
                {
                    hiDataSource.dvOriginal.RowFilter = hiDataSource.GetFilter(hiDataSource.pidColumnName, hiDataSource.rootValue);
                }
                else
                {
                    string lastID = viewPath.Substring(viewPath.LastIndexOf("\\") + 1);
                    hiDataSource.dvOriginal.RowFilter = hiDataSource.GetFilter(hiDataSource.pidColumnName, lastID);
                }

                IEnumerator enumerator = hiDataSource.dvOriginal.ToTable().DefaultView.GetEnumerator();
                hiDataSource.dvOriginal.RowFilter = "";

                return enumerator;
            }
        }

        /// <summary>
        /// 階層式結構的資料項目
        /// </summary>
        private class HierarchyData : IHierarchyData
        {
            public string viewPath { get; private set; }
            public MyHierarchicalDataSource hiDataSource { get; private set; }
            public DataRowView row { get; private set; }

            //Constructor
            public HierarchyData(MyHierarchicalDataSource hiDataSource, string viewPath, DataRowView row)
            {
                this.hiDataSource = hiDataSource;
                this.viewPath = viewPath;
                this.row = row;
            }

            #region 必要實作內容

            public IHierarchicalEnumerable GetChildren()
            {
                return new HierarchicalEnumerable(hiDataSource, hiDataSource.GetChildrenViewPath(viewPath, row));
            }

            public IHierarchyData GetParent()
            {
                return new HierarchyData(hiDataSource, hiDataSource.GetParentViewPath(viewPath), hiDataSource.GetParentRow(row));
            }

            public bool HasChildren
            {
                get { return hiDataSource.HasChildren(row); }
            }

            public object Item
            {
                get { return row; }
            }

            public string Path
            {
                get { return viewPath; }
            }

            public string Type
            {
                get { return typeof(DataRowView).ToString(); }
            }

            #endregion
        }

        #endregion
    }

動態載入 TreeView 節點資料

上面介紹的方法,不管是使用靜態資料或者使用繫結資料庫,該 TreeView 中的資料都會在一開始就全部載入到控制項中,如果資料量較大的話,效能可能不會太好,這時就可以考量採用動態載入節點資料的方式。

動態載入節點的方法在一開始時只載入根節點的資料, 同時將 TreeNodePopulateOnDemand 屬性和 TreeViewPopulateNodesFromClient 屬性都設定為 true , 表示將等到 TreeView.TreeNodePopulate 事件發生時,才動態載入子節點。

PopulateOnDemand 預設值為 false ,PopulateNodesFromClient 預設值為 true 。

<asp:TreeView ID="TreeView1" runat="server" 
        PopulateNodesFromClient="true"
        OnTreeNodePopulate="TreeView1_TreeNodePopulate" >
    </asp:TreeView>
protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            //此處只取得根節點資料,回傳格式 ( OrgName, ID, ParentID, ChildCount )
            DataTable dtOrg = RjCommonDB.GetChildOrg("00000000-0000-0000-0000-000000000000");

            foreach (DataRow row in dtOrg.Rows)
            {
                TreeNode treeNode = CreateNode(row["ID"].ToString(), row["OrgName"].ToString(), int.Parse(row["ChildCount"].ToString()));
                TreeView1.Nodes.Add(treeNode);
            }
        }
    }

    // 建立子節點
    private void PopulateChildNode(TreeNode ParentNode)
    {
        DataTable dtOrg = RjCommonDB.GetChildOrg(ParentNode.Value);
        foreach (DataRow row in dtOrg.Rows)
        {
            TreeNode treeNode = CreateNode(row["ID"].ToString(), row["OrgName"].ToString(), int.Parse(row["ChildCount"].ToString()));
            ParentNode.ChildNodes.Add(treeNode);
        }
    }
    // 節點屬性設定
    private TreeNode CreateNode(string nodeValue, string nodeText, int ChildCount)
    {
        TreeNode node = new TreeNode(); ;
        node.Text = nodeText;
        node.Value = nodeValue;
        node.Expanded = false;
        node.PopulateOnDemand = ChildCount > 0; //若有子節點,才設定成 PopulateOnDemand
        return node;
    }
    // 當發生 NodePopulate 事件時,才由資料庫載入子節點
    protected void TreeView1_TreeNodePopulate(object sender, TreeNodeEventArgs e)
    {
        TreeNode node = e.Node;
        PopulateChildNode(node);
    }

沒有留言:

張貼留言