使用MVVM模式验证ViewModel中的绑定ObservableCollection

5

我是MVVM的新手,最近刚刚开始了我的第一个遵循MVVM模式的项目。我在尝试使用IDataErrorInfo接口验证ObservableCollection时遇到了问题。我的ObservableCollection看起来像这样:

ObservableCollection<Magazine> magazineRepository;
    public ObservableCollection<Magazine> MagazineRepository
    {
        get { return magazineRepository; }
        set
        {
            if (value != null)
            {
                bladRepository = value;
                OnPropertyChanged("MagazineRepository");
            }
        }
    }

我的XAML如下:

<ListBox x:Name="listMagazineRepository"
                 Grid.ColumnSpan="2"
                 ItemsSource="{Binding}" 
                 DataContext="{Binding MagazineRepository}"
                 DisplayMemberPath="Navn" 
                 SelectedItem="{Binding Path=SelectedItem}"/>

        <TextBox x:Name="txtName" Grid.Row="1" Grid.Column="0"
                    Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
        <TextBox x:Name="txtPrice" Grid.Row="2" Grid.Column="0"
                    Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />

这只是一个简单的包含对象的listBox,当您选择一个项目时,所选对象的属性会显示在文本框中,然后绑定到listbox对象。
我的问题是,当我像这样设置代码时,我唯一能想到的验证数据的方法是在域模型中进行,这确实不是一个好的做法,我想在ViewModel中验证它。基本上,我想在MagazineRepository中验证每个属性,在ViewModel中,您会如何处理?
PS:我是第一次在这个论坛(以及编程论坛)发帖,如果我的问题缺乏信息,请告诉我,我会提供所需的详细信息。
非常感谢。

我在你发布的代码中没有看到ViewModel,但是通常的方法是让你的ViewModel实现IDataErrorInfo。 - Alan
抱歉,我以为这是隐含的。集合在我的ViewModel中,它确实实现了IDataErrorInfo,但当文本框更改集合中的属性时,我需要验证该属性,而我无法确定如何准确地执行此操作。因此,有2个文本框,每个绑定到集合中的一个属性,当该属性更改时,我需要对其进行验证,按照我学习的方式使用IDataErrorInfo只能验证整个集合。 - clean_coding
2个回答

3

如果我理解正确,您想验证杂志对象。如果是这样,一种方法是将该类包装在一个视图模型中,称之为MagazineVM,实现IDataErrorInfo并保持杂志对象更新。然后,您可以将MagazineVM的列表绑定到视图上。以下是一个非常简单的示例:

public class MagazineVM : IDataErrorInfo, INotifyPropertyChanged
{
   private Magazine _magazine;

   public int FirstMagazineProperty
   {
      get { return _magazine.FirstMagazineProperty; }
      set { _magazine.FirstMagazineProperty = value; RaisePropertyChanged("FirstMagazineProperty"); }
   }

   //INotifyPropertyChanged implementation

   //IDataErrorInfo implementation
}

3
首先,正如Dtex所说,您应该使用MagazineViewModel类而不是Magazine类。例如:
public class MagazineViewModel : INotifyPropertyChanged, IDataErrorInfo
{
  private string navn;
  private string pris;
  private string error;

  public string Navn
  {
    get { return navn; }
    set
    {
      if (navn != value)
      {
        navn = value;
        RaisePropertyChanged("Navn");
      }
    }
  }
  public string Pris
  {
    get { return pris; }
    set
    {
      if (pris != value)
      {
        pris = value;
        RaisePropertyChanged("Pris");
      }
    }
  }
  public string Error
  {
    get { return error; }
    set
    {
      if (error != value)
      {
        error = value;
        RaisePropertyChanged("Error");
      }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

  public string this[string columnName]
  {
    get
    {
      var result = string.Empty;

      switch (columnName)
      {
        case "Pris":
          if (string.IsNullOrWhiteSpace(Pris))
          {
            result =  "Pris is required";
          }
          break;
        case "Navn":
          if (string.IsNullOrWhiteSpace(Navn))
          {
           result =  "Navn is required";
          }
          break;
      }

      return result;

    }
  }

  private void RaisePropertyChanged(string PropertyName)
  {
    var e = PropertyChanged;
    if (e != null)
    {
      e(this, new PropertyChangedEventArgs(PropertyName));
    }
  }

}

需要注意的重要属性是“public string this[string columnName]”。ColumnName 将是您绑定的属性之一,这是您可以进行验证的地方。

下一个需要考虑的是您的 MainViewModel(您的 DataContext)。例如:

public class MainViewModel : INotifyPropertyChanged
{
  //Use a readonly observable collection. If you need to reset it use the .Clear() method
  private readonly ObservableCollection<MagazineViewModel> magazines = new ObservableCollection<MagazineViewModel>();

  private MagazineViewModel selectedItem;

  //Keep the item being edited separate to the selected item
  private MagazineViewModel itemToEdit;

  public ObservableCollection<MagazineViewModel> Magazines { get { return magazines; } }
  public MagazineViewModel SelectedItem
  {
    get { return selectedItem; }
    set
    {
      if (selectedItem != value)
      {
        selectedItem = value;
        RaisePropertyChanged("SelectedItem");
        //When the selected item changes. Copy it to the ItemToEdit
        //This keeps the the copy you are editing separate meaning that invalid data isn't committed back to your original view model
        //You will have to copy the changes back to your original view model at some stage)
        ItemToEdit = Copy(SelectedItem);
      }
    }
  }
  public MagazineViewModel ItemToEdit
  {
    get { return itemToEdit; }
    set
    {
      if (itemToEdit != value)
      {
        itemToEdit = value;
        RaisePropertyChanged("ItemToEdit");
      }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

  public MainViewModel()
  {
    //Ctor...
  }

  //Create a copy of a MagazineViewModel
  private MagazineViewModel Copy(MagazineViewModel ToCopy)
  {
    var vm = new MagazineViewModel();
    vm.Navn = ToCopy.Navn;
    vm.Pris = ToCopy.Pris;
    return vm;
  }

  private void RaisePropertyChanged(string PropertyName)
  {
    //...
  }
}

这里唯一缺少的是如何将更改复制回原始视图模型。如果ItemToEdit有效,您可以在选定项目更改之前执行此操作,或者只在ItemToEdit有效时启用提交按钮。如果您允许原始视图模型进入无效状态,则无需担心复制问题。
最后是XAML:
一个隐式样式用于显示错误工具提示。
<Style
  TargetType="{x:Type TextBox}">
  <Setter
    Property="ToolTip"
    Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Style>

控件和绑定

<ListBox
  ItemsSource="{Binding Magazines}"
  DisplayMemberPath="Navn"
  SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
<TextBox
  Margin="5"
  x:Name="txtName"
  Grid.Row="1"
  Grid.Column="0"
  Text="{Binding ItemToEdit.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox
  Margin="5"
  x:Name="txtPrice"
  Grid.Row="2"
  Grid.Column="0"
  Text="{Binding ItemToEdit.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />

文本框绑定到ItemToEdit。ItemToEdit将会是SelectedItem的同步副本。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接