Thursday, October 1, 2009

Silverlight ComboBoxes and the Importance of Equals

So I was struggling for awhile with a binding issue in my Silverlight application, and learned the hard way that I forgot my basics.

The scenario is fairly common. I have a view model that contains the entity I want to edit along with supporting lists. The common example I see on the web is something like this:

public class CityEntity 
{
   public int ID { get; set; }
   public string Name { get; set; }
}

public class MyEntity 
{
    public CityEntity CurrentCity { get; set; }
}

public class ViewModel 
{
   public MyEntity Entity { get; set; }
   public ObservableCollection<CityEntity> Cities { get; set; }
}

This is purely contrived but you get the point ... I have basic "building block" entities that are composed into the entity I wish to edit, so my view model hosts that entity as well as some collections for binding to drop downs so I can change properties on the entity.

My ComboBox (I thought) was straightforward:

...
<ComboBox Style="{StaticResource SimpleComboBoxStyle}" 
   ItemsSource="{Binding Cities}" 
   SelectedItem="{Binding Path=Entity.CurrentCity,Mode=TwoWay}" DisplayMemberPath="Name"/> 
.. 

Imagine my chagrin when I'd pop up my window and the combo box ... never ... showed ... the city. What was wrong? I had the right path, the right selected item. Was I missing something?

It turns out, I was.

The framework deals with the lists and bindings as objects, so unless the actual reference to the city on the main entity matches the entity in the list, there is no way to "set" the selected item (the framework doesn't know I intend for them to match).

The solution was quite simple: most of my entites on the Silverlight side derive from a base class, call it SimpleEntity. By implementing Equals (and by way of that, the hash code as well), the framework can now understand how to take my entity over here and match it to the entity in the list over there. Once it was implemented, I pushed it out and voila! my combo boxes started populating with the right value.

Jeremy Likness

6 comments:

  1. Can you post the code please for this solution?

    ReplyDelete
  2. A bit of code showing how you solved this would be helpful. This has been a big headache for me. Thanks!

    ReplyDelete
  3. In the above class, I'd add this:

    override int GetHashCode()
    {
    return ID.GetHashCode();
    }

    override bool Equals(object obj)
    {
    return obj is CityEntity && ((CityEntity)obj).ID.Equals(ID);
    }

    This assumes I base equality on my identifier and not the name.

    ReplyDelete
  4. I use a business entity that I created on server side. I use WCF to communicate with my Silverlight application. So How can I do to override my Equals and GetHashCode Method?

    ReplyDelete
  5. If it is from a WCF entity, I'd make a local entity and have an extension method, like this (Foo is the WCF proxy class):

    public class FooLocal : Foo
    {
    override GetHashCode() ...
    override bool Equals ...
    }

    public static class FooExtensions
    {
    public static FooLocal ToLocal(this Foo incoming)
    {
    return new FooLocal { Name=incoming.Name, Address = incoming.Address ... etc };
    }
    }

    Then you can simply do:

    public FooLocal { get; set; }
    public Foo
    {
    get { return FooLocal; }
    set { FooLocal = value.ToLocal(); }
    }

    or something similar.

    ReplyDelete