Disclaimer: I think my next solution is better. Give it a try. Click here to check it out.
I love the DataForm for Silverlight 3. It is truly awesome. My biggest complaint is that customizing the fields has changed significantly over time. For instance it now longer supports Fields, which I thought worked well.
With that said, the DataForm for Silverlight 3 has the ability to automatically generate the UI based on the objects which you pass it. The collection you pass it can even have different types of objects. This functionality is very useful for doing things that are textfields, calendars, numbers, etc.
The other option is to use templates if you can’t leave it up to the DataForm to decide what to display based on what you pass it. The problem is your UI is now tied on a field by field basis to what object you pass it. While that is not usually an issue, it is a bummer to lose that cool functionality.
The problem comes when you want to add a combo box to the DataForm. All of a sudden it seems that support is thrown out the window. I thought ASP.NET Dynamic Data did a pretty good job of handling DropDownLists, but I am disappointed with how the DataForm for Silverlight handles it. It seems that I have to work too hard just to implement a ComboBox.
The good news is that I did manage to figure out how to use a ComboBox with not much code at all; at least once you have created an edit template for your DataForm. Another bit of good news is that the DataForm makes it very easy to create an edit template. The sample below shows what an edit template looks like. One interesting thing is that since I did not specify any other templates, they are automatically generated it appears. Very nice of the DataForm. :)
Here is the XAML that defines a DataForm that has one hidden ID field, 3 textfields, and one drop down to select the Owner from a list of People.
<dataFormToolkit:DataForm x:Name="dfCar" AutoGenerateFields="True" >
<dataFormToolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataFormToolkit:DataField Visibility="Collapsed">
<TextBox Text="{Binding CarID, Mode=TwoWay}" />
</dataFormToolkit:DataField>
<dataFormToolkit:DataField>
<TextBox Text="{Binding Make, Mode=TwoWay}" />
</dataFormToolkit:DataField>
<dataFormToolkit:DataField>
<TextBox Text="{Binding Model, Mode=TwoWay}" />
</dataFormToolkit:DataField>
<dataFormToolkit:DataField>
<TextBox Text="{Binding Color, Mode=TwoWay}" />
</dataFormToolkit:DataField>
<dataFormToolkit:DataField>
<ComboBox x:Name="cboOwner" DisplayMemberPath="Name" DataContext="{Binding OwnerID}"/>
</dataFormToolkit:DataField>
</StackPanel>
</DataTemplate>
</dataFormToolkit:DataForm.EditTemplate>
</dataFormToolkit:DataForm>
In the constructor of user control that contains this DataForm we need to register for the ContentLoaded event of the DataForm. This will be called after the Template is loaded and thus the ComboBox (cboOwner) has been created. Be sure NOT to register for the Loaded event by mistake. This is too early in the lifecycle of the DataForm and the ComboBox will not have been created by then. You must wait for the ContentLoaded event to fire before looking for the ComboBox.
void dfCar_ContentLoaded(object sender, DataFormContentLoadEventArgs e)
{
// populate the ComboBox
ComboBox cbo = dfCar.FindNameInContent("cboOwner") as ComboBox;
cbo.ItemsSource = GetOwnersList();
// Select the appropriate item in the ComboBox based on the ID
int? personID = (dfCar.CurrentItem as Car).OwnerID;
if (personID.HasValue)
{
Person foundPerson = (cbo.ItemsSource as ObservableCollection<Person>).First(p => p.ID == personID.Value);
cbo.SelectedItem = foundPerson;
}
}
// some supporting stuff just so you can see what they look like.
// Your objects and methods will likely be more complex, and pull data from a web services,
// RIA Services, WCF, etc.
public class Person
{
public string Name { get; set; }
public int ID { get; set; }
}
public List<Person> GetOwnersList()
{
List<Person> people = new List<Person>();
people.Add(new Person { Name = "Brent", ID = 0 });
people.Add(new Person { Name = "Amanda", ID = 1 });
people.Add(new Person { Name = "Lance", ID = 2 });
return people;}
As you can see, most of this isn’t extra code so much as it is just stuff that would need regardless. The ContentLoaded event handler is really all that we had to add that was extra and that code is pretty straight forward.
I really hope I am missing something. It seems to me that I should not have to do this, and I also don’t think I should have to search the ComboBox by ID. It seems that the ASP.NET model (DataMember property to specify what property in the object should be used as a “value” matching property) would be nice here.
If anyone knows of a better way I am very interested to hear about it.
I got the idea from here.