WPF Combo Boxes
I thought I’d put together a quick post today on something that annoyed me and that I found unintuitive: binding with the WPF combo box. I’m doing some development following MVVM, and a situation came up in which I had a view model for editing a conceptual object in my domain. Let’s call it a user for the sake of this post, since the actual work I’m doing is nominally proprietary.
So, let’s say that user has a first name and a last name and that user also has a role. Role is not an enum or a literal, but an actual, conceptual reference object. In the view model for a customer edit screen, I was exposing a model of the user domain object for binding, and this model had properties like first name and last name editable via text box. I now wanted to add a combo box to allow for editing of the role by selecting one choice from a list of valid roles.
Forgetting the code for the presentation tier, I did this in the XAML:
Now, I’ve had plenty of occasions where I’ve exposed a list from a view model and then a separate property from the view model called “Selected Item.” This paradigm above would faithfully set my SelectedItem property to one of the list members. But here, I’m doing something subtly different. The main object in the view model–its focus–is UserModel. That is, the point of this screen is to edit the user, not roles or any other peripheral data. So, what I’m actually doing here is trying to bind a reference in another object to one of the items in the list.
What I have above didn’t work. And after a good bit of reading and head scratching, I figured out why. SelectedItem tries to find whatever it’s bound to in the actual list. In the case where I have a list in my view model and want to pick one of the members, this is perfect. But in the case where the list of roles contains objects that are distinct from the user’s role reference, this doesn’t work. The reason it doesn’t work, I believe, is that SelectedItem operates on equality. So if I were to create a list of roles and then assign one of them to the user model object, everything would be fine, since object equals defaults to looking for identical references. But in this case, where the list of roles and the user’s role are created separately (this is done in the data layer in my application) and have nothing in common until I try to match my user role to the list of roles, reference equals fails.
As an experiment, I tried the following:
public class Role
{
public int Id { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as Role);
}
public bool Equals(Role role)
{
return role != null && role.Id == Id;
}
}
After I put this code in place, viola, success! Everything now worked. The combo box was now looking for matching IDs instead of reference equals. I packed up and went home (it was late when I was doing this). But on the drive home, I started to think about the fact that two roles having equal IDs doesn’t mean that they’re equal. ID is an artificial database construct that I hide from users for identifying roles. ID should be unique, and I have a bunch of unit tests that say that it is. But that doesn’t mean that equal IDs means conceptual equality. What if I somehow had two roles with the same ID but different titles, like, say, if I was allowing the user to edit the role title. If for some reason I wanted to compare the edited value with the new one using Equals(), I wouldn’t want the edited and original always to be equal simply because they shared an ID.
I figured I could amend the equals override, but I’m not big on adding code that I’m not actually using, and this is the only place I’m using Equals override. So I went back to the drawing board and read a bit more about the combo box. What I discovered was a couple of additional, rather unfortunately named properties: SelectedValue and SelectedValuePath. Here is what the amended working version looked like without overriding Equals:
ItemsSource is the same, but instead of a SelectedItem, I now have a “SelectedValue” and a “SelectedValuePath”. SelectedValue allows you to specify the binding target by a property of it, and SelectedValuePath allows you to specify which property of the members of ItemsSource should match SelectedValue.
So what the above is really saying is “I have a list of Roles. The role that’s selected is going to be whichever role in the list has an ID property that matches UserModel’s role ID.” And by default, when you change which value is selected, the UserModel’s “RoleId” gets updated with the new selection.
This actually somewhat resembles what I remember from doing Spring and JSP many moons ago, but there’s a little too much rust and probably too many JDKs and whatnot released between then and now for me to know that it’s current. When you actually get down to the nitty gritty of what’s going on, it is intuitive here. I just think the control’s naming scheme is a bit confusing. I would prefer something that indicated the relationship between the item and the list.
But I suppose lack of familiarity always breeds confusion, which in turn breeds frustration. Maybe now that I took the time to understand the nitty gritty instead of just copying what had worked for me in previous implementations, I’ll warm up to the naming scheme.