WPF的TreeView不像WinForms的TreeView那样,通过TreeNode实现树型节点。WPF的TreeView实现起来,我感觉有点像VM(View-Model)模式,就像WinForms中的DataGrid,控件负责显示,数据在DataTable中。那么我们在实现TreeView时,也需要用这样的思路,数据在Model中,控件是View负责显示。
我需要实现的最终目标是:
1.节点是异步加载的(解决效率问题)
2.节点是无限级别的(递归的,这样可复用与目录结构、组织架构等数据类型)
3.节点是可以多选的(用CheckBox选择)
4.XAML方式绑定(减少UI上Code量)
5.方便得获取选中项信息
先看效果图:
1)数据库设计与准备
为了演示方便,采用了ACCESS数据库,添加一个Department表
表字段:
数据内容:
2)创建WPF项目
3)数据访问类
为了图方便,我采用了Linq to DataSet
- usingSystem;
- usingSystem.Collections.Generic;
- usingSystem.Linq;
- usingSystem.Text;
- usingDepartmentTreeView.DB;
- usingSystem.Collections;
- usingDepartmentTreeView.DB.SampleDataSetTableAdapters;
- namespaceDepartmentTreeView{
- publicclassDepartmentHelper{
- staticSampleDataSetds=newSampleDataSet();
- staticDepartmentTableAdapterda=newDepartmentTableAdapter();
- staticDepartmentHelper(){
- da.Fill(ds.Department);
- }
- publicstaticobjectGetDepartment(intdid){
- vardepart=ds.Department.Where(c=>c.DID==did).SingleOrDefault();
- returndepart;
- }
- publicstaticIEnumerableGetSubDepartments(intpid){
- varlist=ds.Department.Where(c=>c.PID==pid).ToList();
- returnlist;
- }
- }
- }
4)创建TreeView 的Model类DepartmentViewModel
- usingSystem;
- usingSystem.Collections.Generic;
- usingSystem.Linq;
- usingSystem.Text;
- usingSystem.ComponentModel;
- usingSystem.Collections.ObjectModel;
- namespaceDepartmentTreeView{
- publicclassDepartmentViewModel:INotifyPropertyChanged{
- //临时子节点用,当Expanded时移除此节点,添加子节点
- staticreadonlyDepartmentViewModel_temp=newDepartmentViewModel(null);
- //选中的子节点
- privatestaticObservableCollection<DepartmentViewModel>_checkedItems=newObservableCollection<DepartmentViewModel>();
- //根节点
- staticDepartmentViewModel_rootItem;
- #regionfields&properties
- privatebool?_isChecked;
- publicbool?IsChecked{
- get{return_isChecked;}
- set{
- SetCheckState(value,true,true);
- }
- }
- privatevoidSetCheckState(bool?value,boolupdateChildren,boolupdateParent){
- if(_isChecked!=value){
- _isChecked=value;
- //通知选中项的集合
- if(_isChecked==true){
- _checkedItems.Add(this);
- PropertyChanged(this,newPropertyChangedEventArgs("CheckedItems"));
- }elseif(_isChecked==false){
- _checkedItems.Remove(this);
- PropertyChanged(this,newPropertyChangedEventArgs("CheckedItems"));
- }
- PropertyChanged(this,newPropertyChangedEventArgs("IsChecked"));
- if(updateChildren){
- if(HasChildren()){
- Children.ForEach(c=>c.SetCheckState(value,true,false));
- }
- }
- if(updateParent&&_parent!=null){
- _parent.VerifyState();
- }
- }
- }
- privatevoidVerifyState(){
- bool?state=null;
- for(inti=0;i<this.Children.Count;++i){
- bool?currentState=this.Children[i].IsChecked;
- if(i==0){
- state=currentState;
- }elseif(state!=currentState){
- state=null;
- break;
- }
- }
- this.SetCheckState(state,false,true);
- }
- privatebool_isExpanded;
- publicboolIsExpanded{
- get{return_isExpanded;}
- set{
- if(value!=_isExpanded){
- _isExpanded=value;
- PropertyChanged(this,newPropertyChangedEventArgs("IsExpanded"));
- }
- if(!HasChildren()){
- Children.Remove(_temp);
- LoadChildren();
- }
- }
- }
- privateobject_current;
- publicobjectCurrent{
- get{return_current;}
- set{_current=value;}
- }
- publicstringDisplayText{
- get{return((DepartmentTreeView.DB.SampleDataSet.DepartmentRow)Current)["DName"].ToString();}
- }
- privateDepartmentViewModel_parent;
- publicDepartmentViewModelParent{
- get{return_parent;}
- set{_parent=value;}
- }
- privateList<DepartmentViewModel>_children;
- publicList<DepartmentViewModel>Children{
- get{return_children;}
- privateset{_children=value;}
- }
- #endregion
- publicstaticList<DepartmentViewModel>Create(){
- varlist=DepartmentHelper.GetSubDepartments(0);
- DepartmentViewModelroot=newDepartmentViewModel(null);
- _rootItem=root;
- root.Children.Clear();
- foreach(variteminlist){
- root.Children.Add(newDepartmentViewModel(item));
- }
- returnroot.Children;
- }
- privateDepartmentViewModel(objectcurrentObject){
- Current=currentObject;
- _isChecked=false;
- Children=newList<DepartmentViewModel>();
- Children.Add(_temp);
- }
- ///<summary>
- ///初始化,用于设置父节点
- ///</summary>
- privatevoidInit(){
- if(!HasChildren())return;
- foreach(DepartmentViewModelchildinChildren){
- child.Parent=this;
- child.Init();
- }
- PropertyChanged(this,newPropertyChangedEventArgs("Children"));
- }
- ///<summary>
- ///加载子节点
- ///</summary>
- privatevoidLoadChildren(){
- if(Current!=null){
- intpid=Convert.ToInt32(((DepartmentTreeView.DB.SampleDataSet.DepartmentRow)Current)["DID"]);
- varlist=DepartmentHelper.GetSubDepartments(pid);
- foreach(variteminlist){
- DepartmentViewModelmodel=newDepartmentViewModel(item){_isChecked=this.IsChecked};
- if(model.IsChecked==true){
- _checkedItems.Add(model);
- PropertyChanged(this,newPropertyChangedEventArgs("CheckedItems"));
- }
- Children.Add(model);
- }
- Init();
- }
- }
- ///<summary>
- ///判断是否有子节点(逻辑是:如果只有一个临时子节点,说明没有真正的子节点)
- ///</summary>
- ///<returns></returns>
- privateboolHasChildren(){
- return!(Children.Count==1&&Children[0]==_temp);
- }
- publicObservableCollection<DepartmentViewModel>CheckedItems{
- get{
- return_checkedItems;
- }
- }
- publiceventPropertyChangedEventHandlerPropertyChanged;
- }
- }
5)创建WPF窗体
- <Windowx: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:DepartmentTreeView"
- Title="MainWindow"Height="329"Width="212"FontFamily="Arial">
- <Window.Resources>
- <ObjectDataProviderx:Key="depProvider"ObjectType="{x:Typelocal:DepartmentViewModel}"MethodName="Create"/>
- <Stylex:Key="TreeViewItemStyle"TargetType="{x:TypeTreeViewItem}">
- <SetterProperty="IsExpanded"Value="{BindingPath=IsExpanded,Mode=TwoWay}"/>
- </Style>
- <HierarchicalDataTemplatex:Key="CheckBoxItemTemplate"ItemsSource="{BindingChildren}">
- <StackPanelOrientation="Horizontal"Margin="0,2,0,0">
- <CheckBoxFocusable="False"IsChecked="{BindingIsChecked,Mode=TwoWay}"VerticalAlignment="Center"/>
- <ContentPresenterContent="{BindingDisplayText,Mode=OneWay}"Margin="2,0"/>
- </StackPanel>
- </HierarchicalDataTemplate>
- </Window.Resources>
- <GridMargin="5">
- <Grid.RowDefinitions>
- <RowDefinition/>
- <RowDefinitionHeight="Auto"/>
- <RowDefinitionHeight="Auto"/>
- </Grid.RowDefinitions>
- <TreeViewName="tvDepartment"Grid.Row="0"ItemContainerStyle="{StaticResourceTreeViewItemStyle}"ItemsSource="{BindingSource={StaticResourcedepProvider}}"ItemTemplate="{StaticResourceCheckBoxItemTemplate}"/>
- <TextBlockName="tbText"Margin="5"Grid.Row="1">当前选中项:<TextBlockText="{BindingSource={StaticResourcedepProvider},Path=CheckedItems.Count,Mode=OneWay,IsAsync=True}"/></TextBlock>
- <ButtonMargin="5"Grid.Row="2"Content="GetCheckedItems"Click="Button_Click"/>
- </Grid>
- </Window>
6)窗口的后置代码
- usingSystem.Collections.Generic;
- usingSystem.Text;
- usingSystem.Windows;
- usingSystem.Windows.Data;
- usingSystem.Windows.Documents;
- usingSystem.ComponentModel;
- namespaceDepartmentTreeView{
- ///<summary>
- ///MainWindow.xaml的交互逻辑
- ///</summary>
- publicpartialclassMainWindow:Window{
- publicMainWindow(){
- InitializeComponent();
- }
- privatevoidButton_Click(objectsender,RoutedEventArgse){
- ObjectDataProviderprovider=FindResource("depProvider")asObjectDataProvider;
- List<DepartmentViewModel>firstLevelItems=provider.DataasList<DepartmentViewModel>;
- ICollectionViewview=CollectionViewSource.GetDefaultView(firstLevelItems);
- DepartmentViewModelrootItem=view.CurrentItemasDepartmentViewModel;
- List<DepartmentViewModel>checkedItems=CollectionViewSource.GetDefaultView(provider)asList<DepartmentViewModel>;
- StringBuilderbuilder=newStringBuilder();
- foreach(DepartmentViewModelcheckIteminrootItem.CheckedItems){
- builder.AppendLine(checkItem.DisplayText);
- }
- MessageBox.Show("Checkeditems:/n"+builder.ToString());
- }
- }
- }
好了,到此大功告成!!!
源代码:http://download.csdn.net/source/3253097