在項目中經常會遇到從數據庫查詢數據綁定到TreeVIew上,這時我們需要將查詢出來的數據轉換成樹形結構數據,每次寫覺得工作會很重復,所以寫了一個通用的轉換類。
第一步,我們需要建一個基類,這個基類的意義主要是擴展數據庫實體類做連接用,用於確定樹形結構中節點與子項的關系,
其中Parent為當前節點的父節點,Children為當前節點的子節點,IsLinked是判斷當前節點是否已連接,用於防止數據中有循環依賴導致創建樹的時候形成死循環。
TreeModel基類
public class TreeBase<T> { private bool isLinked = false; /// <summary> /// 是否已創建連接 /// </summary> public bool IsLinked { get { return isLinked; } set { isLinked = value; } } /// <summary> /// 父節點 /// </summary> public T Parent { get; set; } /// <summary> /// 子節點 /// </summary> public ObservableCollection<T> Children { get; set; } }
第二步,我們需要創建一個連接方法,將輸入的扁平狀數據轉換為樹狀結構,我希望在使用的時候可以自己指定實體的哪個屬性為ID,哪個為父ID
,這里我們使用了Expression,這樣就可以使用linq表達式去指定屬性了,剩下的就是利用反射獲取實體的值與遞歸連接了,這樣一個簡單的通用創建樹的方法就有了。
public class TreeHelper { /// <summary> /// 創建樹 /// </summary> /// <typeparam name="T">實體類型</typeparam> /// <param name="root">根節點</param> /// <param name="list">所有數據</param> /// <param name="idProperty">節點唯一標識屬性表達式</param> /// <param name="parentIdProperty">父節點屬性表達式</param> public static void CreateTree<T>(T root, IList<T> list, string idPropertyName, string parentIdPropertyName) where T : TreeBase<T> { root.Children = new ObservableCollection<T>(); list.Where(e => (string)GetPropertyValue(e, parentIdPropertyName) == (string)GetPropertyValue(root, idPropertyName) && !e.IsLinked).ToList().ForEach(e => { root.Children.Add(e); e.IsLinked = true; }); foreach (var leaf in root.Children) { leaf.Parent = root; CreateTree<T>(leaf, list, idPropertyName, parentIdPropertyName); } } /// <summary> /// 創建多個根節點的樹 /// </summary> /// <typeparam name="T">實體類型</typeparam> /// <param name="root">根節點</param> /// <param name="list">所有數據</param> /// <param name="idProperty">節點唯一標識屬性表達式</param> /// <param name="parentIdProperty">父節點屬性表達式</param> public static ObservableCollection<T> CreateTree<T>(IList<T> list, Expression<Func<T, object>> idProperty, Expression<Func<T, object>> parentIdProperty) where T : TreeBase<T> { //查找父節點不存在的leaf,作偽根節點 var roots = new ObservableCollection<T>(); var idPropertyName = GetMemberName(idProperty); var parentIdPropertyName = GetMemberName(parentIdProperty); list.Where(e => list.Count(item => (string)GetPropertyValue(item, idPropertyName) == (string)GetPropertyValue(e, parentIdPropertyName)) == 0).ToList().ForEach(e => roots.Add(e)); foreach (var root in roots) { CreateTree<T>(root, list, idPropertyName, parentIdPropertyName); } return roots; } private static object GetPropertyValue<T>(T t, string propertyName) { return t.GetType().GetProperty(propertyName).GetValue(t, null); } private static string GetMemberName<T, TMember>(Expression<Func<T, TMember>> propertySelector) { var propertyExp = propertySelector.Body as MemberExpression; if (propertyExp == null) { throw new ArgumentException("不合理的表達式!"); } return propertyExp.Member.Name; } }
使用示例:
先聲明一個綁定實體,繼承自TreeBase
public class Item : TreeBase<Item>
{
public string Id{get;set;}
public string ParentID{get;set;}
public string DisplayText{get;set;}
}
創建測試數據
IList<Item> data = new List<Item>(); var group1= new Item(){ Id = Guid.NewGuid().ToString(), DisplayText="Root"}; var item1 = new Item { Id = Guid.NewGuid().ToString(), ParentId = group1.Id, DisplayText = "Leaf1" }; var item2 = new Item { Id = Guid.NewGuid().ToString(), ParentId = group1.Id, DisplayText = "Leaf2" }; var item3 = new Item { Id = Guid.NewGuid().ToString(), ParentId = group1.Id, DisplayText = "Leaf3" }; var item4 = new Item() { Id = Guid.NewGuid().ToString(), ParentId = item3.Id, DisplayText = "Leaf3:Leaf1" }; var item5 = new Item() { Id = Guid.NewGuid().ToString(), ParentId = item3.Id, DisplayText = "Leaf3:Leaf2" }; var item6 = new Item() { Id = Guid.NewGuid().ToString(), ParentId = item3.Id, DisplayText = "Leaf3:Leaf3" }; data.Add(group1); data.Add(item1); data.Add(item2); data.Add(item3); data.Add(item4); data.Add(item5); data.Add(item6);
界面模板綁定
<TreeView Height="245" HorizontalAlignment="Left" Margin="46,35,0,0" Name="treeView1" VerticalAlignment="Top" Width="156" > <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}"> <TextBlock Text="{Binding DisplayText}"></TextBlock> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView>
后台數據綁定
treeView1.ItemsSource = TreeHelper.CreateTree<Item>(data, item => item.Id, item => item.ParentId);
以后綁定樹形數據是不是很簡單呢?
這種方法不適應主鍵為Guid的數據類型
因為以下代碼無法獲取Guid類型的名稱,歡迎各位大叔,大嬸,大神們指點
private static string GetMemberName<T, TMember>(Expression<Func<T, TMember>> propertySelector) { var propertyExp = propertySelector.Body as MemberExpression; if (propertyExp == null) { throw new ArgumentException("不合理的表達式!"); } return propertyExp.Member.Name; }