Silverlight中使用递归构造关系图

2016-08-20 11:07:21来源:http://wengyuli.blog.51cto.com/44136/587171作者:wengyuli人点击


这两天遇到一个问题,项目中需要在silverlight中使用连接图的方式来显示任务之间的关系,总体有父子和平行两种,昨天在改同事的代码,一直出问题,索性晚上写了一下实现方法。


需求:

有一个List对象中存了若干个Task,这些Task对象通过ParentID属性进行关联,现在要求将这个List中的任务使用图的方式形成如父子关系和平行关系的图示例如下图:



实现方法思考

刚开始接到这个任务我就想着递归应该可以搞定了,但是仔细考虑才发现每个任务的子任务需要在一定区域内才行,需要计算子级和子级之间的距离,如果使用递归,例如上图的元素“12”的位置就没有办法很好确定了。


我决定将途中的节点抽象为一个类,这个类至少应该含有上边界top,左边届left及节点的名称等属性,然后从这个List对象中构造出每个节点的属性。


实现步骤

1,首先我们为图模拟一个数据源,注意其中的任务是通过ParentID关联的



代码

privatestaticList<Task>listTask;
publicMainPage()
{
InitializeComponent();
listTask=newList<Task>();
listTask.Add(newTask(){ID=1,ParentID=0,Name="1"});
listTask.Add(newTask(){ID=2,ParentID=1,Name="11"});
listTask.Add(newTask(){ID=3,ParentID=1,Name="12"});
listTask.Add(newTask(){ID=4,ParentID=2,Name="21"});
listTask.Add(newTask(){ID=5,ParentID=2,Name="22"});
listTask.Add(newTask(){ID=6,ParentID=3,Name="31"});
listTask.Add(newTask(){ID=7,ParentID=3,Name="32"});
listTask.Add(newTask(){ID=8,ParentID=3,Name="33"});
listTask.Add(newTask(){ID=9,ParentID=4,Name="42"});
listTask.Add(newTask(){ID=10,ParentID=4,Name="42"});
listTask.Add(newTask(){ID=11,ParentID=3,Name="34"});
listTask.Add(newTask(){ID=12,ParentID=5,Name="51"});
listTask.Add(newTask(){ID=13,ParentID=8,Name="81"});
this.Loaded+=newRoutedEventHandler(MainPage_Loaded);
}





2,然后我们为要生成的图中节点构造一个类



代码

classTaskPro
{
publicTasktask{set;get;}
publicdoubletop{set;get;}
publicdoubleleft{set;get;}
publicintindex{set;get;}//这是为了找到节点在某层的位置来计算left
}





3,使用递归将List中的数据做初步整理,存入一个List<TaskPro>中,此时节点对象将具备top属性,上边距搞定。



代码

voidAddMethod(Tasktask)
{
if(task.ParentID==0)
{
listOfTaskPro.Add(newTaskPro(){task=task,top=0,index=0,left=0});
}
else
{
vart=listTask.Where(m=>m.ID==task.ParentID).FirstOrDefault();
vartpro=listOfTaskPro.Where(m=>m.task.ID==t.ID).FirstOrDefault();
listOfTaskPro.Add(newTaskPro(){task=task,index=0,top=tpro.top+50,left=0});
}
foreach(TasktinlistTask.Where(m=>m.ParentID==task.ID).ToList())
{
AddMethod(t);
}
}





4,我们需要算出节点对象的左边距,在第3步中我没能找到方法,于是想到利用每一级的元素个数来计算每个节点的位置,然后使用每一级的平均节点距离*节点的索引便可得到left



代码

//构造各层及数量
foreach(TaskProtinlistOfTaskPro)
{
boolIsExist=false;
foreach(TaskCounttcinlistTopAndTasks)
{
IsExist=tc.Top==t.top?true:false;
}
if(!IsExist)
{
listTopAndTasks.Add(newTaskCount(){Top=t.top,Tasks=newList<Task>()});
}
vartopAndTasks=listTopAndTasks.Where(m=>m.Top==t.top).FirstOrDefault();
topAndTasks.Tasks.Add(t.task);
}
//构造index
foreach(TaskProtinlistOfTaskPro)
{
for(inti=0;i<listTopAndTasks.Count;i++)
{
for(intj=0;j<listTopAndTasks[i].Tasks.Count;j++)
{
if(listTopAndTasks[i].Tasks[j].ID==t.task.ID)
{
t.index=j+1;
}
}
}
}
//构造left
for(inti=0;i<listOfTaskPro.Count;i++)
{
if(listOfTaskPro[i].task.ParentID==0)
{
listOfTaskPro[i].left=this.canvas1.Width/2;
}
else
{
varchildCount=listOfTaskPro.Where(m=>m.task.ParentID==listOfTaskPro[i].task.ParentID).Count();
varparentLeft=listOfTaskPro.Where(m=>m.task.ID==listOfTaskPro[i].task.ParentID).FirstOrDefault().left;
varperLength=parentLeft*1.5/(childCount+1);
listOfTaskPro[i].left=listOfTaskPro[i].index*perLength;
}
}





5,至此,节点对象已经具备了left,top属性,我们只需要找到每个节点的父节点即可将两个几点的坐标确定,进而进行划线的操作了。



代码

foreach(TaskProtinlistOfTaskPro)
{
AddBtn(t.task.Name,t.left,t.top);
if(t.task.ParentID!=0)
{
TaskProtp=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添加按钮及线条
doublebuttonHeight=20;
doublebuttonWidth=50;

voidAddBtn(stringcontent,doubleleft,doubletop)
{
Buttonbtn=newButton();
btn.Content=content;
btn.Width=buttonWidth;
btn.Height=buttonHeight;
this.canvas1.Children.Add(btn);
Canvas.SetLeft(btn,left);
Canvas.SetTop(btn,top);
}

//画线方法,只需要有起始亮点的坐标即可

voidAddLine(doublestartLeft,doublestartTop,doubleendLeft,doubleendTop)
{
Pathp=newPath();
LineGeometrygeometry=newLineGeometry();
SolidColorBrushbrush=newSolidColorBrush();
brush.Color=Colors.Black;
geometry.StartPoint=newPoint(startLeft,startTop);
geometry.EndPoint=newPoint(endLeft,endTop);
p.Data=geometry;
p.Stroke=brush;
p.StrokeThickness=1;
canvas1.Children.Add(p);
}
#endregion





运行一下,如上图。


之前没有使用递归的方法是只有这样的:


代码

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Net;
usingSystem.Windows;
usingSystem.Windows.Controls;
usingSystem.Windows.Documents;
usingSystem.Windows.Input;
usingSystem.Windows.Media;
usingSystem.Windows.Media.Animation;
usingSystem.Windows.Shapes;

namespaceSilverlightApplication2
{
publicclassTask
{
publicintID{set;get;}
publicintParentID{set;get;}
publicstringName{set;get;}
}
publicpartialclassMainPage:UserControl
{
privatestaticList<Task>listTask;
publicMainPage()
{
InitializeComponent();
listTask=newList<Task>();
listTask.Add(newTask(){ID=1,ParentID=0,Name="1"});
listTask.Add(newTask(){ID=2,ParentID=1,Name="11"});
listTask.Add(newTask(){ID=3,ParentID=1,Name="12"});
listTask.Add(newTask(){ID=4,ParentID=2,Name="21"});
listTask.Add(newTask(){ID=5,ParentID=2,Name="22"});
listTask.Add(newTask(){ID=6,ParentID=3,Name="31"});
listTask.Add(newTask(){ID=7,ParentID=3,Name="32"});
listTask.Add(newTask(){ID=8,ParentID=3,Name="33"});
listTask.Add(newTask(){ID=9,ParentID=4,Name="42"});
listTask.Add(newTask(){ID=10,ParentID=4,Name="42"});
listTask.Add(newTask(){ID=11,ParentID=3,Name="34"});
listTask.Add(newTask(){ID=12,ParentID=5,Name="51"});
listTask.Add(newTask(){ID=13,ParentID=8,Name="81"});
this.Loaded+=newRoutedEventHandler(MainPage_Loaded);
}
voidMainPage_Loaded(objectsender,RoutedEventArgse)
{
AddAll();
}
classTaskPro
{
publicTasktask{set;get;}
publicdoubletop{set;get;}
publicdoubleleft{set;get;}
publicintindex{set;get;}
}
classTaskCount
{
publicdoubleTop{set;get;}
publicList<Task>Tasks{set;get;}
}
staticList<TaskPro>listTaskPro=newList<TaskPro>();
staticList<TaskCount>listTopAndTasks=newList<TaskCount>();
voidAddAll()
{
foreach(TasktinlistTask)
{
if(t.ParentID==0)
{
listTaskPro.Add(newTaskPro(){task=t,index=1,left=this.canvas1.Width/2,top=0});
}
else
{
for(inti=0;i<listTaskPro.Count;i++)
{
if(t.ParentID==listTaskPro[i].task.ID)
{
listTaskPro.Add(newTaskPro(){task=t,top=listTaskPro[i].top+80,index=0,left=0});
}
}
}
}

#region汇总层及层内的元素个数
foreach(TaskProtinlistTaskPro)
{
boolIsExist=false;
foreach(TaskCounttcinlistTopAndTasks)
{
if(tc.Top==t.top)
{
IsExist=true;
}
}
if(!IsExist)
{
listTopAndTasks.Add(newTaskCount(){Top=t.top,Tasks=newList<Task>()});
}
vartopAndTasks=listTopAndTasks.Where(m=>m.Top==t.top).FirstOrDefault();
topAndTasks.Tasks.Add(t.task);
}
#endregion

foreach(TaskProtinlistTaskPro)
{
for(inti=0;i<listTopAndTasks.Count;i++)
{
for(intj=0;j<listTopAndTasks[i].Tasks.Count;j++)
{
if(listTopAndTasks[i].Tasks[j].ID==t.task.ID)
{
t.index=j+1;
}
}
}
}

for(inti=0;i<listTaskPro.Count;i++)
{
if(listTaskPro[i].task.ParentID==0)
{
listTaskPro[i].left=this.canvas1.Width/2;
}
else
{
varchildCount=listTaskPro.Where(m=>m.task.ParentID==listTaskPro[i].task.ParentID).Count();
varparentLeft=listTaskPro.Where(m=>m.task.ID==listTaskPro[i].task.ParentID).FirstOrDefault().left;
varperLength=parentLeft*1.5/(childCount+1);
listTaskPro[i].left=listTaskPro[i].index*perLength;
}
}
foreach(TaskProtinlistTaskPro)
{
AddBtn(t.task.Name,t.left,t.top);
if(t.task.ParentID!=0)
{
TaskProtp=listTaskPro.Where(m=>m.task.ID==t.task.ParentID).FirstOrDefault();
AddLine(tp.left+buttonWidth/2,tp.top+buttonHeight,t.left+buttonWidth/2,t.top);
}
}
}
doublebuttonHeight=20;
doublebuttonWidth=50;
voidAddBtn(stringcontent,doubleleft,doubletop)
{
Buttonbtn=newButton();
btn.Content=content;
btn.Width=buttonWidth;
btn.Height=buttonHeight;
this.canvas1.Children.Add(btn);
Canvas.SetLeft(btn,left);
Canvas.SetTop(btn,top);
}

voidAddLine(doublestartLeft,doublestartTop,doubleendLeft,doubleendTop)
{
Pathp=newPath();
LineGeometrygeometry=newLineGeometry();
SolidColorBrushbrush=newSolidColorBrush();

brush.Color=Colors.Black;
geometry.StartPoint=newPoint(startLeft,startTop);
geometry.EndPoint=newPoint(endLeft,endTop);
p.Data=geometry;
p.Stroke=brush;
p.StrokeThickness=1;

canvas1.Children.Add(p);
}
}
}





如果您有更好的方法,希望不吝赐教,我正在不断修正代码,希望能更简洁。


最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台