C# MVVM-从模型到视图模型,保持模型不变
假设我有一个代表我的模型的第三方后端[C#.NET标准]库;此库纯粹用作数据处理后端工具。 显然没有GUI,只提供了一些公共只读寄存器,以便允许其他父/所有者代码观察这些寄存器的状态。举个简单的例子:C# MVVM-从模型到视图模型,保持模型不变,c#,wpf,mvvm,binding,dispatcher,C#,Wpf,Mvvm,Binding,Dispatcher,假设我有一个代表我的模型的第三方后端[C#.NET标准]库;此库纯粹用作数据处理后端工具。 显然没有GUI,只提供了一些公共只读寄存器,以便允许其他父/所有者代码观察这些寄存器的状态。举个简单的例子: public class MyModel { public int MyVariable { get; private set; } public List<int> MyCollection { get; private set; } ... } 您的模型完
public class MyModel
{
public int MyVariable { get; private set; }
public List<int> MyCollection { get; private set; }
...
}
您的模型完全可以作为一些库对象的包装器。在一个完美的世界中,这些库对象的功能将非常全面,它们将支持观察模式,例如
INotifyPropertyChanged
。然后,它们可以在各种上下文中使用,比如直接作为MVVM的模型。在您的情况下,由于库对象不支持可观察性,因此创建提供该可观察性的包装器类可能是有意义的。否则,您将在视图模型中对此进行补偿(这也是完全可以接受的,尽管我希望在定义体系结构边界时我的范围非常干净)
至于dispatcher,我的直觉是,模型不应该表现出任何线程亲和力——如果某个线程上的模型发生了更新,通知就会在同一个线程上发出。由于视图本质上是线程绑定的,封送通知的责任要么在视图模型中,要么直接在视图中。当然,您可以始终封送更新模型的调用,以便通知仅在UI线程上发生。通常您的模型类不需要实现
INotifyPropertyChaged
和INotifyCollectionChanged
。这是由视图模型类实现的接口,用于为视图提供非常通用的通知。模型应该公开专门的事件,以通知视图模型有关数据更改的信息
因为库是第三方的,所以只能按照API允许的方式使用它。当库公开动态数据时,它很可能会公开视图模型可以订阅的相关事件。否则,视图模型将不得不轮询库中的数据更改(在这种情况下,您可能会寻找更重要的替代库) 在MVVM中,模型不应处理任何
调度程序<代码>调度程序
是一个与UI相关的概念。它处理UI对象的UI线程关联(DispatcherObject
)。它还负责管理UI线程的作业队列。
模型组件既没有与UI相关的对象,也不依赖UI或UI线程
在所描述的场景中,正确的方法是从视图模型订阅模型事件。然后填充一个可观察集合
,视图模型将其公开给视图进行数据绑定
根据MVVM,模型从不访问视图模型。因此,只有视图模型必须封送CollectionChanged
事件或对与UI线程关联的对象的任何其他访问(例如,如果访问发生在与UI线程不同的线程中)
您更正的示例可能如下所示:
public class MyModel
{
public int Property { get; private set; }
public List<int> DataCollection { get; private set; }
public event EventHandler DataIsReady;
public void AddElementToDataCollectionOnBackgroundThread(int element)
{
Task.Run(()
{
this.DataCollection.Add(element);
DataIsReady?.Invoke(this, EventArgs.Empty);
}
}
}
public class MyViewModel : INotifyPropertyChanegd
{
private MyModel Model { get; };
private int viewModelProperty;
public int ViewModelProperty
{
get => return this.viewModelProperty;
set
{
this.viewModelProperty = value;
OnPropertyChanged();
}
}
public ObservaleCollection<int> ViewItemsSource { get }
public MyViewModel(MyModel model)
{
this.Model = model;
this.Model.DataIsReady += OnModelDataIsReady;
}
private void OnModelDataIsReady(object sender, EventArgs e)
{
// Since the event was raised on a background thread,
// the view model is responsible to marshal the collection change to the UI thread
Application.Current.Dispatcher.InvokeAsync(
() =>
{
this.ViewItemsSource.Clear();
this.Model.DataCollection.ForEach(this.ViewItemsSource.Add));
});
// PropertyChanged is automatically marshalled
// to the UI thread by the framework --> no dispatcher needed
this.ViewModelProperty = this.Model.Property;
}
}
公共类MyModel
{
公共int属性{get;private set;}
公共列表数据收集{get;private set;}
公共事件事件处理程序数据已就绪;
public void AddElementToDataCollectionOnBackgroundThread(int元素)
{
Task.Run()
{
this.DataCollection.Add(元素);
DataIsReady?.Invoke(此为EventArgs.Empty);
}
}
}
公共类MyViewModel:INotifyPropertyChanegd
{
私有MyModel模型{get;};
私有财产;
公共int ViewModelProperty
{
get=>返回this.viewModelProperty;
设置
{
this.viewModelProperty=值;
OnPropertyChanged();
}
}
公共ObservaleCollection ViewItemsSource{get}
公共MyViewModel(MyModel模型)
{
这个模型=模型;
this.Model.DataIsReady+=OnModelDataIsReady;
}
ModelDataisReady上的私有void(对象发送方,事件参数e)
{
//由于事件是在后台线程上引发的,
//视图模型负责将集合更改封送到UI线程
Application.Current.Dispatcher.InvokeAsync(
() =>
{
this.ViewItemsSource.Clear();
this.Model.DataCollection.ForEach(this.ViewItemsSource.Add));
});
//PropertyChanged将自动编组
//通过框架-->发送到UI线程不需要调度程序
this.ViewModelProperty=this.Model.Property;
}
}
关于第一项,您建议采用什么方法来提供可观察性:基于时间的更新还是其他方法?关于第二项,当您声明“如果某个线程上的模型发生了更新,通知将在同一个线程上发出”时,我没有问题,但是我无法通过调度程序找到正确处理这种情况的方法,您能提供一个快速简单的示例吗?@GiacomoPirinoli。这些对象是在您的控制下修改的,还是在您的控制之外修改的?如果在外部,唯一的选项是轮询(基于时间的更新)。如果它在你的控制范围内,我永远不会推荐投票。您可以改为创建包装类,或者在最坏的情况下,直接在视图模型中的对象之间进行方法调用或触发事件。@GiacomoPirinoli如果我没有记错,WPF会自动向UI线程发送IPropertyChanged/ICollectionChanged通知。你看到不同的东西了吗?如果通知是由您正在编写的代码接收的(而不是由WPF绑定接收),那么您必须在接收站点进行封送处理,CollectionChanged事件被激发,然后我得到异常“此类型的CollectionView不存在”
public class MyModel
{
public int Property { get; private set; }
public List<int> DataCollection { get; private set; }
public event EventHandler DataIsReady;
public void AddElementToDataCollectionOnBackgroundThread(int element)
{
Task.Run(()
{
this.DataCollection.Add(element);
DataIsReady?.Invoke(this, EventArgs.Empty);
}
}
}
public class MyViewModel : INotifyPropertyChanegd
{
private MyModel Model { get; };
private int viewModelProperty;
public int ViewModelProperty
{
get => return this.viewModelProperty;
set
{
this.viewModelProperty = value;
OnPropertyChanged();
}
}
public ObservaleCollection<int> ViewItemsSource { get }
public MyViewModel(MyModel model)
{
this.Model = model;
this.Model.DataIsReady += OnModelDataIsReady;
}
private void OnModelDataIsReady(object sender, EventArgs e)
{
// Since the event was raised on a background thread,
// the view model is responsible to marshal the collection change to the UI thread
Application.Current.Dispatcher.InvokeAsync(
() =>
{
this.ViewItemsSource.Clear();
this.Model.DataCollection.ForEach(this.ViewItemsSource.Add));
});
// PropertyChanged is automatically marshalled
// to the UI thread by the framework --> no dispatcher needed
this.ViewModelProperty = this.Model.Property;
}
}