Silverlight中使用递归构造关系图
这两天遇到一个问题,项目中需要在silverlight中使用连接图的方式来显示任务之间的关系,总体有父子和平行两种,昨天在改同事的代码,一直出问题,索性晚上写了一下实现方法。
需求:
有一个List对象中存了若干个Task,这些Task对象通过ParentID属性进行关联,现在要求将这个List中的任务使用图的方式形成如父子关系和平行关系的图示例如下图:
实现方法思考
刚开始接到这个任务我就想着递归应该可以搞定了,但是仔细考虑才发现每个任务的子任务需要在一定区域内才行,需要计算子级和子级之间的距离,如果使用递归,例如上图的元素“12”的位置就没有办法很好确定了。
我决定将途中的节点抽象为一个类,这个类至少应该含有上边界top,左边届left及节点的名称等属性,然后从这个List对象中构造出每个节点的属性。
实现步骤
1,首先我们为图模拟一个数据源,注意其中的任务是通过ParentID关联的
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,然后我们为要生成的图中节点构造一个类
{
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属性,上边距搞定。
{
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属性,我们只需要找到每个节点的父节点即可将两个几点的坐标确定,进而进行划线的操作了。
{
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,添加按钮及划线的方法
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.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);
}
}
}
如果您有更好的方法,希望不吝赐教,我正在不断修正代码,希望能更简洁。
有朋友问我代码在哪里,很抱歉,当时可能没有来得及上传代码,刚才整理了一下:
https://files.cnblogs.com/wengyuli/DemoSL.rar
2012.2.5