这两天遇到一个问题,项目中需要在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);
}
}
}
如果您有更好的方法,希望不吝赐教,我正在不断修正代码,希望能更简洁。
有朋友问我代码在哪里,很抱歉,当时可能没有来得及上传代码,刚才整理了一下:
http://files.cnblogs.com/wengyuli/DemoSL.rar
2012.2.5