WPF应用基础篇---TreeView

  1.前言

    最近有部分朋友经常问我,WPF的TreeView控件,如何用MVVM来实现绑定和显示?所以写下了这篇WPF应用基础篇---TreeView.

 2.介绍

  • 案例浏览:

    

                  图 1-1(案例结构图)

  • 目的:本文中做了三个简单的Demo给刚刚入门或者入门不久而且不熟悉TreeView控件在MVVM中具体实现的朋友们。希望以下3个例子能够给他们带来帮助。
  • 背景:Demo是采用现实生活中一个大网络的某一部分网络来作为案例。这里为了演示方便,整个网络由路由器、交换机、集线器等服务器组成。他们的之间的关系是多对多的关系,一个网络中有可能一个路由器包含了多个路由器、交换机、集线器;而且交换机、集线器也是相同的原理。
  • 数据:本文中用到的数据随机产生的测试数据。根据界面中树的深度(下拉框)来选择树最多有多少层,然后创建树结构的数据。这里需要注意的是我们TreeView提供的数据源必须是树结构的;为什么需要树结构的数据呢?大家可能会觉得很奇怪,其实,我们ViewModel要将数据Binding到TreeView控件上就必须指定一个ItemsSource,所以必须把节点的子节点集合绑定到模板中的ItemsSource中。
  • 案例解析:

  整个Demo分为两部分:左边是功能菜单,右边是显示具体内容,可以参考图1-1。

  基础数据:为了实现一下案例功能,我建立了一个SmlAnt.DataLibrary的数据类库,专门提供原始基本类型和基本数据。下面是具体代码:

  

  实体类:

  

  

View Code
  1  namespace  DataLibrary
  2  {
  3       ///   <summary>
  4       ///  设备状态
  5       ///   </summary>
  6       public   enum  DeviceStatus
  7      {
  8          Connected,Off
  9      }
 10 
 11       ///   <summary>
 12       ///  设备基类
 13       ///   </summary>
 14       public   class  Device:INotifyPropertyChanged
 15      {
 16           // 是否被选中
 17           private   bool ?  isSelected;
 18           public   bool ?  IsSelected 
 19          {
 20               get  {  return  isSelected; }
 21               set
 22              {
 23                   if  (isSelected  !=  value)
 24                  {
 25                      isSelected  =  value;   
 26                      ChangeChildNodes( this );
 27                      ChangedParentNodes( this );
 28                      NotifyPropertyChanged( " IsSelected " );
 29                  }
 30              }
 31          }
 32          
 33           private  DeviceStatus status;
 34           public  DeviceStatus Status
 35          {
 36               get  {  return  status; }
 37               set
 38              {
 39                   if  (status  !=  value)
 40                  {
 41                      status  =  value;
 42                      NotifyPropertyChanged( " Status " );
 43                  }
 44              }
 45          }
 46 
 47           public   string  Name {  get set ; }
 48           public   string  ImageUrl{ get ; set ;}
 49 
 50           private  List < Device >  childNodes;
 51           public  List < Device >  ChildNodes
 52          {
 53               get  {  return  childNodes; }
 54               set
 55              {
 56                   if  (childNodes  !=  value)
 57                  {
 58                      childNodes  =  value;
 59                      NotifyPropertyChanged( " ChildNodes " );
 60                  }
 61              }
 62          }
 63 
 64           private  Device parentNode;
 65           public  Device ParentNode
 66          {
 67               get  {  return  parentNode; }
 68               set
 69              {
 70                   if  (parentNode  !=  value)
 71                  {
 72                      parentNode  =  value;
 73                      NotifyPropertyChanged( " ParentNode " );
 74                  }
 75              }
 76          }
 77 
 78           ///   <summary>
 79           ///  向下遍历,更改孩子节点状态
 80           ///  注意:这里的父节点不是属性而是字段
 81           ///  采用字段的原因是因为不想让父节点触发访问器而触发Setter
 82           ///   </summary>
 83           ///   <param name="CurrentNode"></param>
 84           public   void  ChangeChildNodes(Device CurrentNode)
 85          {
 86               if  (ChildNodes  !=   null )
 87              {
 88                   foreach  (var data  in  childNodes)
 89                  {
 90                      data.isSelected  =  CurrentNode.IsSelected;
 91                      CurrentNode.NotifyPropertyChanged( " IsSelected " );
 92                       if  (data.ChildNodes  !=   null )
 93                      {
 94                          data.ChangeChildNodes(data);
 95                      }
 96                  }
 97              }
 98          }
 99 
100           ///   <summary>
101           ///  向上遍历,更改父节点状态
102           ///  注意:这里的父节点不是属性而是字段
103           ///  采用字段的原因是因为不想让父节点触发访问器而触发Setter
104           ///   </summary>
105           ///   <param name="CurrentNode"></param>
106           public   void  ChangedParentNodes(Device CurrentNode)
107          {
108               if  (CurrentNode.ParentNode  !=   null )
109              {
110                   bool ?  parentNodeState  =   true ;
111                   int  selectedCount  =   0 ;   // 被选中的个数
112                   int  noSelectedCount  =   0 ;     // 不被选中的个数
113 
114                   foreach  (var data  in  CurrentNode.ParentNode.ChildNodes)
115                  {
116                       if  (data.IsSelected  ==   true )
117                      {
118                          selectedCount ++ ;
119                      }
120                       else   if  (data.IsSelected  ==   false )
121                      {
122                          noSelectedCount ++ ;
123                      }
124                  }
125 
126                   // 如果全部被选中,则修改父节点为选中
127                   if  (selectedCount  ==  
128                      CurrentNode.ParentNode.ChildNodes.Count)
129                  {
130                      parentNodeState  =   true ;
131                  }
132                   // 如果全部不被选中,则修改父节点为不被选中
133                   else   if  (noSelectedCount  ==  
134                      CurrentNode.ParentNode.ChildNodes.Count)
135                  {
136                      parentNodeState  =   false ;
137                  }
138                   // 否则标记父节点(例如用实体矩形填满)
139                   else
140                  {
141                      parentNodeState  =   null ;
142                  }
143 
144                  CurrentNode.parentNode.isSelected  =  parentNodeState;
145                  CurrentNode.parentNode.NotifyPropertyChanged( " IsSelected " );
146 
147                   if  (CurrentNode.ParentNode.ParentNode  !=   null )
148                  {
149                      ChangedParentNodes(CurrentNode.parentNode);
150                  }
151              }
152          }
153 
154           public   void  NotifyPropertyChanged( string  name)
155          {
156               if (PropertyChanged != null )
157              PropertyChanged( this , new  PropertyChangedEventArgs(name));
158          }
159           public   event  PropertyChangedEventHandler PropertyChanged;
160      }
161 
162       ///   <summary>
163       ///  路由器
164       ///   </summary>
165       public   class  Router : Device
166      {
167 
168      }
169 
170       ///   <summary>
171       ///  交换机
172       ///   </summary>
173       public   class  Switcher : Device
174      {
175 
176      }
177 
178       ///   <summary>
179       ///  集线器
180       ///   </summary>
181       public   class  Concentrator : Device
182      {
183 
184      }
185  }

  

  

  数据工厂:

  

View Code
  1  public   class  DataFactory
  2      {
  3           ///   <summary>
  4           ///  随机数据产生器
  5           ///   </summary>
  6           static  Random random  =   new  Random();        
  7 
  8           ///   <summary>
  9           ///  根据参数获取设备状态
 10           ///   </summary>
 11           ///   <param name="intValue"></param>
 12           ///   <returns></returns>
 13           private   static  DeviceStatus GetStatus( int  intValue)
 14          {
 15               return  intValue  %   2   ==   0   ?  DeviceStatus.Off : DeviceStatus.Connected;
 16          }
 17          
 18           ///   <summary>
 19           ///  
 20           ///   </summary>
 21           ///   <param name="intValue"></param>
 22           ///   <returns></returns>
 23           private   static  String GetName( int  intValue)
 24          {
 25               string  refValue  =   " 路由器 " ;
 26               if  (intValue  %   3   ==   0 )
 27              {
 28                  refValue  =   " 路由器 " ;
 29              }
 30               else   if  (intValue  %   3   ==   1 )
 31              {
 32                  refValue  =   " 交换机 " ;
 33              }
 34               else
 35              {
 36                  refValue  =   " 集线器 " ;
 37              }
 38               return  refValue;
 39          }
 40 
 41           ///   <summary>
 42           ///  根据参数创建设备(简单工厂-参数工厂)
 43           ///   </summary>
 44           ///   <param name="typeValue"></param>
 45           ///   <returns></returns>
 46           public   static  Device DeviceFactory( int  typeValue)
 47          {
 48              Device refEntity  =   null ;
 49               if  (typeValue  %   3   ==   0 )
 50              {
 51                  refEntity  =   new  Router();
 52              }
 53               else   if  (typeValue  %   3   ==   1 )
 54              {
 55                  refEntity  =   new  Switcher();
 56              }
 57               else
 58              {
 59                  refEntity  =   new  Concentrator();
 60              }
 61               return  refEntity;
 62          }
 63 
 64           ///   <summary>
 65           ///  随即获取基类设备数据
 66           ///   </summary>
 67           ///   <param name="level"> 当前节点所在层 </param>
 68           ///   <param name="MaxLevel"> 树最大深度 </param>
 69           ///   <returns> 设备树 </returns>
 70           public   static  List < Device >  GetBaseTypeDevices( int  level,  int  MaxLevel)
 71          {
 72              level ++ ;
 73              var count  =  random.Next( 6 10 );
 74              List < Device >  listTo  =   new  List < Device > ();
 75               for  ( int  i  =   1 ; i  <  count; i ++ )
 76              {
 77                  Device entity  =   new  Device();
 78                  var typeValue  =  random.Next( 1 6 );
 79                  entity.Name  =  GetName(typeValue);
 80                  entity.ImageUrl  =   " ..\\..\\Resource\\ "   +  entity.Name  +   " .png " ;
 81                  entity.Status  =  GetStatus(typeValue);
 82                   if  (level  <=  MaxLevel)
 83                      entity.ChildNodes  =  GetBaseTypeDevices(level, MaxLevel);
 84                  listTo.Add(entity);
 85              }
 86               return  listTo;
 87          }
 88 
 89           ///   <summary>
 90           ///  随即获取所有子类型设备数据
 91           ///   </summary>
 92           ///   <param name="level"> 当前节点所在层 </param>
 93           ///   <param name="MaxLevel"> 树最大深度 </param>
 94           ///   <returns> 设备树 </returns>
 95           public   static  List < Device >  GetAllTypeDevice( int  level, int  MaxLevel)
 96          {
 97              level ++ ;
 98              var count  =  random.Next( 6 10 );
 99              List < Device >  listTo  =   new  List < Device > ();
100               for  ( int  i  =   1 ; i  <  count; i ++ )
101              {
102                  var typeValue  =  random.Next( 1 6 );
103                  Device entity  =  DeviceFactory(typeValue);                
104                  entity.Name  =  GetName(typeValue);
105                  entity.ImageUrl  =   " ..\\..\\Resource\\ "   +  entity.Name  +   " .png " ;
106                  entity.Status  =  GetStatus(typeValue); 
107                   if  (level  <=  MaxLevel)
108                      entity.ChildNodes  =  GetAllTypeDevice(level,MaxLevel);
109                  listTo.Add(entity);
110              }
111               return  listTo;
112          }
113 
114           ///   <summary>
115           ///  随即获取所有子类型设备数据
116           ///   </summary>
117           ///   <param name="level"> 当前节点所在层 </param>
118           ///   <param name="MaxLevel"> 树最大深度 </param>
119           ///   <param name="parentNode"> 父节点 </param>
120           ///   <returns> 设备树 </returns>
121           public   static  List < Device >  GetAllTypeDevice( int  level,  int  MaxLevel, Device parentNode)
122          {
123              level ++ ;
124              var count  =  random.Next( 6 10 );
125              List < Device >  listTo  =   new  List < Device > ();
126               for  ( int  i  =   1 ; i  <  count; i ++ )
127              {
128                  var typeValue  =  random.Next( 1 6 );
129                  Device entity  =  DeviceFactory(typeValue);
130                  entity.IsSelected  =   false ;
131                  entity.Name  =  GetName(typeValue);
132                  entity.ParentNode  =  parentNode;
133                  entity.ImageUrl  =   " ..\\..\\Resource\\ "   +  entity.Name  +   " .png " ;
134                  entity.Status  =  GetStatus(typeValue);               
135                   if  (level  <=  MaxLevel)
136                      entity.ChildNodes  =  GetAllTypeDevice(level, MaxLevel, entity);
137                  listTo.Add(entity);
138              }
139               return  listTo;
140          }
141      }

 

 

  案例一,主要为大家介绍如何创建一个无限级的树,其实说简单点就是采用HierarchicalDataTemplate 作为树模板,然后通过Binding把数据绑定到树上。因为模板是HierarchicalDataTemplate这个模板,这里就不详细讲解,如果了解多点可以到MSDN,所以会无限级别的增加,只要数据结构上能支持,数据有多少级别,View中显示的树也会对应有多少级别。而如果采用的是DataTemplate的话,则只能有一层的数据。

  效果图如下:

  

        图 1-2(无限级别树)

  View(XAML)代码 代码1-3:

  

View Code
1  < HierarchicalDataTemplate x:Key = " TreeViewTemplate "  ItemsSource = " {Binding ChildNodes} " >
2               < StackPanel Orientation = " Horizontal " >
3                   < Image Source = " {Binding ImageUrl} "  Margin = " 2 " />
4                   < TextBlock Text = " {Binding Name} "  Margin = " 2 " />
5               </ StackPanel >
6           </ HierarchicalDataTemplate >
7 
8  < TreeView Grid.Row = " 1 "  ItemTemplate = " {StaticResource TreeViewTemplate} "  ItemsSource = " {Binding DataSource} "  Margin = " 5 " />

  

  ViewModel代码:

View Code
 1  private  List < Device >  dataSource;
 2           public  List < Device >  DataSource
 3          {
 4               get  {  return  dataSource; }
 5               set
 6              {
 7                   if  (dataSource  !=  value)
 8                  {
 9                      dataSource  =  value;
10                      RaisePropertyChanged( " DataSource " );
11                  }
12              }
13          }
14 
15  DataSource  =  DataFactory.GetBaseTypeDevices( 1 , SelectedLevel);

   

  

  案例二,主要给大家讲解的是,如何采用DataTmeplateSelector通过重写SelectTemplate方法来实现的。来控制显示样式、右键菜单等功能。这里主要讲的是,不同服务器之间显示不一样,而且连快捷菜单也对应不一样。这里有个特别说明的是:因为功能显示的需求,这里把集线器定义为没有子设备的模板。还有另外一个功能就是当我按下重启的时候,断开按钮就不能使用。这里用到的是Command。园里前辈们写了很多这方面的文章,我这里就不对ICommand进行详细讨论。

  效果图:图1-1

  快捷菜单(如下图):

  

  

  图 1-3(路由器快捷菜单)   图 1-4(交换机快捷菜单)       图1-5(集线器快捷菜单)

  快捷菜单代码:

  

View Code
 1  < ContextMenu x:Key = " RouterMenu " >
 2               < MenuItem Header = " 启动路由器 " >
 3                   < MenuItem.Icon >
 4                       < Image Source = " ..\..\Resource\Connect.png " />
 5                   </ MenuItem.Icon >
 6               </ MenuItem >
 7               < MenuItem Header = " 断开路由器 " >
 8                   < MenuItem.Icon >
 9                       < Image Source = " ..\..\Resource\Break.png " />
10                   </ MenuItem.Icon >
11               </ MenuItem >
12           </ ContextMenu >
13           < ContextMenu x:Key = " SwitchMenu " >
14               < MenuItem Header = " 启动交换机 " >
15                   < MenuItem.Icon >
16                       < Image Source = " ..\..\Resource\Connect.png " />
17                   </ MenuItem.Icon >
18               </ MenuItem >
19               < MenuItem Header = " 断开交换机 " >
20                   < MenuItem.Icon >
21                       < Image Source = " ..\..\Resource\Break.png " />
22                   </ MenuItem.Icon >
23               </ MenuItem >
24           </ ContextMenu >
25           < ContextMenu x:Key = " ConcentratorMenu " >
26               < MenuItem Header = " 启动集线器 " >
27                   < MenuItem.Icon >
28                       < Image Source = " ..\..\Resource\Connect.png " />
29                   </ MenuItem.Icon >
30               </ MenuItem >
31               < MenuItem Header = " 断开集线器 " >
32                   < MenuItem.Icon >
33                       < Image Source = " ..\..\Resource\Break.png " />
34                   </ MenuItem.Icon >
35               </ MenuItem >
36           </ ContextMenu >

  TreeView模板代码:

View Code
 1  xmlns:LocalTmeplate = " clr-namespace:Smlant.DataTemplates "       
 2 
 3  < LocalTmeplate:ContextMenuDataTemplateSelector x:Key = " ContextMenuDataTemplateSelector " />
 4 
 5  <!-- 交换机模板 -->
 6           < HierarchicalDataTemplate x:Key = " SwitchTemplate "  ItemsSource = " {Binding ChildNodes} "  DataType = " {x:Type DataLib:Switcher} " >
 7               < StackPanel Orientation = " Horizontal "  ContextMenu = " {StaticResource SwitchMenu} " >
 8                   < Image Source = " {Binding ImageUrl} "  Margin = " 2 " />
 9                   < TextBlock Text = " {Binding Name} "  Margin = " 2 "  VerticalAlignment = " Center " />
10                   < Button Margin = " 2 "  Command = " {Binding DataContext.OffCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}} "
11                          CommandParameter = " {Binding} " >
12                       < StackPanel >
13                           < Image Source = " ..\..\Resource\Connect.png "  ToolTip = " 重新连接 " />
14                       </ StackPanel >
15                   </ Button >
16                   < Button Margin = " 2 "  Command = " {Binding DataContext.ConnectionCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}} "
17                          CommandParameter = " {Binding} " >
18                       < StackPanel >
19                           < Image Source = " ..\..\Resource\Break.png "  ToolTip = " 断开连接 " />
20                       </ StackPanel >
21                   </ Button >
22               </ StackPanel >
23           </ HierarchicalDataTemplate >
24           <!-- 路由器模板 -->
25           < HierarchicalDataTemplate x:Key = " RouterTemplate "  ItemsSource = " {Binding ChildNodes} "  DataType = " {x:Type DataLib:Router} " >
26               < StackPanel Orientation = " Horizontal "  ContextMenu = " {StaticResource RouterMenu} " >
27                   < Image Source = " {Binding ImageUrl} "  Margin = " 2 " />
28                   < TextBlock Text = " {Binding Name} "  Margin = " 2 "  VerticalAlignment = " Center " />
29                   < Button Margin = " 2 "  Content = " 重启路由 "  Command = " {Binding DataContext.OffCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}} "
30                          CommandParameter = " {Binding} " >
31                   </ Button >
32                   < Button Margin = " 2 "  Content = " 断开连接 "   Command = " {Binding DataContext.ConnectionCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}} "
33                          CommandParameter = " {Binding} " >
34                   </ Button >
35               </ StackPanel >
36           </ HierarchicalDataTemplate >
37           <!-- 集线器模板 -->
38           < DataTemplate x:Key = " ConcentratorTemplate "  DataType = " {x:Type DataLib:Concentrator} " >
39               < StackPanel Orientation = " Horizontal "  ContextMenu = " {StaticResource ConcentratorMenu} " >
40                   < Image Source = " {Binding ImageUrl} "  Margin = " 2 " />
41                   < TextBlock Text = " {Binding Name} "  Margin = " 2 "  VerticalAlignment = " Center " />
42                   < Button Margin = " 2 "  Content = " 重新连接 "  Command = " {Binding DataContext.OffCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}} "
43                          CommandParameter = " {Binding} " />
44                   < Button Margin = " 2 "  Content = " 断开连接 "   Command = " {Binding DataContext.ConnectionCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}} "
45                          CommandParameter = " {Binding} " />
46               </ StackPanel >
47           </ DataTemplate >

  DataTemplateSelector代码:

  

View Code
 1  public   class  ContextMenuDataTemplateSelector:DataTemplateSelector
 2      {
 3           public   override  System.Windows.DataTemplate SelectTemplate( object  item, System.Windows.DependencyObject container)
 4          {
 5              FrameworkElement element  =  container  as  FrameworkElement;
 6              DataTemplate template  =   null ;
 7               if  (item  is  Router)
 8              {
 9                  template  =  element.FindResource( " RouterTemplate " as  HierarchicalDataTemplate;
10              }
11               else   if  (item  is  Switcher)
12              {
13                  template  =  element.FindResource( " SwitchTemplate " as  HierarchicalDataTemplate;
14              }
15               else   if  (item  is  Concentrator)
16              {
17                  template  =  element.FindResource( " ConcentratorTemplate " as  DataTemplate;
18              }
19               return  template;
20          }
21      }

  ViewModel代码: 

  

View Code
 1  private  List < Device >  dataSource;
 2           public  List < Device >  DataSource
 3          {
 4               get  {  return  dataSource; }
 5               set
 6              {
 7                   if  (dataSource  !=  value)
 8                  {
 9                      dataSource  =  value;
10                      RaisePropertyChanged( " DataSource " );
11                  }
12              }
13          }
14 
15   DataSource  =  DataFactory.GetAllTypeDevice( 1 , SelectedLevel);

 

  

 案例三,主要跟大家分享的是,如何在TreeView上实现三态树的功能。具体什么是三态树的话我在这里就不多说了。以下是案例三的具体结构图和代码:

  结构图:

  

       图 1-6(三态树)

  代码:具体代码实现在上面的实体类代码的 IDevice中实现。请参考上面代码。

  

  

  3.个人观点

    很多朋友都抱怨说WPF的TreeView是一个很麻烦的东西,而且不好用。这点我持反对的意见,每一种新东西,在我们还不熟悉的时候,是挺麻烦的。但是WPF--TreeView较WinForm--Tree来说,WPF提供一个强大的模板功能,能让我们根据自己的需要,灵活地更换模板。如果在做WinForm开发的时候,我想实现一棵树上保存N种数据类型的数据,而且根据不同的类型,在节点上显示不一样的状态和样式,也许你会花很多的时间来重写Tree的控件,而WPF提供了一个模板功能,而且具体的模板是我们自己来实现的。

  

  4.附加代码:

    百度网盘  :http://pan.baidu.com/s/1kVqRyrt

    密码:cm4k

  

 

转载于:https://www.cnblogs.com/smlAnt/archive/2011/07/31/2121689.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值