Silverlight中使用遞歸構造關系圖

作者: Leon Weng  來源: 博客園  發布時間: 2010-08-08 20:44  閱讀: 708 次  推薦: 0   原文鏈接   [收藏]  

  這兩天遇到一個問題,項目中需要在silverlight中使用連接圖的方式來顯示任務之間的關系,總體有父子和平行兩種,昨天在改同事的代碼,一直出問題,索性晚上寫了一下實現方法。

  需求

  有一個List對象中存了若干個Task,這些Task對象通過ParentID屬性進行關聯,現在要求將這個List中的任務使用圖的方式形成如父子關系和平行關系的圖示例如下圖:

image

  實現方法思考

  剛開始接到這個任務我就想著遞歸應該可以搞定了,但是仔細考慮才發現每個任務的子任務需要在一定區域內才行,需要計算子級和子級之間的距離,如果使用遞歸,例如上圖的元素“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 { setget; } 
            
public double top { setget; } 
            
public double left { setget; } 
            
public int index { setget; }//這是為了找到節點在某層的位置來計算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 { setget; } 
        
public int ParentID { setget; } 
        
public string Name { setget; } 
    } 
    
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 { setget; } 
            
public double top { setget; } 
            
public double left { setget; } 
            
public int index { setget; } 
        } 
        
class TaskCount 
        { 
            
public double Top { setget; } 
            
public List<Task> Tasks { setget; } 
        } 
        
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); 
        } 
    } 
}
0
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()