天天看點

Silverlight中使用遞歸構造關系圖

這兩天遇到一個問題,項目中需要在silverlight中使用連接配接圖的方式來顯示任務之間的關系,總體有父子和平行兩種,昨天在改同僚的代碼,一直出問題,索性晚上寫了一下實作方法。

有一個List對象中存了若幹個Task,這些Task對象通過ParentID屬性進行關聯,現在要求将這個List中的任務使用圖的方式形成如父子關系和平行關系的圖示例如下圖:

<a href="http://images.cnblogs.com/cnblogs_com/wengyuli/WindowsLiveWriter/Silverlight_1D2E/image_2.png"></a>

剛開始接到這個任務我就想着遞歸應該可以搞定了,但是仔細考慮才發現每個任務的子任務需要在一定區域内才行,需要計算子級和子級之間的距離,如果使用遞歸,例如上圖的元素“12”的位置就沒有辦法很好确定了。

我決定将途中的節點抽象為一個類,這個類至少應該含有上邊界top,左邊屆left及節點的名稱等屬性,然後從這個List對象中構造出每個節點的屬性。

1,首先我們為圖模拟一個資料源,注意其中的任務是通過ParentID關聯的

代碼

private static List&lt;Task&gt; listTask; 

        public MainPage() 

        { 

            InitializeComponent(); 

            listTask = new List&lt;Task&gt;(); 

            listTask.Add(new Task() { ID = 1, ParentID = 0, Name = "1" }); 

            listTask.Add(new Task() { ID = 2, ParentID = 1, Name = "11" }); 

            listTask.Add(new Task() { ID = 3, ParentID = 1, Name = "12" }); 

            listTask.Add(new Task() { ID = 4, ParentID = 2, Name = "21" }); 

            listTask.Add(new Task() { ID = 5, ParentID = 2, Name = "22" }); 

            listTask.Add(new Task() { ID = 6, ParentID = 3, Name = "31" }); 

            listTask.Add(new Task() { ID = 7, ParentID = 3, Name = "32" }); 

            listTask.Add(new Task() { ID = 8, ParentID = 3, Name = "33" }); 

            listTask.Add(new Task() { ID = 9, ParentID = 4, Name = "42" }); 

            listTask.Add(new Task() { ID = 10, ParentID =4, Name = "42" }); 

            listTask.Add(new Task() { ID = 11, ParentID =3, Name = "34" }); 

            listTask.Add(new Task() { ID = 12, ParentID = 5, Name = "51" }); 

            listTask.Add(new Task() { ID = 13, ParentID = 8, Name = "81" }); 

            this.Loaded += new RoutedEventHandler(MainPage_Loaded); 

        }

2,然後我們為要生成的圖中節點構造一個類

class TaskPro 

            public Task task { set; get; } 

            public double top { set; get; } 

            public double left { set; get; } 

            public int index { set; get; }//這是為了找到節點在某層的位置來計算left 

3,使用遞歸将List中的資料做初步整理,存入一個List&lt;TaskPro&gt;中,此時節點對象将具備top屬性,上邊距搞定。

void AddMethod(Task task) 

            if (task.ParentID == 0) 

            { 

                listOfTaskPro.Add(new TaskPro() { task = task, top = 0, index = 0, left = 0  }); 

            } 

            else 

                var t=listTask.Where(m=&gt;m.ID==task.ParentID).FirstOrDefault(); 

                var tpro=listOfTaskPro.Where(m=&gt;m.task.ID==t.ID).FirstOrDefault(); 

                listOfTaskPro.Add(new TaskPro() { task=task, index=0, top=tpro.top+50, left=0 }); 

            foreach (Task t in listTask.Where(m=&gt;m.ParentID==task.ID).ToList()) 

                AddMethod(t);          

4,我們需要算出節點對象的左邊距,在第3步中我沒能找到方法,于是想到利用每一級的元素個數來計算每個節點的位置,然後使用每一級的平均節點距離*節點的索引便可得到left

//構造各層及數量 

            foreach (TaskPro t in listOfTaskPro) 

                bool IsExist = false; 

                foreach (TaskCount tc in listTopAndTasks) 

                { 

                    IsExist = tc.Top==t.top?true:false; 

                } 

                if (!IsExist) 

                    listTopAndTasks.Add(new TaskCount() { Top = t.top, Tasks = new List&lt;Task&gt;() }); 

                var topAndTasks = listTopAndTasks.Where(m =&gt; m.Top == t.top).FirstOrDefault(); 

                topAndTasks.Tasks.Add(t.task); 

            //構造index 

                for (int i = 0; i &lt; listTopAndTasks.Count; i++) 

                    for (int j = 0; j &lt; listTopAndTasks[i].Tasks.Count; j++) 

                    { 

                        if (listTopAndTasks[i].Tasks[j].ID == t.task.ID) 

                        { 

                            t.index = j + 1; 

                        } 

                    } 

            //構造left 

            for (int i = 0; i &lt; listOfTaskPro.Count; i++) 

                if (listOfTaskPro[i].task.ParentID == 0) 

                    listOfTaskPro[i].left = this.canvas1.Width / 2; 

                else 

                    var childCount = listOfTaskPro.Where(m =&gt; m.task.ParentID == listOfTaskPro[i].task.ParentID).Count(); 

                    var parentLeft = listOfTaskPro.Where(m =&gt; m.task.ID == listOfTaskPro[i].task.ParentID).FirstOrDefault().left; 

                    var perLength = parentLeft * 1.5 / (childCount + 1); 

                    listOfTaskPro[i].left = listOfTaskPro[i].index * perLength; 

            }

5,至此,節點對象已經具備了left,top屬性,我們隻需要找到每個節點的父節點即可将兩個幾點的坐标确定,進而進行劃線的操作了。

foreach (TaskPro t in listOfTaskPro) 

                AddBtn(t.task.Name, t.left, t.top); 

                if (t.task.ParentID != 0) 

                    TaskPro tp = listOfTaskPro.Where(m =&gt; m.task.ID == t.task.ParentID).FirstOrDefault(); 

                    AddLine(tp.left + buttonWidth / 2, tp.top + buttonHeight, t.left + buttonWidth / 2, t.top); 

6,添加按鈕及劃線的方法

#region 添加按鈕及線條 

        double buttonHeight = 20; 

        double buttonWidth = 50; 

        void AddBtn(string content, double left, double top) 

            Button btn = new Button(); 

            btn.Content = content; 

            btn.Width = buttonWidth; 

            btn.Height = buttonHeight; 

            this.canvas1.Children.Add(btn); 

            Canvas.SetLeft(btn, left); 

            Canvas.SetTop(btn, top); 

        } 

//畫線方法,隻需要有起始亮點的坐标即可

        void AddLine(double startLeft, double startTop, double endLeft, double endTop) 

            Path p = new Path(); 

            LineGeometry geometry = new LineGeometry(); 

            SolidColorBrush brush = new SolidColorBrush(); 

            brush.Color = Colors.Black; 

            geometry.StartPoint = new Point(startLeft, startTop); 

            geometry.EndPoint = new Point(endLeft, endTop); 

            p.Data = geometry; 

            p.Stroke = brush; 

            p.StrokeThickness = 1; 

            canvas1.Children.Add(p); 

        #endregion

運作一下,如上圖。

之前沒有使用遞歸的方法是隻有這樣的:

using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Net; 

using System.Windows; 

using System.Windows.Controls; 

using System.Windows.Documents; 

using System.Windows.Input; 

using System.Windows.Media; 

using System.Windows.Media.Animation; 

using System.Windows.Shapes; 

namespace SilverlightApplication2 

    public class Task 

    { 

        public int ID { set; get; } 

        public int ParentID { set; get; } 

        public string Name { set; get; } 

    } 

    public partial class MainPage : UserControl 

        private static List&lt;Task&gt; listTask; 

        void MainPage_Loaded(object sender, RoutedEventArgs e) 

        {             

            AddAll();         

        }        

        class TaskPro 

            public int index { set; get; } 

        class TaskCount 

            public double Top { set; get; } 

            public List&lt;Task&gt; Tasks { set; get; } 

        static List&lt;TaskPro&gt; listTaskPro = new List&lt;TaskPro&gt;(); 

        static List&lt;TaskCount&gt; listTopAndTasks = new List&lt;TaskCount&gt;(); 

        void AddAll() 

            foreach(Task t in listTask) 

                if (t.ParentID == 0) 

                    listTaskPro.Add(new TaskPro() { task = t, index = 1, left = this.canvas1.Width / 2, top = 0 }); 

                    for(int i=0;i&lt;listTaskPro.Count;i++) 

                        if (t.ParentID == listTaskPro[i].task.ID) 

                            listTaskPro.Add(new TaskPro() { task = t, top = listTaskPro[i].top + 80, index = 0, left = 0 }); 

            #region 彙總層及層内的元素個數 

            foreach (TaskPro t in listTaskPro) 

                foreach(TaskCount tc in listTopAndTasks) 

                    if(tc.Top==t.top) 

                        IsExist = true; 

                if(!IsExist) 

                    listTopAndTasks.Add(new TaskCount() { Top=t.top, Tasks=new List&lt;Task&gt;() }); 

                var topAndTasks = listTopAndTasks.Where(m=&gt;m.Top==t.top).FirstOrDefault(); 

                topAndTasks.Tasks.Add(t.task);               

            #endregion 

                for (int i = 0; i &lt; listTopAndTasks.Count;i++ ) 

                    for (int j = 0; j &lt; listTopAndTasks[i].Tasks.Count;j++ ) 

            for (int i = 0; i &lt; listTaskPro.Count; i++) 

                if (listTaskPro[i].task.ParentID == 0) 

                    listTaskPro[i].left = this.canvas1.Width / 2; 

                    var childCount = listTaskPro.Where(m =&gt; m.task.ParentID == listTaskPro[i].task.ParentID).Count(); 

                    var parentLeft = listTaskPro.Where(m =&gt; m.task.ID == listTaskPro[i].task.ParentID).FirstOrDefault().left; 

                    var perLength = parentLeft*1.5 / (childCount + 1); 

                    listTaskPro[i].left=listTaskPro[i].index*perLength;                                     

                if(t.task.ParentID!=0) 

                    TaskPro tp = listTaskPro.Where(m=&gt;m.task.ID==t.task.ParentID).FirstOrDefault(); 

                    AddLine(tp.left+buttonWidth/2, tp.top+buttonHeight, t.left+buttonWidth/2, t.top); 

        void AddBtn(string content,double left,double top) 

        void AddLine(double startLeft,double startTop,double endLeft,double endTop) 

            SolidColorBrush brush = new SolidColorBrush();             

}

如果您有更好的方法,希望不吝賜教,我正在不斷修正代碼,希望能更簡潔。

<b>     本文轉自wengyuli 51CTO部落格,原文連結:http://blog.51cto.com/wengyuli/587171</b><b>,如需轉載請自行聯系原作者</b>

繼續閱讀