Why DataBinding With ComboBoxes is NonTrivial

…and Why Coding Is So Much Fun

iStock_BoyWithBrainXSmall A Gonzo Adventure In What You Can Learn When You’re Not Doing What You Are Supposed To Be Doing And You’ll Be Done In Just A Minute, It Almost Works

 

[Read the VB version of this post here

 

 

 

Two days ago I thought I was late getting the code done for my latest tutorial (it turns out that I was actually early but that is a different saga).  I got up early and swallowed lots of pills and drank lots of coffee and took a short nap and then came into the office and sat down at the computer and said, “OK, I just need 20 pages on 3d in Silverlight 3; piece of cake. But anyone can write a tutorial on 3d, what I want to do is show how it fits into writing something real. It isn’t just a toy for little kids, this thing has sharp edges, a person could hurt themselves, we want to put this in context…”

Now, the coolest use of 3d I happened to have seen in the last six or seven minutes was a free program that gets the lyrics for whatever you’re listening to. Its entire UI (or UX as we RIA cognoscenti call it)  is a panel about 400 x 600 pixels. This is nearly entirely filled with scrollable lyrics but there are a couple small icons, including, in the lower left hand corner, a very attractive italicized “i”

If you hover over that seductive little i, it lights up invitingly. Click on the i and hey! voila! the panel rotates 180 degrees revealing its “back” where you can find settings and other cool stuff that you don’t need every day but is very handy to have easily available.

When you finish playing with the settings and have put back all the things you broke, you click and the panel rotates back.  Perfect.  Useful, elegant, space saving and classy.  I want one. More urgent:  I want to build one.

Zo!  That is the next tutorial, but that is not what I’m writing about here.

What I’m writing about here is what happened when I set out to build one, that I did not intend, and was not part of the project and I had no business spending time on. it was long ago and far away….

I had chosen to make my panel databound, and because I was listening to music at the time I was writing the code, I decided that the cards would provide basic information about a musician on the front, and the musician’s bio on the back.

To insinuate a tenuous business connection I created an Employee class to hold all this information (who knows, maybe these guys work for my record company?), and I used the same fields you would (more or less):

public string FullName { get; set; }
public string NickName { get; set; }
public DateTime Birthdate { get; set; }
public string Locality { get; set; }
public string Phone1 { get; set; }
public int Phone1Location { get; set; }
public string Phone2 { get; set; }
public int Phone2Location { get; set; }
public string JobTitle { get; set; }
public string JobNickName { get; set; }
public double JobLevel { get; set; }
public string Salary { get; set; }
public string Performance { get; set; }
public string Bio { get; set; }

Now, this was certainly not the final set of properties, and there was much to fix, but as I created the UI for the phone numbers, it was blazingly clear to me that I wanted the phone type to be in a combo box

ComboBox

That was when the trouble began.  When you chose a new artist all the fields changed…. except the Phone Location (cell, home, etc.)

The more I looked at it the more baffled I became. Set aside that the code was a quick hack with insufficient decoupling of UI from data, it didn’t even work.

After a while this became the night’s obsession, culminating in my doing what I always tell others to do but never quite get around to doing myself: write a tiny program that demonstrates the problem in isolation and ask for help.

Thus was born: ComboBoxProblem.xaml

   <UserControl x:Class=”ShowTheProblem.MainPage”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
    Width=”400″ Height=”300″>
   <StackPanel x:Name=”mySP”>
      <TextBlock x:Name=”Phone”
                 Height=”20″
                 VerticalAlignment=”Bottom”
                 Text=”{ Binding Phone, Mode=OneWay }” />
      <ComboBox x:Name=”PhoneType”
                HorizontalAlignment=”Left”
                Width=”100″
                VerticalAlignment=”Bottom”
                SelectedIndex=”{Binding PhoneLocationID, Mode=OneWay}”
                ItemsSource=”{Binding}”>
         <ComboBox.ItemTemplate>
            <DataTemplate>
               <TextBlock x:Name=”PhoneTypeItem”
                          Text=”{Binding Location}” />
            </DataTemplate>
         </ComboBox.ItemTemplate>
      </ComboBox>
      <Button x:Name=”mybutton”
              Content=”Press Me”
              Margin=”10″ />
   </StackPanel>
</UserControl>

I’ll spare you the supporting code (I hate showing broken code) but I posted this on an internal list and immediately got back two interesting answers: there’s a bug in combo box (there isn’t) and if you set the Mode to TwoWay instead of OneWay everything works.

The Great White Whale

Now I know a Great White Whale when I see one, so call me Ishmael and I was off, spending much of the rest of the night trying to figure this out, and realizing as I went that I truly hated the fact that the Person object  had to know so much about the phone location collection that populated the ComboBox.

It turns out that there were a number of interlocking problems here. Mike Hillberg (a Principal Architect at Microsoft) pointed out that with one-way binding as soon as the control changes SelectedItem, the binding is replaced with the new value.  He also pointed out that I/we were having the person’s GetPhoneLocation return new objects, and just because two objects have locations set to the same string (e.g., “Home”) doesn’t mean the two references point to the same object in memory. (yes, yes, but by then it was very late).

His excellent blog post got me most of the way towards where I needed to go, but I rebelled at the idea of imposing WPF semantics on Silverlight, and I really wanted to constrain the values so that they were self-identifying; that is I wanted more of an enumeration than a class.

Another Microsoftie offered his solution, which I liked very much but his approach included the assumption that the person object would contain an ID that was an offset into the collection of phone numbers used to populate the combo box. That was where I choked.

How To Make An Enumeration Behave Like A Collection

What I wanted was to use an enumeration, with all the wonderful Intellisense support and the intuitive scoping it provides. But you can’t hand an enumeration to the ItemsSource property of a comboBox, nor can you treat enumerations as strings (at least, not without a little work).

After a bit of tinkering, (and then a little hammering, followed by some smashing and yelling)  the solution that finally caught the leviathan (though I’m afraid ol’ Starbuck was never seen again)  was this:   

  1. Create a class that represents one location (so that you can have a collection of said locations, so that each member of the combobox can be an instance of that class).
  2. Obtain the enumeration semantics by giving the class a single member that is an enumerated constant.
public enum PhoneLocationEnum
{
Home,
Office,
Cell,
Other
};
public class PhoneLocation
{
public PhoneLocationEnum Location { get; set; }
}

 

You can now create a collection of these, initializing them with full support from Intellisense,

PhoneLocation2 

I’m so happy.

But I need an ItemsSource

What we need is an observable collection to serve as the ItemsSource for the comboBox. We’d like to add each of the PhoneLocation objects to that collection, but where will that collection live?  What we really need is a class that is a collection of Phone Location objects. Aha!

public class PhoneLocations : ObservableCollection<PhoneLocation>

This sneaky bit of C# makes PhoneLocations a kind of or derived type of Observable collection of PhoneLocaitons and as such fully legal as an ItemsSource. To wire it up I need to create a namespace (local) that points to this assembly and then declare this puppy in the resources section of the Xaml file,

<UserControl.Resources>
<local:PhoneLocations x:Key="phoneLocations"/>

[ Thank you Hamid Mahmood for showing me this bit of Xaml ]

Now, finally, I can put the pieces together, Here is the Xaml

<UserControl x:Class="ComboBoxSample.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ComboBoxSample"
Width="400" Height="300"
Loaded="UserControl_Loaded">
<UserControl.Resources>
<local:PhoneLocations x:Key="phoneLocations"/>
<local:EnumToStringConverter x:Key="converter" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel x:Name="mySP" Margin="20">
<TextBlock x:Name="Phone"
Height="20"
VerticalAlignment="Bottom"
Text="{ Binding Phone, Mode=OneWay}" />
<ComboBox x:Name="PhoneType"
HorizontalAlignment="Left" Width="100"
VerticalAlignment="Bottom"
ItemsSource="{StaticResource phoneLocations}"
SelectedItem="{Binding PhoneLocVal,
Mode=TwoWay,
Converter={StaticResource converter},
ConverterParameter={StaticResource phoneLocations}}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Location}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button x:Name="mybutton"
Content="Click Me" />
</StackPanel>
</Grid>
</UserControl>

ComboBox has three important binding-related statements:

  1. The ItemsSource is bound to the phoneLocations that was defined in the resources and thus tied to the observable collection of PhoneLocation objects

  2. The SelectedItem is bound to PhoneLocVal which as you’ll see in a moment is the integer value stored in Person that is directly derived from the enumerated constant for the phone location

  3. The DataTemplate is bound to Location which is the (only) property of the PhoneLocation object and which is of type PhoneLocationEnum

This last point is critical.  At the risk of repeating; we declare the enumeration, then we declare a PhoneLocation class that has a single member whose type is that enumeration, and finally we declare a class that is an observable collection of those objects whose member is of the enumerated type!

Type Conversion

There is a second statement in the resources section of the Xaml that I’ve glided right past,

<local:EnumToStringConverter x:Key="converter" />

This declares a type converter, and we see it invoked in the ComboBox 

SelectedItem="{Binding PhoneLocVal, 
Mode=TwoWay,
Converter={StaticResource converter},
ConverterParameter={StaticResource phoneLocations}}" >

In this declaration we are saying that while we are binding to the PhoneLocVal property we don’t expect to know how to display that, and so we’ll need to use the declared converter and, further, we’ll pass in the phoneLocations as a parameter.

The code for the conversion implements IValueConverter, and thus has two methods: Convert and ConvertBack:

public class EnumToStringConverter : IValueConverter
{
public object Convert(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture )
{
int phoneLocationAsInteger = (int)value; ;
PhoneLocations locations = parameter as PhoneLocations;
if (locations != null)
{
foreach (PhoneLocation pl in locations)
{
if ((int)pl.Location == phoneLocationAsInteger)
return pl;
}
}
return null;

}

public object ConvertBack(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture )
{
throw new NotImplementedException();
}

}

We cast the value we’re given to an integer and the pareameter to a PhoneLocations collection. We then iterate through the collection checking to see if we find a member whose Location when cast to an int is equal to the value we were given. If so, we have our winner, and we return it.  This converts an integer value to a Phone Location while ensuring that we stay within the boundaries of the enumerated constants.

Once I had that, I could sleep.

The complete source code is available here.

womc100

This code was built with Silverlight 3 Beta.

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in z Silverlight Archives. Bookmark the permalink.

5 Responses to Why DataBinding With ComboBoxes is NonTrivial

Comments are closed.