天天看点

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>

继续阅读