views:

52

answers:

3

I'm trying to do this as MVVM as possible:
My Model (InterestTypeEntity) implements INotifyPropertyChanged.
My ViewModel (InterestTypeAllViewModel) has an ObservableCollection that binds to a DataGrid. When changes are made to it, it sends those changes (add/remove) to the Database.

the problem is, I want to also be able to update the database when the properties of the objects within the collection change. I'm not sure how to do that? Here's my code so far...

XAML:

    <DataGrid Name="TestGrid" Grid.Row="3" Grid.ColumnSpan="2" AutoGenerateColumns="False"
              ItemsSource="{Binding IntTypes}" SelectedItem="{Binding CurrentIntType}">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Interest ID" Binding="{Binding IntType}" />
            <DataGridTextColumn Header="Interested Parties Description" Binding="{Binding Description}" MaxWidth="500" />
        </DataGrid.Columns>
    </DataGrid>

ViewModel Code:

public ObservableCollection<InterestTypeEntity> IntTypes {
    get
    {
        return DataRepository.InterestTypeEntities;
    }
}

public InterestTypeEntity CurrentIntType { get; set; }

public Int16 IntType
{
    get { return CurrentIntType.IntType; }
    set
    {
        if (value != CurrentIntType.IntType)
        {
            CurrentIntType.IntType = value;
            OnPropertyChanged("IntType");
        }
    }
}

public String Description
{
    get { return CurrentIntType.Description; }
    set
    {
        if (value != CurrentIntType.Description)
        {
            CurrentIntType.Description = value;
            OnPropertyChanged("Description");
        }
    }
}
A: 

General strategy:

Add TwoWay to your binding:

SelectedItem="{Binding CurrentIntType,Mode=TwoWay}"

And then subscribe to the changed event of the observable collection in your ViewModel. When the collection changes, send it to your model/DAL and persist it.

Jason
Well, the only problem I see with that is that I'm not actually changing the collection... instead, I'm changing the properties of an object ALREADY IN the collection. Which means that the event for the collection changing never gets fired.
myermian
According to the docs, the ObservableCollection changed event fires "when an item is added, removed, **changed**, moved, or the entire list is refreshed." http://msdn.microsoft.com/en-us/library/ms653375.aspx
Jason
But the item is a reference type, so the reference doesn't change. The values within the reference are changing.
myermian
@Jason: Well, sort of. `foo[i] = new Foo()` changes the `i`th element of `foo`. `foo[i].Bar = 'x'` doesn't.
Robert Rossney
+1  A: 

Please see my answer here. It will give you an observable collection that tells you when the collection changes, or when an item within the collection changes.

HTH,
Kent

Kent Boogaart
What is this `ItemPropertyChangedEventArgs<T>`? C# can't find it...
myermian
Also, I get a weird error when trying to use the `ItemObservableCollection<T>` class. I keep getting this Compiler Error CS0311: The type 'type1' cannot be used as type parameter 'T' in the generic type or method '<name>'. There is no implicit reference conversion from 'type1' to 'type2'. It's saying that there is no implicit conversion from my `InterestTypeEntity` to `System.ComponentModel.INotifyPropertyChanged`.
myermian
@myermian: that's not a weird error - it's as a result of the generic constraint. Type `T` must implement `INotifyPropertyChanged` in order for the `ItemObservableCollection<T>` to use it. Therefore, your `InterestTypeEntity` must implement `INotifyPropertyChanged`. Also, I've updated the other post to include the `ItemPropertyChangedEventArgs` class.
Kent Boogaart
@Kent: Trust me it is strange because I have InterestTypeEntity as a child class of AbstractEntity. AbstractEntity implements INotifyPropertyChanged. In fact, even if I have InterestTypeEntity implement INotifyPropertyChanged directly it still gives me that error... :-/
myermian
@Kent: I found out it was because my Entity types aren't implementing INotifyPropertyChanged, My ViewModels are. That's why I kept getting that error.
myermian
+2  A: 

Don't create a collection of model objects, and don't implement IntType and Description properties on your (current) view model. And unless you have some other reason to do so, don't implement property-change notification in your model.

Instead, make IntTypes a collection of InterestTypeEntityViewModel objects.

This class wraps InterestTypeEntity. It exposes IntType and Description properties that a) wrap the underlying InterestTypeEntity properties and b) performs property change notification. If you make its constructor take an InterestTypeEntity argument, it's easy to populate in your view model:

IntTypes = new ObservableCollection<InterestTypeEntityViewModel>(
   DataRepository.InterestTypeEntities.Select(x => new InterestTypeEntityViewModel(x));

Bind the ItemsSource to this collection. (Also, make CurrentIntType a property of type InterestTypeEntityViewModel and raise PropertyChanged when it changes.)

Edit:

If the owning view model needs to be notified when properties change on the items in its collection, it's pretty simple to make it handle the PropertyChanged events they're raising. In your constructor, add:

foreach (InterestTypeEntityViewModel vm in IntTypes)
{
  vm.PropertyChanged += InterestTypeEntityViewModel_PropertyChanged;
}

and this method:

private void InterestTypeEntityViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
   InterestTypeEntityViewModel vm = (InterestTypeEntityViewModel) sender;
   // check e.PropertyName and do whatever you need to do here.
}

Don't forget to unregister the event handler if you remove an object from your collection; otherwise, the child view model objects won't get disposed until the parent one does.

Note, by the way, that by implementing the view models this way, you can exercise a lot of control over your updates to the underlying entity model. For instance, you can implement a command in your parent view model that does all of the updates in a single operation, and another one that lets the user discard all of the changes they've made in the UI without performing an update. And all of this logic is very nicely decoupled from the actual presentation layer.

Robert Rossney
Well, I did the necessary changes, and I can get my InterestTypeViewModel to accept changes, but how does that bubble up to the InterestTypeAllViewModel which contains that ObservableCollection of viewmodels?
myermian
Not all by itself, but it's simple to make it do so. See my edit.
Robert Rossney
Got it working this way. I was really hoping to have that ItemObservableCollection working, since that would mean I didn't have to link additional events and such. But, gotta go with what works. And, if I ever decide to have a view that uses InterestTypeViewModel (not the "All" version) it'll come in handy.
myermian
It's worth getting handy at having view model objects communicate with each other through events. Using events really simplifies a lot of UI issues.
Robert Rossney