Stumbling Through: WPF (Databinding Part V)
So I thought about another scenario that I'd like to prove out using databinding in WPF: Lets create a databound combobox that displays a dropdown list of all possible states, selecting the state of the currently selected address. That was a long-winded way of saying: 'I want to choose the State of an address from a list'. Following the status-quo that we established in previous posts, lets create a 'StateCollection' and 'State' class, though we'll avoid all the notification and error handling stuff just for simplicity sake. The classes will look like so:
using System.Collections.ObjectModel;
namespace StumblingThroughWPFPartI
{
public class StateCollection : ObservableCollection<State>
{
public void LoadAll()
{
this.Add(new State("IL", "Illinois"));
this.Add(new State("CA", "California"));
}
}
}
using System;
using System.ComponentModel;
namespace StumblingThroughWPFPartI
{
public class State
{
String _abbreviation;
String _name;
public State(String abbreviation, String name)
{
_abbreviation = abbreviation;
_name = name;
}
public String Abbreviation
{
get
{
return _abbreviation;
}
set
{
_abbreviation = value;
}
}
public String Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
}
}
For the front-end, we will add a combobox named 'cboState' and a textbox named 'txtCity', positioning both of them next to our lbAddresses listbox. In fact, why don't we make things easier on ourselves and put them into a stackpanel, setting the stackpanel's DataContext to '{Binding Addresses}'. This will make binding our text box and combo box a bit easier, by putting them into the selected person's 'address' context. Simply bind the textbox to the 'City' property using the binding tag we used previously, the data context tells it that it is the address' property that is being bound. The combo box binding, though, is a bit trickier. I want it to display all state names, but have the values tied to address by state abbreviation. Before we get into that mess, we have to actually define and load our state collection instance. We'll do this just like we did our person collection, by including it in the dictionary like this:
<StumblingThroughWPFPartI:StateCollection x:Key="_allStates"/>
Then populating all states in the 'Load' event, found in the code behind:
((StateCollection)this.Resources["_allStates"]).LoadAll();
Now that we have that resource defined, we can get started on defining our combo box tag. First things first, we'll stick with what is familiar and define its 'ItemsSource', just like we did for the listbox (only specifying _allStates as the source):
ItemsSource="{StaticResource _allStates}"
Next, we tell it that we want it to display the state's name, again using a similar construct to the listbox:
DisplayMemberPath="Name"
Now things get interesting. I want the value behind each item in the list to be the state's abbreviation. To configure this, set the 'SelectedValuePath' property to the property of the bound object that represents the value, in this case, Abbreviation:
SelectedValuePath="Abbreviation"
Next, it is time to bind the selected value to the state of the address selected in the listbox. This can be achieved via the 'SelectedValue' property, by binding it to the 'State' property of our Addresses datacontext. Here is the logic to achieve this:
SelectedValue="{Binding State}"
Running the application and selecting a person shows us exactly what we had hoped:
We can also see that the 'State' dropdown is populated with all the states we defined in our State Collection:
We can prove the bindings are working by selecting the second guy, which we bound to San Francisco:
I think that concludes everything I want to do with databinding at this time. I am very happy with how it works in WPF, datacontext is incredibly useful as we saw with wrapping our address fields in a stackpanel with context. It is good to know that with all the 'look & feel' benefits of WPF, we aren't lacking in some of the nuts & bolts that real-world applications will still required. Here is the entirety of the window XAML as it stands now:
<Window x:Class="StumblingThroughWPFPartI.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:StumblingThroughWPFPartI="clr-namespace:StumblingThroughWPFPartI"
Title="Window1" Height="300" Width="559" Loaded="Window_Loaded">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/VisualFoundation;component/HotTrackListBox.xaml"/>
</ResourceDictionary.MergedDictionaries>
<StumblingThroughWPFPartI:PersonCollection x:Key="_personCollection"/>
<StumblingThroughWPFPartI:StateCollection x:Key="_allStates"/>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
<Window.Background>
<ImageBrush ImageSource="Greenstone.bmp" />
</Window.Background>
<Grid DataContext="{Binding Source={StaticResource _personCollection}}">
<ListBox Margin="22,73,0,89" Name="listBox1" FontSize="16" Style="{StaticResource HotTrackListBoxStyle}" DisplayMemberPath="FullName" ItemsSource="{Binding}" HorizontalAlignment="Left" Width="126" />
<TextBox Height="21" Margin="160,73,0,0" Name="txtFirstName" VerticalAlignment="Top" HorizontalAlignment="Left" Width="101">
<Binding Path="FirstName" ValidatesOnDataErrors="True"/>
</TextBox>
<TextBox Height="21" Margin="160,104,0,0" Name="txtLastName" VerticalAlignment="Top" HorizontalAlignment="Left" Width="101">
<Binding Path="LastName" ValidatesOnDataErrors="True"/>
</TextBox>
<ListBox HorizontalAlignment="Right" Margin="0,73,143,89" Name="lbPersonAddresses" Width="120" FontSize="16" Style="{StaticResource HotTrackListBoxStyle}" DisplayMemberPath="City" ItemsSource="{Binding Addresses}"/>
<StackPanel DataContext="{Binding Addresses}" Margin="0,73,0,110">
<TextBox Height="21" HorizontalAlignment="Right" Name="txtCity" VerticalAlignment="Top" Width="120" >
<Binding Path="City" ValidatesOnDataErrors="True"/>
</TextBox>
<ComboBox Height="21" HorizontalAlignment="Right" Name="cboState" VerticalAlignment="Top" Width="120" DisplayMemberPath="Name" SelectedValuePath="Abbreviation" SelectedValue="{Binding State}" ItemsSource="{StaticResource _allStates}" />
</StackPanel>
</Grid>
</Window>