项目场景:
最近在学习MVVM的过程中,本着学以致用的想法,便尝试着将曾经做过的一个项目以MVVM的方式重构一遍。
问题描述:
在开发过程中,项目使用了 Treeview 控件。在之前的实现过程中,控件树的源数据为 List<T> 类型,绑定在属性 ItemSource 上。若想实时更新 List 上的属性变化,需在类型T的定义过程中实现 INotifyPropertyChanged 接口,并在需要通知属性值变更的对应属性内实现类似 RaisePropertyChanged() 的方法。且在原来的开发中,没有实现对 List 内成员数量变化的时候的实时通知。
原实现方法:
Treeview.ItemSource = null;
Treeview.ItemSource = TreeviewNodes;
通过对控件绑定的源数据的“手动”更新去更新前台UI,不方便也很不科学
原因分析:
我们先看看 List 的定义:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
{...}
可以看出, List 本身是没有继承类似 Notify 的接口的,因此本身也无法提供属性改变的通知行为。
解决方案:
经过查找资料后,发现 .Net 提供了这样的一个集合类型 ObservableCollection<T>
public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{...}
该类继承了继承了Collection, INotifyCollectionChanged, INotifyPropertyChanged,其中:
-
Collection:为泛型集合提供基类。
-
INotifyCollectionChanged:将集合的动态更改通知给侦听器,例如,何时添加和移除项或者重置整个集合对象。
-
INotifyPropertyChanged:向客户端发出某一属性值已更改的通知。
所以再ObservableCollection这个类的方法,对数据的操作很少,重点放在了当自己本事变化的时候(不管是属性,还是集合)会调用发出通知的事件。
下面贴出修改后的 Treeview 控件的实现代码
后台 ViewModel 中的代码:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using sdm.Model;
namespace sdm.ViewModel
{
...
public class MainViewModel : ViewModelBase
{
private ObservableCollection<NodeViewModel> treeviewNodes;
/// <summary>
/// TreeView
/// </summary>
public ObservableCollection<NodeViewModel> TreeviewNodes
{
get => treeviewNodes;
set => Set(ref treeviewNodes, value);
}
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
// Code runs "for real"
}
TreeviewNodes = new ObservableCollection<NodeViewModel>();
//这里为了测试新增了5个测试节点
for (int i = 0; i < 5; i++)
{
RtuModel rtu = new RtuModel();
rtu.RtuNum = i;
rtu.Address = $"测试节点{i}";
NodeViewModel node = new NodeViewModel()
{
Rtu = rtu,
};
TreeviewNodes.Add(node);
}
}
}
}
前台 xaml 中的代码:
<TreeView Grid.Row="1" x:Name="treeview" Margin="10,5,10,0" ItemsSource="{Binding TreeviewNodes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type vmmain:NodeViewModel}" ItemsSource="{Binding}">
<StackPanel x:Name="SPanel" Orientation="Horizontal" Margin="0,2,0,2">
<Image Height="15" Width="15" Source="{Binding ConIcon.Source, Mode=TwoWay}"/>
<TextBlock x:Name="NodeText" Text="{Binding Title, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView>
运行后效果:
实时新增节点:
完美实现需求
参考资料:
[1] WPF MVVM-TreeView数据源添加了节点,UI没有刷新
[2] C# ObservableCollection和List的区别
[3] WPF中使用MVVM模式实现可动态检索的Treeview