TreeView控件的用法還是有蠻多坑點的,最好記錄一下。
參考項目:
靜態的樹形結構
如果樹形結構的所有子節點都已經確定且不會改動,可以直接在控制層用C#代碼來生成這個TreeView。
var rootItem = new OutlineTreeData { outlineTypeName = "David Weatherbeam", Children= { new OutlineTreeData { outlineTypeName="Alberto Weatherbeam", Children= { new OutlineTreeData { outlineTypeName="Zena Hairmonger", Children= { new OutlineTreeData { outlineTypeName="Sarah Applifunk", } } },new OutlineTreeData { outlineTypeName="Jenny van Machoqueen", Children= { new OutlineTreeData { outlineTypeName="Nick van Machoqueen", }, new OutlineTreeData { outlineTypeName="Matilda Porcupinicus", }, new OutlineTreeData { outlineTypeName="Bronco van Machoqueen", } } } } }, new OutlineTreeData { outlineTypeName="Komrade Winkleford", Children= { new OutlineTreeData { outlineTypeName="Maurice Winkleford", Children= { new OutlineTreeData { outlineTypeName="Divinity W. Llamafoot", } } }, new OutlineTreeData { outlineTypeName="Komrade Winkleford, Jr.", Children= { new OutlineTreeData { outlineTypeName="Saratoga Z. Crankentoe", }, new OutlineTreeData { outlineTypeName="Excaliber Winkleford", } } } } } } };
運行后能看到樹形結構是下面的樣子。

獲取TreeViewItem控件
前台頁面xaml:
<!-- 樹形結構 --> <TreeView x:Name="treeView" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,-10,0,0" ItemsSource="{Binding ItemTreeDataList}" BorderThickness="0" Width="215" Height="210"> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> <Setter Property="FontWeight" Value="Normal" /> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="FontWeight" Value="Bold" /> </Trigger> </Style.Triggers> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}"> <TextBlock x:Name="treeViewItemTB" Text="{Binding itemName}" Tag="{Binding itemId}"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView>
嘗試過在初始化時獲取TreeViewItem,發現都是為Null。
- TreeViewItem item= (TreeViewItem)(myWindow.treeView.ItemContainerGenerator.ContainerFromIndex(0)); // 無法獲取,為Null!
- VisualTreeHelper.GetChild(); // 無法獲取,為Null!
谷歌一下,看到這篇解答,下面這位跟我遇到的情況一樣,用以上方法都無法獲取TreeViewItem。

不過他給出的答案是通過點擊來獲取到被選中的TreeViewItem。
給TreeView默認選中一個TreeViewItem
上面的辦法通過點擊TreeViewItem來從事件中獲得這個控件,但是如果我們想生成TreeView后不靠手動點擊,立馬自動選中一個默認的TreeViewItem呢?注意此時控件還未渲染,通過ItemContainerGenerator無法獲取到控件。
此時只能考慮使用MVVM的綁定機制來獲取控件!因為如果修改數據后,UI的更新是延遲的,無法立刻獲取到前台控件。
綁定:
xaml中TreeView的ItemsSource綁定到ViewModel中的ItemTreeDataList列表,該列表中的元素類型是自定義ItemTreeData實體類。在樣式中綁定好IsExpanded和IsSelected屬性。TreeViewItem模板是綁定到Children屬性,該屬性在ItemTreeData實體類中。
ViewModel:
// Item的樹形結構 private ObservableCollection<ItemTreeData> itemTreeDataList; public ObservableCollection<ItemTreeData> ItemTreeDataList { get { return itemTreeDataList; } set { SetProperty(ref itemTreeDataList, value); } }
ItemTreeData實體類:
public class ItemTreeData // 自定義Item的樹形結構 { public int itemId { get; set; } // ID public string itemName { get; set; } // 名稱 public int itemStep { get; set; } // 所屬的層級 public int itemParent { get; set; } // 父級的ID private ObservableCollection<ItemTreeData> _children = new ObservableCollection<ItemTreeData >(); public ObservableCollection<ItemTreeData> Children { // 樹形結構的下一級列表 get { return _children; } } public bool IsExpanded { get; set; } // 節點是否展開 public bool IsSelected { get; set; } // 節點是否選中 }
關於上面的層級/父級/下一級的概念,假設現在樹形結構為以下結構,從上往下依此定義為根節點、零級節點、一級節點、二級節點。
/* * rootItem * |----zeroTreeItem * |----firstTreeItem * |----secondTreeItem */
控制層在生成TreeView時通過綁定的屬性來設置默認選中的Item,預先將三個層級的Item分別裝在不同的列表中使用。
現在嘗試把二級節點中的第一個Item作為默認選中的Item。關鍵代碼如下:
// 構造輪廓選擇的樹形結構 private void InitTreeView() { // 添加樹形結構 ItemTreeData item = GetTreeData(); myViewModel.ItemTreeDataList.Clear(); myViewModel.ItemTreeDataList.Add(item); } // 構造樹形結構 private ItemTreeData GetTreeData() { /* * rootItem * |----zeroTreeItem * |----firstTreeItem * |----secondTreeItem */ // 根節點 ItemTreeData rootItem = new ItemTreeData(); rootItem.itemId = -1; rootItem.itemName = " -- 請選擇輪廓 -- "; rootItem.itemStep = -1; rootItem.itemParent = -1; rootItem.IsExpanded = true; // 根節點默認展開 rootItem.IsSelected = false; for (int i = 0; i < itemViewModel.ZeroStepList.Count; i++) // 零級分類 { Items zeroStepItem = itemViewModel.ZeroStepList[i]; ItemTreeData zeroTreeItem = new ItemTreeData(); zeroTreeItem.itemId = zeroStepItem.itemId; zeroTreeItem.itemName = zeroStepItem.itemName; zeroTreeItem.itemStep = zeroStepItem.itemSteps; zeroTreeItem.itemParent = zeroStepItem.itemParent; if (i == 0) { zeroTreeItem.IsExpanded = true; // 只有需要默認選中的第一個零級分類是展開的 } zeroTreeItem.IsSelected = false; rootItem.Children.Add(zeroTreeItem); // 零級節點無條件加入根節點 for (int j = 0; j < itemViewModel.FirstStepList.Count; j++) // 一級分類 { Items firstStepItem = itemViewModel.FirstStepList[j]; if (firstStepItem.itemParent == zeroStepItem.itemId) //零級節點添加一級節點 { ItemTreeData firstTreeItem = new ItemTreeData(); firstTreeItem.itemId = firstStepItem.itemId; firstTreeItem.itemName = firstStepItem.itemName; firstTreeItem.itemStep = firstStepItem.itemSteps; firstTreeItem.itemParent = firstStepItem.itemParent; if (j == 0) { firstTreeItem.IsExpanded = true; // 只有需要默認選中的第一個一級分類是展開的 } firstTreeItem.IsSelected = false; zeroTreeItem.Children.Add(firstTreeItem); for (int k = 0; k < itemViewModel.SecondStepList.Count; k++) // 二級分類 { Items secondStepItem = itemViewModel.SecondStepList[k]; if (secondStepItem.itemParent == firstStepItem.itemId) // 一級節點添加二級節點 { ItemTreeData secondTreeItem = new ItemTreeData(); secondTreeItem.itemId = secondStepItem.itemId; secondTreeItem.itemName = secondStepItem.itemName; secondTreeItem.itemStep = secondStepItem.itemSteps; secondTreeItem.itemParent = secondStepItem.itemParent; if (k == 0) { // 默認選中第一個二級分類 secondTreeItem.IsExpanded = true; // 已經沒有下一級了,這個屬性無所謂 secondTreeItem.IsSelected = true; } firstTreeItem.Children.Add(secondTreeItem); } } } } } return rootItem; }
通過初始化時給被綁定的屬性賦值,使得TreeView默認選中了二級節點中的第一個TreeViewItem,效果如下圖:

注意點:
- 只是修改目標節點的IsSelected = true還不夠,還要把它的所有父節點(祖父節點)都設置IsExpanded = true才行!!!
顯示選中TreeViewItem的完整分類路徑
需求是如上圖,要得到字符串"I戶型_1.5-1_a2",即通過下划線連接得到“零級節點_一級節點_二級節點”,然后用一個TextBlock控件顯示出來。注意這里不包含root根節點。
該功能可以寫在樹形結構的選項改變事件中。因為初始化TreeView時就選中了其中一個Item,所以初始化時也會調用到選項改變事件。
// 樹形結構的選項改變事件 private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { ItemTreeData selectedItem = (ItemTreeData)(myWindow.treeView.SelectedItem); if (selectedItem != null) { // UI層找不到,只能在數據層找 List<string> nameList = new List<string>(); int step = selectedItem.itemStep; // 可能值為:-1,0,1,2 int parentId = selectedItem.itemParent; // 臨時保存遍歷中每次使用的Id for (int i = 0; i < step; i++) // 零級分類雖然有父節點root,但是數據層中沒有相應的itemParent值(為-1) { ItemTreeData parent = this.GetTreeDataById(parentId); if (parent != null) { nameList.Add(parent.itemName); parentId = parent.itemParent; } } // 組拼字符串 = 零級名稱 + 一級名稱 + 二級名稱 string text = ""; for (int i = nameList.Count; i > 0; i--) // 倒序遍歷 { if (i == nameList.Count) { text = nameList[i - 1]; } else { text = text + "_" + nameList[i - 1]; } } if (string.IsNullOrEmpty(text)) { myWindow.itemSelectedTB.Text = selectedItem.itemName; } else { myWindow.itemSelectedTB.Text = text + "_" + selectedItem.itemName; } } } // 根據Id,獲得樹形結構中指定的Item private ItemTreeData GetTreeDataById(int targetId) { ItemTreeData data = null; // 是否為根節點 ItemTreeData root = myViewModel.ItemTreeDataList[0]; if (root.itemId == targetId) { data = root; return data; } // 搜索零級大類 foreach (ItemTreeData zeroStepData in root.Children) { if (zeroStepData.itemId == targetId) { data = zeroStepData; return data; } // 搜索一級分類 foreach (ItemTreeData firstStepData in zeroStepData.Children) { if (firstStepData.itemId == targetId) { data = firstStepData; return data; } // 搜索二級分類 foreach (ItemTreeData secondStepData in firstStepData.Children) { if (secondStepData.itemId == targetId) { data = secondStepData; return data; } } } } return data; }
注意點:
- 同樣是找不到TreeViewItem控件!UI層找不到,所以只能在數據層找它關聯的ItemTreeData對象,從數據層中去獲取itemName屬性值。
- 因為每一級ItemTreeData對象中記錄了它的父級ID,所以往根節點方向遍歷父節點、祖父節點時,先加入到List中的是上一級的節點名。而需求是“零級節點_一級節點_二級節點”的順序,所以在使用List時需要倒序遍歷!
最后顯示的完整分類如下:

