Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two-way binding problem with WPF ComboBox using MVVM

Tags:

I have an Activity object with many properties. One of them is as follows:

public ActivityStatus Status
{
    get { return status; }
    set { status = value; NotifyPropertyChanged("Status"); }
}

The ActivityStatus class has just two properties:

public Guid Guid
{
    get { return guid; }
    set { guid = value; NotifyPropertyChanged("Guid"); }
}
public string Name
{
    get { return name; }
    set { name = value; NotifyPropertyChanged("Name"); }
}

and the Equals methods:

public override bool Equals(object otherObject)
{
    if (!(otherObject is ActivityStatus)) return false;
    return Equals(otherObject as ActivityStatus);
}
public bool Equals(ActivityStatus otherStatus)
{
    if (!(otherStatus is ActivityStatus) || otherStatus == null) return false;
    return Guid == otherStatus.Guid && Name == otherStatus.Name;
}

I have an ActivityViewModel class as the DataContext of an ActivityView class. The ActivityViewModel has an Activity property of type Activity and among others, an ActivityStatuses property of type ObservableCollection<ActivityStatus>. In the ActivityView I have a ComboBox declared as follows:

<ComboBox ItemsSource="{Binding ActivityStatuses}" 
          SelectedItem="{Binding Activity.Status, Mode=TwoWay}"
          DisplayMemberPath="Name" />

This allows me to select an ActivityStatus from the ComboBox and this correctly updates the Status property of the Activity in the Activity property of the viewmodel. The problem is with the two-way binding... when loading a new Activity, the ComboBox.SelectedItem does not update to show the Activity.Status property value.

Using this declaration of the ComboBox, the SelectedItem is bound to the ActivityStatus object in the Activity and this is a different object to the one with the same values in the viewmodel ActivityStatuses property. Therefore the WPF Framework does not think that the items are the same and does not select the item in the ComboBox.

If I assign the item from the collection with the same values to the Activity.Status property after loading each Activity, then the ComboBox finds a match in its ItemsSource collection and correctly sets the SelectedItem property displaying the value. I don't really want to have to do this though because I have many other similar properties in th Activity class and I'll have to repeat this code anywhere I want to two-way bind to ComboBoxes.

So I also tried binding to ActivityStatus.Guid property as follows:

<ComboBox ItemsSource="{Binding ActivityStatuses}" 
          SelectedValue="{Binding Activity.Status.Guid, Mode=TwoWay}"
          SelectedValuePath="Guid" 
          DisplayMemberPath="Name" />

This correctly selected the object with the same Guid as the one in the Activity.Status property from the ComboBox.ItemsSource collection when loading different Activity objects. The problem with this method is that the SelectedValue is bound to the ActivityStatus.Guid property in the ActivityStatus object and and so when changing values in the UI, only the 'Guid' property of the ActivityStatus object would update, leaving the name unchanged. The object in the Activity.Status property does not change except for the value of its Guid property.

As you can see, I also tried implementing the Equals method as I assumed that the ComboBox would use this to compare the objects, but it didn't make any difference. So finally, I am at a loss and keen to find a simple clean way to fix this problem... hopefully, there's a simple property that I've missed on the ComboBox.

I simply want to be able to select an item in the ComboBox and have the Activity.Status object change accordingly and change the value of the Activity.Status property from code and have the ComboBox.SelectedItem also update accordingly. I'd be grateful for any advice.

UPDATE >>>

After reading Will's response, I tried his code sample in a new solution and saw that it worked as expected. I then examined his code thorouhly and saw that it was the same as mine, so ran my own solution again (for the first time since this post). To my complete surprise, it worked as expected without me changing any code!

This puzzled me greatly and I've spent some time to find out what happened. It turns out that the problem was/is Visual Studio 2010! I had added the Equals methods to my data types as the last stage. For some reason Visual Studio did not build the data types project when running the application.

So the application must have been using an older dll file and my changes were not being used... I did wonder why my break points on the Equals methods were never hit. This led to my assumption that implementing the Equals methids did not help. Visual Studio has the same behaviour today and that's how I found out what had happened.

I checked the project build order in my solution, but that lists the data types project in the correct place in the order. When running the application though, the Output window in Visual Studio shows the project dlls being loaded in a different order. I'm not sure why running the application no longer does a complete build, but at least I know that I have to build that project after making changes in it before running the application.

FINAL UPDATE >>>

I just found out why my data types project was not building... I looked in the Configuration Manager window and saw that somehow the Platform was incorrect for that project and the Build checkbox had become unchecked! I have no idea how this happened, but am much relieved that I finally got to the bottom of the problem.