潜移默化学会WPF(Treeview异步加载节点)

本人尊重别人劳动成果,感觉写的很好,拿过来分享一下,本文不是原文,而是个人的理解,学习和分享

声明:原文:http://blog.csdn.net/qing2005/article/details/6523002

一、基本工作

1.新建WPF应用程序  TreeViewLoadingAsync

2.新建文件夹DB,把准备好的 Access 示例数据库Sample.mdb拷贝到DB文件夹下

  此时会弹出 数据源配置向导 窗口,点下一步,勾选表和视图,点击完成,此时app.config数据库连接字符串都已经生成好了

  数据库就一张表

3.用linq获得基本数据

在DB文件夹下新建一个DepartmentHelper类获得基本数据

新建两个静态对象

       static SampleDataSet ds = new SampleDataSet();
       static DepartmentTableAdapter da = new DepartmentTableAdapter();

第一个  Sample是你的数据库名称,然后加DataSet,这个对象可直接敲出,你可以理解为Sample这个数据库的临时数据集,就是另一个形式的数据库,充当临时数据库的角色,这个数据库我么直接可以用linq去操作它,不用sql了,目前还是空的一个数据库,下面一行代码是 Sample中的一个表名称+TableAdapter,部门表适配器,这样的话就可以用linq语法直接操作Department这张表了,如果还有其他表以此类推。(讲的不专业,只是方便理解)DepartmentTableAdapter 写完不变色,按一下Shift+Alt+F10导入命名空间,导不进来,请检查一下名称是否拼写错误

继续写代码

  static DepartmentHelper() {
            da.Fill(ds.Department);
        }
public static IEnumerable GetSubDepartments(int pid) {
            var list = ds.Department.Where(c => c.PID == pid).ToList();
            return list;
        }
staticDepartmentHelper(),静态构造函数,就是在调用DepartmentHelper里面的方法时首先要向ds里面的Department表中填充数据,这里用static修饰的,所以DepartmentHelper的对象是保存在内存中的,所以不会重复被创建,知道该程序关闭时,这块被占用的内存会被释放
(使用 static 修饰符声明属于类型本身而不是属于特定对象的静态成员。static 修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、析构函数或类以外的类型)
如果把这个思想理解了,Entity Framework就好学了
这两个方法很简单不讲了

二、新建视图模型
1.本例子简单,不细分MVVM了,思想是的
新建DepartmentViewModel类,实现INotifyPropertyChanged接口,导入
 System.ComponentModel;System.Collections.ObjectModel;这两个命名空间,惯性,实现该接口

                     先写3个变量,1个构造函数

        private DepartmentViewModel(object currentObject)
        {

        }
        //临时子节点用,当Expanded时移除此节点,添加子节点
        static readonly DepartmentViewModel _temp = new DepartmentViewModel(null);
        //选中的子节点
        private static ObservableCollection<DepartmentViewModel> _checkedItems = new ObservableCollection<DepartmentViewModel>();
        public ObservableCollection<DepartmentViewModel> CheckedItems
        {
            get
            {
                return _checkedItems;
            }
        }

        //根节点
        static DepartmentViewModel _rootItem;

                                                                                                                                                     

先写4个属性,保存父节点数据,子节点集合,treeview上要显示的文字

  private DepartmentViewModel _parent;
        public DepartmentViewModel Parent
        {
            get { return _parent; }
            set { _parent = value; }
        }

        private List<DepartmentViewModel> _children;
        public List<DepartmentViewModel> Children
        {
            get { return _children; }
            private set { _children = value; }
        }

        private object _current;
        public object Current
        {
            get { return _current; }
            set { _current = value; }
        }
        public string DisplayText
        {
            get { return ((SampleDataSet.DepartmentRow)Current)["DName"].ToString(); }
        }

添加判断

      /// <summary>
        /// 判断是否有子节点(逻辑是:如果只有一个临时子节点,说明没有真正的子节点)
        /// </summary>
        /// <returns></returns>
        private bool HasChildren() {
            return !(Children.Count == 1 && Children[0] == _temp);
        }

添加checkbox处理代码

  private bool? _isChecked;
        public bool? IsChecked {
            get { return _isChecked; }
            set {
                SetCheckState(value, true, true);
                
            }
        }
        private void SetCheckState(bool? value, bool updateChildren, bool updateParent) {
            if (_isChecked != value) {
                _isChecked = value;

                //通知选中项的集合
                if (_isChecked == true) {
                    _checkedItems.Add(this);
                    PropertyChanged(this, new PropertyChangedEventArgs("CheckedItems"));
                } else if (_isChecked == false) {
                    _checkedItems.Remove(this);
                    PropertyChanged(this, new PropertyChangedEventArgs("CheckedItems"));
                }

                PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));

                if (updateChildren) {
                    if (HasChildren()) {
                        Children.ForEach(c => c.SetCheckState(value, true, false));
                    }
                }
                if (updateParent && _parent != null) {
                    _parent.VerifyState();
                }
            }
        }
        private void VerifyState() {
            bool? state = null;
            for (int i = 0; i < this.Children.Count; ++i) {
                bool? currentState = this.Children[i].IsChecked;
                if (i == 0) {
                    state = currentState;
                } else if (state != currentState) {
                    state = null;
                    break;
                }
            }
            this.SetCheckState(state, false, true);
        }

构造函数,添加一下代码,初始化一些值

        private DepartmentViewModel(object currentObject) {
            Current = currentObject;
            _isChecked = false;
            Children = new List<DepartmentViewModel>();
            Children.Add(_temp);  //好让显示有个图标箭头,一个treeview节点下至少一个子节点
        }

展开节点,展开如果没有子节点,把默认的那个节点移除,

        private bool _isExpanded;
        public bool IsExpanded {
            get { return _isExpanded; }
            set {
                if (value != _isExpanded) {
                    _isExpanded = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("IsExpanded"));
                }
                if (!HasChildren()) {
                    Children.Remove(_temp);
                    LoadChildren();
                }
            }
        }
 /// <summary>
        /// 加载子节点
        /// </summary>
        private void LoadChildren() {
            if (Current != null) {
                int pid = Convert.ToInt32(((SampleDataSet.DepartmentRow)Current)["DID"]);
                var list = DepartmentHelper.GetSubDepartments(pid);
                foreach (var item in list) {
                    DepartmentViewModel model = new DepartmentViewModel(item) { _isChecked = this.IsChecked };
                    if (model.IsChecked == true) {
                        _checkedItems.Add(model);
                        PropertyChanged(this, new PropertyChangedEventArgs("CheckedItems"));
                    }
                    Children.Add(model);
                }
                Init();
            }
        }
  public static List<DepartmentViewModel> Create() {
            // 获得ID获得部门对象
            var list = DepartmentHelper.GetSubDepartments(0);
            DepartmentViewModel root = new DepartmentViewModel(null);
            _rootItem = root;
            root.Children.Clear();
            foreach (var item in list) {
                root.Children.Add(new DepartmentViewModel(item));
            }
            return root.Children;
        }

/// <summary>
/// 初始化,用于设置父节点
/// </summary>
private void Init() {
   if (!HasChildren()) return;
     foreach (DepartmentViewModel child in Children) {
     child.Parent = this;
     child.Init();
     }
     PropertyChanged(this, new PropertyChangedEventArgs("Children"));
  }

 就一个xaml窗体文件,就前台,后台没有代码

<Window x:Class="DepartmentTreeView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TreeViewLoadingAsync"
        Title="MainWindow" Height="329" Width="212" FontFamily="Arial">
    <Window.Resources>
        <ObjectDataProvider x:Key="depProvider" ObjectType="{x:Type local:DepartmentViewModel}" MethodName="Create" />
        <Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}" />
        </Style>
        <HierarchicalDataTemplate x:Key="CheckBoxItemTemplate" ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal" Margin="0,2,0,0">
                <CheckBox Focusable="False" IsChecked="{Binding IsChecked,Mode=TwoWay}" VerticalAlignment="Center" />
                <ContentPresenter Content="{Binding DisplayText,Mode=OneWay}" Margin="2,0" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TreeView Name="tvDepartment" Grid.Row="0" ItemContainerStyle="{StaticResource TreeViewItemStyle}" ItemsSource="{Binding Source={StaticResource depProvider}}" ItemTemplate="{StaticResource CheckBoxItemTemplate}" />
       
    </Grid>
</Window>
对上分析:
<ObjectDataProvider x:Key="depProvider" ObjectType="{x:Type local:DepartmentViewModel}" MethodName="Create" />
对象类型的数据源提供器:它很常用的,这里ObjectType指定了该对象的类型,是个DepartmentViewModel类型的,该类型下面有个方法叫做Create方法,所以后台不用指定数据了,Create方法返回的是一个List<DepartmentViewModel>类型的
如果你要的数据,多个地方都要用的到,不容易绑定,试着把公用的数据放在资源里,例如:ObjectDataProvider ,还有个XMLDataProvider有兴趣可以看一下


整体思路:
1.创建数据库的linq类,写个读取Department数据的访问类,这里叫DepartmentHelper,其实也就是数据访问层,根据父节点ID获取对象集合
2.创建ViewModel,主要通过这个方法List<DepartmentViewModel> Create() 提供treeview的数据;
①获得父节点ID是0的,即根节点集合
②创建一个虚拟根节点DepartmentViewModel类型的,把得到的真正根节点的DepartmentViewModel类型化后,遍历根节点结合,添加到虚拟根节点的Children集合中
3.前台给treeview绑定数据,ObjectDataProvider
4.绑定容器样式ItemContainerStyle,子项目数据源ItemsSource,子项目模板ItemTemplate
5.由于DepartmentViewModel实现了INotifyPropertyChanged接口,直接对他里面的值修改,于是不要对前台的控件执行修改,就可以改变了
6.treeview的ItemContainerStyle样式修改了IsExpanded属性,设置了Mode属性双向绑定
7.在DepartmentViewModel中有个IsExpanded属性,在设置set属性中触发事件,触发LoadChildren方法,读取以此ID为父节点ID的那些部门对象,根据父节点左边的CheckBox状态,初始化其他节点的选中状态,子对象集合全部放入Children集合内,想要立即更新UI的状态,调用PropertyChanged委托就行了
8.UI界面绑定了Chekbox的IsChecked属性和DepartmentViewModel中的IsChecked属性,同理双向的,IsChecked属性,SetCheckState方法,VerifyState方法
9.Init方法,是遍历Children,设置Children这个集合中的对象的Parent属性,递归把Children中Children等以此类推全部设置一下
10.CheckedItems集合存着的是DepartmentViewModel对象,也就是间接的选中的TreeviewItem对象。也方便后台提取选中项的信息,然后继续操作


扩展:在treeview外添加一个按钮,添加单击事件
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ObjectDataProvider provider = FindResource("depProvider") as ObjectDataProvider;
            List<DepartmentViewModel> firstLevelItems = provider.Data as List<DepartmentViewModel>;

            ICollectionView view = CollectionViewSource.GetDefaultView(firstLevelItems);
            DepartmentViewModel rootItem = view.CurrentItem as DepartmentViewModel;

            StringBuilder builder = new StringBuilder();
            foreach (DepartmentViewModel checkItem in rootItem.CheckedItems)
            {
                builder.AppendLine(checkItem.DisplayText);
            }
            MessageBox.Show("Checked items:\n" + builder.ToString());
        }
本例子靓点:MVVM,Treeview ViewModel的设计,点击读取加载节点信息,checkbox的正确选择,后台能够获得选择的treeview中选择的项;
难点:ViewModel类
巧妙:利用ObjectDataProvider,IsExpanded巧妙双向绑定时,利用属性动态加载数据,后台页面无代码;CheckBox版本treeview选择的问题
待解决:样式,还有在读取节点信息时,友好提示,例如,"信息读取中..."






原文地址:https://www.cnblogs.com/AaronYang/p/2652684.html