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