Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validating bound ObservableCollection in ViewModel using MVVM Pattern

Tags:

c#

mvvm

wpf

I'm new to MVVM, just recently started my first project following the MVVM pattern. I have an issue trying to validate an ObservableCollection using the IDataErrorInfo Interface. My ObservableCollection looks like this:

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

And my XAML like this:

<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}" />

It's just a simple listBox containing objects, when you select an item, the selected objects properties is displayed in the textboxes, and is then bound to the listbox object.

My Problem is, that when I set my code up like this, the only way I can figure out how to validate my data is in the Domain Model, which really isn't a good practise, I'd like to validate in the ViewModel before it gets there. Basically I want to validate each property in the MagazineRepository, in the ViewModel, How would you go about doing this?

PS: I'm new to posting on this board (and programming boards in general) if my question is lacking information, please let me know and I will supply the needed details.

Thanks a lot.

like image 641
clean_coding Avatar asked Nov 13 '12 08:11

clean_coding


2 Answers

If i understand correctly you want to validate the Magazine object. If that's the case, one way to do it is to wrap that class in a viewmodel, let's call it MagazineVM, that implements IDataErrorInfo and keep the magazine object updated. You then bind to the view a list of MagazineVM. As a very simple example:

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
}
like image 97
Dtex Avatar answered Nov 18 '22 01:11

Dtex


Firstly, as Dtex says, you should use a MagazineViewModel class rather than a Magazine class. E.G.

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

}

The important property to note is "public string this[string columnName]". ColumnName will be one of your bound properties and this is where you can do validation.

The next thing to consider is your MainViewModel (Your DataContext). E.G.

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)
  {
    //...
  }
}

The only thing missing here is how you copy the changes back to the original view model. You could do it before the selected item changes (if the ItemToEdit is valid) or have a Commit button that is only enabled when the ItemToEdit is valid. If you can allow your original view models to go into an invalid state you don't need to worry about the copying.

Finally the XAML

An implicit style to show the error tooltip

<Style
  TargetType="{x:Type TextBox}">
  <Setter
    Property="ToolTip"
    Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Style>

And the controls and bindings

<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}" />

The TextBoxes bind to ItemToEdit. ItemToEdit will be an in-sync copy of the SelectedItem.

like image 3
wdavo Avatar answered Nov 18 '22 01:11

wdavo