这两天遇到一个问题,项目中需要在silverlight中使用连接图的方式来显示任务之间的关系,总体有父子和平行两种,昨天在改同事的代码,一直出问题,索性晚上写了一下实现方法。
有一个List对象中存了若干个Task,这些Task对象通过ParentID属性进行关联,现在要求将这个List中的任务使用图的方式形成如父子关系和平行关系的图示例如下图:
刚开始接到这个任务我就想着递归应该可以搞定了,但是仔细考虑才发现每个任务的子任务需要在一定区域内才行,需要计算子级和子级之间的距离,如果使用递归,例如上图的元素“12”的位置就没有办法很好确定了。
我决定将途中的节点抽象为一个类,这个类至少应该含有上边界top,左边届left及节点的名称等属性,然后从这个List对象中构造出每个节点的属性。
1,首先我们为图模拟一个数据源,注意其中的任务是通过ParentID关联的
- private static List<Task> listTask;
- public MainPage()
- {
- InitializeComponent();
- listTask = new List<Task>();
- 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<TaskPro>中,此时节点对象将具备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=>m.ID==task.ParentID).FirstOrDefault();
- var tpro=listOfTaskPro.Where(m=>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=>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<Task>() });
- }
- var topAndTasks = listTopAndTasks.Where(m => m.Top == t.top).FirstOrDefault();
- topAndTasks.Tasks.Add(t.task);
- }
- //构造index
- foreach (TaskPro t in listOfTaskPro)
- {
- for (int i = 0; i < listTopAndTasks.Count; i++)
- {
- for (int j = 0; j < listTopAndTasks[i].Tasks.Count; j++)
- {
- if (listTopAndTasks[i].Tasks[j].ID == t.task.ID)
- {
- t.index = j + 1;
- }
- }
- }
- }
- //构造left
- for (int i = 0; i < listOfTaskPro.Count; i++)
- {
- if (listOfTaskPro[i].task.ParentID == 0)
- {
- listOfTaskPro[i].left = this.canvas1.Width / 2;
- }
- else
- {
- var childCount = listOfTaskPro.Where(m => m.task.ParentID == listOfTaskPro[i].task.ParentID).Count();
- var parentLeft = listOfTaskPro.Where(m => 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 => 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<Task> listTask;
- public MainPage()
- {
- InitializeComponent();
- listTask = new List<Task>();
- 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);
- }
- void MainPage_Loaded(object sender, RoutedEventArgs e)
- {
- AddAll();
- }
- class TaskPro
- {
- public Task task { set; get; }
- public double top { set; get; }
- public double left { set; get; }
- public int index { set; get; }
- }
- class TaskCount
- {
- public double Top { set; get; }
- public List<Task> Tasks { set; get; }
- }
- static List<TaskPro> listTaskPro = new List<TaskPro>();
- static List<TaskCount> listTopAndTasks = new List<TaskCount>();
- 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 });
- }
- else
- {
- for(int i=0;i<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)
- {
- bool IsExist = false;
- foreach(TaskCount tc in listTopAndTasks)
- {
- if(tc.Top==t.top)
- {
- IsExist = true;
- }
- }
- if(!IsExist)
- {
- listTopAndTasks.Add(new TaskCount() { Top=t.top, Tasks=new List<Task>() });
- }
- var topAndTasks = listTopAndTasks.Where(m=>m.Top==t.top).FirstOrDefault();
- topAndTasks.Tasks.Add(t.task);
- }
- #endregion
- foreach (TaskPro t in listTaskPro)
- {
- for (int i = 0; i < listTopAndTasks.Count;i++ )
- {
- for (int j = 0; j < listTopAndTasks[i].Tasks.Count;j++ )
- {
- if (listTopAndTasks[i].Tasks[j].ID == t.task.ID)
- {
- t.index = j + 1;
- }
- }
- }
- }
- for (int i = 0; i < listTaskPro.Count; i++)
- {
- if (listTaskPro[i].task.ParentID == 0)
- {
- listTaskPro[i].left = this.canvas1.Width / 2;
- }
- else
- {
- var childCount = listTaskPro.Where(m => m.task.ParentID == listTaskPro[i].task.ParentID).Count();
- var parentLeft = listTaskPro.Where(m => m.task.ID == listTaskPro[i].task.ParentID).FirstOrDefault().left;
- var perLength = parentLeft*1.5 / (childCount + 1);
- listTaskPro[i].left=listTaskPro[i].index*perLength;
- }
- }
- foreach (TaskPro t in listTaskPro)
- {
- AddBtn(t.task.Name, t.left, t.top);
- if(t.task.ParentID!=0)
- {
- TaskPro tp = listTaskPro.Where(m=>m.task.ID==t.task.ParentID).FirstOrDefault();
- AddLine(tp.left+buttonWidth/2, tp.top+buttonHeight, t.left+buttonWidth/2, t.top);
- }
- }
- }
- 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);
- }
- }
- }