0

I'm trying to work out an issue I'm having with implementing MVVM in WPF. My Contact class below is my model that's being populated by Entity Framework.

public class Contact : INotifyPropertyChanged
{
    public string _firstName;
    public string FirstName
    {
        get
        {
            return _firstName;
        }
        set
        {
            _firstName = value;
            OnPropertyChanged("FirstName");
        }
    }

    public string _lastName;
    public string LastName
    {
        get
        {
            return _lastName;
        }
        set
        {
            _lastName = value;
            OnPropertyChanged("LastName");
        }
    }

    //INotifyPropertyChanged implementation omitted for brevity
}

Here's my ViewModel:

public class ContactViewModel
{
    public Contact MyContact { get; set; }

    public string FullName
    {
        get
        {
            return MyContact.FirstName + " " + MyContact.LastName;
        }
    }
}

So I set my View's datasource to an instance of ContactViewModel, and I'm binding two TextBoxes to MyContact.FirstName and MyContact.LastName. I'm binding a TextBlock to FullName. When I change either of my TextBoxes the Full Name TextBlock doesn't update (obviously, I'm not doing an OnPropertyChanged("FullName") anywhere).

The question is, where do I add OnPropertyChanged("FullName")? I don't necessarily want to modify my model because it's being used elsewhere and I don't to tie it to my ViewModel.

Do I need to rethink my architecture?

Benjamin Gale
  • 12,977
  • 6
  • 62
  • 100
Mike Cole
  • 14,474
  • 28
  • 114
  • 194
  • 1
    Normally it's the ViewModel that implements the INPC interface, not the model. – Brandorf May 10 '13 at 18:41
  • If that were the case, how would I bind to my model? – Mike Cole May 10 '13 at 18:51
  • 1
    Some people duplicate the properties on their viewModel. These properties wrap the properties on the model and your view only binds to your viewModel. Both approaches are valid. – Benjamin Gale May 10 '13 at 18:52
  • Precisely, the viewmodel contains only properties that need to be displayed or manipulated by the view. The properties in the VM use the property of the model as their backing field. – Brandorf May 10 '13 at 19:07

2 Answers2

3

Do I need to rethink my architecture?

This can be solved with your current architecture. You just need to propagate the call from your Contact object to your viewModel object.

You will need to implement INotifyPropertyChanged in the viewModel to achieve this.

Something like this:

public class ContactViewModel : INotifyPropertyChanged
{
    //INotifyPropertyChanged implementation omitted for brevity...

    private Contact _myContact;

    public Contact MyContact 
    { 
        get
        {
            return _myContact;
        } 
        set
        {
            _myContact.PropertyChanged -= myHandler;
            _myContact = value;
            _myContact.PropertyChanged += myHandler;
        }
    }

    public string FullName
    {
        get
        {
            return MyContact.FirstName + " " + MyContact.LastName;
        }
    }

    private void myHandler(Object sender, PropertyChangedEventArgs e)
    {
        OnPropertyChanged("FullName");
    }
}

I would also recommend taking a look at MVVM Foundation as this includes a class called PropertyObserver which is designed to make wiring up this sort of thing much easier.

If you want to take the more MVVM pure approach suggested by Big Daddy, you would need to do something like this:

public class ContactViewModel : INotifyPropertyChanged
{
    // INotifyPropertyChanged implementation omitted for brevity...

    // You will require some way of setting this, either via a property
    // or the viewModel constructor...
    private Contact _myContact;

    public string FirstName
    {
        get { return _myContact.FirstName; }
        set
        {
            _myContact.FirstName = value;
            OnPropertyChanged("FirstName");
            OnPropertyChanged("FullName");
        }
    }

    public string LastName
    {
        get { return _myContact.LastName; }
        set
        {
            _myContact.LastName = value;
            OnPropertyChanged("LastName");
            OnPropertyChanged("FullName");
        }
    }

    public string FullName
    {
        get
        {
            return MyContact.FirstName + " " + MyContact.LastName;
        }
    }
}
Benjamin Gale
  • 12,977
  • 6
  • 62
  • 100
  • From the question: I don't necessarily want to modify my model because it's being used elsewhere and I don't to tie it to my ViewModel. – Mike Cole May 10 '13 at 18:26
  • So this would trigger OnPropertyChanged("FullName") when any property changes, and not just FirstName or LastName, correct? – Mike Cole May 10 '13 at 18:37
  • 1
    @MikeCole Yes, but that's easy to avoid if you want by just adding an `if (e.PropertyName == "FirstName" || e.PropertyName == "LastName")` check to your property change handler. (Also, you'll want to check if `_myContact` is `null` before trying to attach and detach the `PropertyChanged` handler in your setter) – Rachel May 10 '13 at 18:38
  • @Mike Cole - What Rachel said ^ – Benjamin Gale May 10 '13 at 18:39
  • Let me play around with this a bit to make it apply to my case. I think it's what I need though. – Mike Cole May 10 '13 at 18:51
  • 1
    @Rachel I think this is the 2nd time you've helped me today, thanks! – Mike Cole May 10 '13 at 18:52
  • How would this apply to child objects of Contact? It appears I might have simplified my example a bit much. – Mike Cole May 10 '13 at 19:05
  • Do the child objects also implement `INotifyPropertyChanged`? – Benjamin Gale May 10 '13 at 19:14
  • Yes. Perhaps it's just the syntax of hooking to a child object. – Mike Cole May 10 '13 at 19:16
  • `ParentObject.ChildObject.PropertyChanged += ...` although if you are propagating more than a couple of levels deep you may want to re-think your approach. My answer to [this](http://stackoverflow.com/a/15907840/577417) question may help. – Benjamin Gale May 10 '13 at 19:18
  • Intellisense isn't picking up the events on child class. – Mike Cole May 10 '13 at 19:20
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/29756/discussion-between-benjamin-and-mike-cole) – Benjamin Gale May 10 '13 at 19:20
1
Do I need to rethink my architecture?

Maybe...

It looks to me like you're binding your view's properties to your view-model (ContactViewModel) and your model (Contact). Your view can see your public model's properties, etc. via your view-model - I don't think this is good. It looks like a violation of the Law of Demeter. I'd rather see you use your view-model as a wrapper/façade to your model. This creates more work for sure, but I think it gives you a better design and more flexibility. Your view-model will need to implement INotifyPropertyChanged for this to work.

Big Daddy
  • 5,160
  • 5
  • 46
  • 76
  • The issue with this is that my model implements IDataErrorInfo and has validation. Right now this is being passed through to my View and is handled gracefully. Would I lose those validation rules if I mapped to properties of my ViewModel? – Mike Cole May 10 '13 at 18:58
  • 1
    Personally I see nothing wrong with binding directly to the Model, especially in smaller applications with only a single developer working on both the Model and ViewModel layers. It's not the "MVVM-purist" approach, however it's more practical since it's less work and code-duplication. – Rachel May 10 '13 at 19:02
  • @Rachel...You've got a point if it's for a small app, but my experience says otherwise if it's not – Big Daddy May 10 '13 at 19:07
  • @MikeCole...You still keep your validation rules in the model, if you want. You'll just call YourPrivateModel.RaisePropertyChanged() in the VM's property. I can post more code if you'd like. – Big Daddy May 10 '13 at 19:11
  • Yeah, but doesn't the view need to be bound to an entity that implemented IDataErrorInfo to get the automatic validation? How could I reuse the rules that were in the model if I change the binding to use the ViewModel? – Mike Cole May 10 '13 at 19:46
  • @MikeCole...No it doesn't, the VM serves as a wrapper to the model. Here's a most excellent article written by Josh Smith that illustrates the point better than I can - http://msdn.microsoft.com/en-us/magazine/dd419663.aspx. – Big Daddy May 10 '13 at 20:11