• 潜移默化学会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选择的问题
    待解决:样式,还有在读取节点信息时,友好提示,例如,"信息读取中..."






  • 相关阅读:
    前言
    npm安装全局模块之后项目提示找不到的解决
    mybatisPlus自动填充功能
    springMvc跨域的问题
    mybatisPlus逻辑删除
    java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
    Maven 打包指定名称
    Host is not allowed to connect to this MySQL server
    MySQL 8.0 Public Key Retrieval is not allowed
    SpringBoot1.5 项目启动报错 (jdk.internal.loader.ClassLoaders$AppClassLoader and java.net.URLClassLoader are in module java.base of loader 'bootstrap')
  • 原文地址:https://www.cnblogs.com/AaronYang/p/2652684.html
Copyright © 2020-2023  润新知