彩票走势图

如何用MVVM来实现绑定和显示TreeView控件

转帖|其它|编辑:郝浩|2011-08-10 14:26:04.000|阅读 5594 次

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

# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>

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

 1.介绍

  • 案例浏览:

    

                  图 1-1(案例结构图)

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

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

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

  实体类:

  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 }

  数据工厂:

  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:

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>

8  <TreeView Grid.Row="1" ItemTemplate=
"{StaticResource TreeViewTemplate}" ItemsSource=
"{Binding DataSource}" Margin="5"/>  

  ViewModel代码:

 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

  快捷菜单(如下图):

   

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

  快捷菜单代码:

 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模板代码:

 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代码:

 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代码:  

 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中实现。请参考上面代码。

  2.个人观点

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

 


标签:MVVM

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@capbkgr.cn

文章转载自:博客园

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP