I am often asked what my favorite feature of Silverlight is, and my answer in always “binding”. The binding in Silverlight and WPF was finally done right and it is an incredibly powerful tool. However, you still run into a few “gotchas” every once in a while and one happened to me the other day with the Combobox
. If binding to a Combobox
is nothing new to you, then you can skip past the Binding to a Combobox section to the description and setup of the issue.
Binding to a Combobox
Before we can get to the “gotcha” I came across, we first need to set up how to use binding with a Combobox
. For my example, we will be using a simplistic MVVM architecture because it helps to demonstrate the point a bit better. Just as a reminder, you don’t have to use binding with the Combobox
or to recreate the issue described in this post.
So let’s get started with our view model. For our purposes, the view can be rather simplistic, needing only 2 properties. I added a method to populate our list data just for completion.
public class MainPageViewModel : INotifyPropertyChanged
{
private string _favoriteConf;
public MainPageViewModel()
{
PopulateConfs();
}
public string FavoriteConf
{
get { return _favoriteConf; }
set
{
_favoriteConf = value;
NotifyPropertyChanged("FavoriteConf");
}
}
private List<string> _confs;
public List<string> Confs
{
get { return _confs; }
set
{
_confs = value;
NotifyPropertyChanged("Confs");
}
}
private void PopulateConfs()
{
Confs = new List<string>()
{"MIX", "TechEd", "PDC", "DevConnections"};
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
As you can see, we have just the two properties: FavoriteConf
and Confs
. Our constructor calls the PopulateConfs
to add some data for us to work with. (Yes, Conf
= Conference
, I just didn’t want to write out Conference too many times.
).
Now that we have our view model, the next step would be to create our XAML. For this demonstration, we are going to show two elements: a Combobox
and a TextBox
. The Combobox
will have our list of conferences (bound to the Confs
property) and the TextBox
will show the selected conference by binding to the FavoriteConf
property.
If you look at the Combobox
, you will notice two different binding statements. The first one is the ItemsSource
is bound to the Confs
property. This is done with the default OneWay
binding, since our Combobox
will never be modifying the list of data. The second is the SelectedValue
which is bound to the FavoriteConf
property. Since we want to update the FavoriteConf
value in our view mode when the user selects a drop down, then we do a two-way binding.
Here is our XAML:
<UserControl x:Class="ComboBoxBinding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ComboBoxBinding"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:MainPageViewModel x:Key="viewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White"
DataContext="{StaticResource viewModel}">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"
Background="LightGray"
Height="Auto" Width="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center"
Text="Select Favorite Conference"/>
<TextBlock Text="Choose One :" Grid.Row="1"/>
<ComboBox Grid.Row="1" Grid.Column="1"
SelectedValue="{Binding FavoriteConf, Mode=TwoWay}"
ItemsSource="{Binding Confs}"/>
<TextBlock Text="You Picked :" Grid.Row="2"/>
<TextBox Text="{Binding FavoriteConf}" Grid.Row="2"
Grid.Column="1"/>
</Grid>
</Grid>
</UserControl>
Note: Since this is a demonstration, I’m creating a static
copy of our view model in the resources of our XAML and binding the LayoutRoot
’s DataContext
to this resource.
If you run the project, the application should look something like this:
When you select an item from the Combobox
, the TextBox
will update with the selected value of the Combobox
.
Great! It works, everything is added as it should be, right?
Note: Silverlight 4 added some additional properties to the Combobox
to make binding easier. You can read a walkthrough of using them on John Papa’s blog here.
Ordering of XAML Properties: The Gotcha
So let’s take our example one step further, but setting a default value for our FavoriteConf
value. To do this, we are going to change up our constructor to our view model by adding another line of code:
public MainPageViewModel()
{
PopulateConfs();
FavoriteConf = "MIX";
}
If we run our example again, this is what we get:
Not exactly what I was looking for. The TextBox
picked up the default value of the FavoriteConf
, but why didn’t the Combobox
? Well it turns out, after much head scratching, that the answer is in our XAML.
Let’s take a look at the XAML of our Combobox
again:
<ComboBox Grid.Row="1" Grid.Column="1"
SelectedValue="{Binding FavoriteConf, Mode=TwoWay}"
ItemsSource="{Binding Confs}"/>
Do you see where we went wrong?
It looked right to me for a while. However, it turns out that ordering of the properties in XAML can affect the behavior of the control. If we switch the order of the ItemsSource
and the SelectedValue
, your XAML will now look something like this:
<ComboBox Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Confs}"
SelectedValue="{Binding FavoriteConf, Mode=TwoWay}"/>
Now, if we run the two examples side-by-side, we get the following:
It turns out that ordering in your XAML can be important and something to look out for if you are not getting the results that you are looking for. If you have found yourself in a similar situation, hopefully this post will help.
You can download the code for this example here.