Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/326.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/wpf/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# MVVM-从模型到视图模型,保持模型不变_C#_Wpf_Mvvm_Binding_Dispatcher - Fatal编程技术网

C# MVVM-从模型到视图模型,保持模型不变

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; } ... } 您的模型完

假设我有一个代表我的模型的第三方后端[C#.NET标准]库;此库纯粹用作数据处理后端工具。 显然没有GUI,只提供了一些公共只读寄存器,以便允许其他父/所有者代码观察这些寄存器的状态。举个简单的例子:

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;
  }
}