Silverlight中使用递归构造关系图

时间:2022-05-15 05:26:04

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

需求:

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

Silverlight中使用递归构造关系图

实现方法思考

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

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

实现步骤

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

 

Silverlight中使用递归构造关系图Silverlight中使用递归构造关系图代码
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,然后我们为要生成的图中节点构造一个类

 

Silverlight中使用递归构造关系图Silverlight中使用递归构造关系图代码
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属性,上边距搞定。

 

Silverlight中使用递归构造关系图Silverlight中使用递归构造关系图代码
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

 

Silverlight中使用递归构造关系图Silverlight中使用递归构造关系图代码
// 构造各层及数量 
             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属性,我们只需要找到每个节点的父节点即可将两个几点的坐标确定,进而进行划线的操作了。

 

Silverlight中使用递归构造关系图Silverlight中使用递归构造关系图代码
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,添加按钮及划线的方法

 

Silverlight中使用递归构造关系图Silverlight中使用递归构造关系图代码
#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

 

运行一下,如上图。

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

Silverlight中使用递归构造关系图Silverlight中使用递归构造关系图代码
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); 
        } 
    } 
}

 

 

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

 

有朋友问我代码在哪里,很抱歉,当时可能没有来得及上传代码,刚才整理了一下:

 http://files.cnblogs.com/wengyuli/DemoSL.rar

 

2012.2.5