最近要用WPF寫一個樹,同事給了我一個Demo(不知道是從哪裏找來的),我基本上就是參照了這個Demo。express
先放一下效果圖(3棵樹):ide
這個樹索要知足的條件是:this
那麼這個樹究竟要怎麼造出來呢?編碼
因爲用WPF,且用MVVM模式,故TreeView的ItemSource及複選框的選中狀態IsChecked須要從ViewModel層進行綁定。先看一下樹的xaml:spa
<Window x:Class="MyWpfCheckTreeDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:VM="clr-namespace:MyWpfCheckTreeDemo.AppViewModel" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" Title="MainWindow" Height="350" Width="525" Loaded="LoadedEvent"> <Window.Resources> <HierarchicalDataTemplate x:Key="MyTreeItemTemplate" DataType="{x:Type VM:CommonTreeView}" ItemsSource="{Binding Path=Children,Mode=OneWay}"> <StackPanel x:Name="My_SP" Orientation="Horizontal" Margin="2"> <CheckBox IsChecked="{Binding Path=IsChecked}" > </CheckBox> <ContentPresenter Content="{Binding Path=NodeName,Mode=OneTime}" Margin="2,0"/> </StackPanel> </HierarchicalDataTemplate> <Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="True" /> </Style> </Window.Resources> <Grid> <TreeView Grid.Row="1" x:Name="tv" ItemsSource="{Binding MyTrees}" ItemContainerStyle="{StaticResource TreeViewItemStyle}" ItemTemplate="{StaticResource MyTreeItemTemplate}"> </TreeView> </Grid> </Window>
在xaml中的數據綁定好以後,就是在後臺如何實現數據的傳遞了。3d
先來看一下每一個節點所須要包含的數據:code
如今將上述數據封裝成一個樹節點類:xml
public class CommonTreeView : NotifyPropertyBase { /// <summary> /// 父 /// </summary> public CommonTreeView Parent { get; set; } /// <summary> /// 子 /// </summary> public List<CommonTreeView> Children { get; set; } /// <summary> /// 節點的名字 /// </summary> public string NodeName { get; set; } public bool? _isChecked; /// <summary> /// CheckBox是否選中 /// </summary> public bool? IsChecked { get { return _isChecked; } set { SetIsChecked(value, true, true); } } public CommonTreeView(string name) { this.NodeName=name; this.Children=new List<CommonTreeView>(); } public CommonTreeView() { } private void SetIsChecked(bool? value, bool checkedChildren, bool checkedParent) { if (_isChecked == value) return; _isChecked = value; //選中和取消子類 if (checkedChildren && value.HasValue && Children != null) Children.ForEach(ch => ch.SetIsChecked(value, true, false)); //選中和取消父類 if (checkedParent && this.Parent != null) this.Parent.CheckParentCheckState(); //通知更改 this.SetProperty(x => x.IsChecked); } /// <summary> /// 檢查父類是否選 中 /// 若是父類的子類中有一個和第一個子類的狀態不同父類ischecked爲null /// </summary> private void CheckParentCheckState() { List<CommonTreeView> checkedItems = new List<CommonTreeView>(); string checkedNames = string.Empty; bool? _currentState = this.IsChecked; bool? _firstState = null; for (int i = 0; i < this.Children.Count(); i++) { bool? childrenState = this.Children[i].IsChecked; if (i == 0) { _firstState = childrenState; } else if (_firstState != childrenState) { _firstState = null; } } if (_firstState != null) _currentState = _firstState; SetIsChecked(_firstState, false, true); } /// <summary> /// 建立樹 /// </summary> /// <param name="children"></param> /// <param name="isChecked"></param> public void CreateTreeWithChildre( CommonTreeView children,bool? isChecked) { this.Children.Add(children); //必須先把孩子加入再爲Parent賦值, //不然當只有一個子節點時Parent的IsChecked狀態會出錯 children.Parent = this; children.IsChecked = isChecked; } }
節點值變化時對UI進行通知的方法PropertyNotify:blog
public class NotifyPropertyBase : INotifyPropertyChanged { public void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public event PropertyChangedEventHandler PropertyChanged; } /// <summary> /// 擴展方法 /// 避免硬編碼問題 /// </summary> public static class NotifyPropertyBaseEx { public static void SetProperty<T, U>(this T tvm, Expression<Func<T, U>> expre) where T : NotifyPropertyBase, new() { string _pro = CommonFun.GetPropertyName(expre); tvm.OnPropertyChanged(_pro); }//爲何擴展方法必須是靜態的 } public class CommonFun { /// <summary> /// 返回屬性名 /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="U"></typeparam> /// <param name="expr"></param> /// <returns></returns> public static string GetPropertyName<T, U>(Expression<Func<T, U>> expr) { string _propertyName = ""; if (expr.Body is MemberExpression) { _propertyName = (expr.Body as MemberExpression).Member.Name; } else if (expr.Body is UnaryExpression) { _propertyName = ((expr.Body as UnaryExpression).Operand as MemberExpression).Member.Name; } return _propertyName; } }
到這裏基本上這個樹就能夠投入使用了,如今在ViewModel中給樹賦值,並取出選中狀態的葉子的節點名稱:
這個裏面的方法只給出重點部分:get
/// <summary> /// 建立樹 /// </summary> /// <returns></returns> public List<CommonTreeView> MyCreateTree() { List<CommonTreeView> views = new List<CommonTreeView>(); //CommonTreeView _myT = new CommonTreeView("合約"); CommonTreeView _myy = new CommonTreeView("CCP"); views.Add(_myy); CommonTreeView _myy1 = new CommonTreeView("CCP_1"); _myy.CreateTreeWithChildre(_myy1, true); CommonTreeView _myy1_1 = new CommonTreeView("CCP_1.1"); _myy1.CreateTreeWithChildre(_myy1_1, true); } /// <summary> /// 選中的節點名稱保存在_names中 /// </summary> public void SaveC() { _names = new List<string>(); //SelectedTree=MyTrees.FindAll(r => r.IsChecked == true); foreach (CommonTreeView tree in MyTrees) { GetCheckedItems(tree); } } /// <summary> /// 獲取選中項 /// </summary> /// <param name="tree"></param> private void GetCheckedItems(CommonTreeView tree) { if (tree.Parent != null && (tree.Children == null || tree.Children.Count == 0)) { if (tree.IsChecked.HasValue && tree.IsChecked == true) _names.Add(tree.NodeName); } else if (tree.Children != null && tree.Children.Count > 0) { foreach (CommonTreeView tv in tree.Children) GetCheckedItems(tv); } }
到這裏就結束啦。。。