I'm developing a business application, using Silverlight for the UI and a WCF webservice for the back-end. In the database I have a number of lookup tables. When the WCF service returns a business object, one of the properties contains the entire row out of the lookup table instead of just the foreign key, so in the UI I can display things like the description from the lookup table without making another call to the service. What I am trying to do at the moment is provide a combobox bound to the entire list of lookup values and have it update properly. The business object I'm dealing with in this example is called Session and the lookup is called SessionType.
Below is the definition of the combobox. The DataContext is set to an instance of Session. I am setting an ItemTemplate because the combobox is displaying more than just a list of strings.
<ComboBox
x:Name="SessionTypesComboBox"
ItemTemplate="{StaticResource SessionTypeDataTemplate}"
ItemsSource="{Binding Source={StaticResource AllSessionTypes}}"
SelectedItem="{Binding Path=SessionType, Mode=TwoWay}"
/>
Both the business object and the lookup table are being loaded asynchronously via the web service. If I do nothing else, the combobox list will be populated with SessionTypes, but it will not show the initial SessionType value from Session. However Session will be updated with the correct SessionType if the combobox selection is changed.
What seems to be happening is that the SelectedItem binding cant match the SessionType in Session to its equivalent in the SessionType list. The object values are the same but the references are not.
The workaround I have found is to load the Session and the SessionTypes list, then update the current SessionType of Session with the corresponding one from the SesstionTypes list. If I do that then the combobox displays correctly. However to me this has a bad code smell. Because everything is loaded asyncronously, I have to determine when everything is available. Here's how I'm doing that:
In the code-behind of my Silverlight user control:
// incremented every time we get data back during initial form load.
private volatile int m_LoadSequence = 0;
...
// Loaded event, called when the form is er... loaded.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// load session types
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
if (sessionTypes != null)
{
sessionTypes.DataLoadCompleted += (s, ea) =>
{
IncrementLoadSequence();
};
sessionTypes.LoadAsync();
}
// start loading another lookup table, same as above
// omitted for clarity
// set our DataContect to our business object (passed in when form was created)
this.LayoutRoot.DataContext = this.m_Session;
IncrementLoadSequence();
}
// This is the smelly part. This gets called by OnBlahCompleted events as web service calls return.
private void IncrementLoadSequence()
{
// check to see if we're expecting any more service calls to complete.
if (++m_LoadSequence < 3)
return;
// set lookup values on m_Session to the correct one in SessionType list.
// Get SessionType list from page resources
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
// Find the matching SessionType based on ID
this.m_Session.SessionType = sessionTypes.Where((st) => { return st.SessionTypeID == this.m_Session.SessionType.SessionTypeID; }).First();
// (other lookup table omitted for clarity)
}
So basically I have a counter that gets incremented each time I get data back from the webservice. Since I'm expecting 3 things (core business object + 2 lookup tables), when that counter gets to 3 I match up the references.
To me, this seems very hacky. I would rather see the combobox specify a ValueMemberPath and SelectedValue to match the selected item with one in the list.
Can anyone see a cleaner way of doing this? This situation is very common in business apps, so I'm sure there must be a nice way of doing it.