views:

951

answers:

3

I have a view model with a property Fields which is an ObservableCollection<FieldVM>. In the view that uses this property, I have an ItemsControl like so:

...
<ItemsControl ItemsSource="{Binding Fields}" />
...

FieldVM is an abstract class, implemented by such classes as TextFieldVM and EnumFieldVM. During run time, these FieldVM-implementations get added to the Fields property and I want them to show up in my view with their associated views.

In WPF, doing this is simple, I do it all the time. You just do this in an appropriate resource dictionary, and everything works as expected:

<DataTemplate DataType="{x:Type vm:TextFieldVM}">
    <v:TextFieldView />
</DataTemplate>

<DataTemplate DataType="{x:Type vm:EnumFieldVM}">
    <v:EnumFieldView />
</DataTemplate>

Now, working in Silverlight for the first time, I expected I could just do the same thing, but the DataTemplate doesn't have a DataType property. I'm stumped. What's the Silverlight-way of doing this?

+1  A: 

Use a Value Converter to bind the type to the visibility of each view:

<DataTemplate> 
    <Grid>
        <v:EnumFieldView 
            Visibilty="{Binding Converter={StaticResource ViewVisibilityConverter}, ConverterParameter=Enum}" /> 
        <v:TextFieldView 
            Visibilty="{Binding Converter={StaticResource ViewVisibilityConverter}, ConverterParameter=Text}" />
    </Grid
</DataTemplate> 

And in the ConvertTo of the ViewVisibilityConverter, switch the visibility based on the type.

Another way to look at it would be to use a different type of value converter to return a different data template from the Application.Resources.

<ListBox ItemTemplate="{Binding Converter={StaticResource ItemTemplateFactory}"/>

in ItemTemplateFactory.Convert():

var fieldVM = value as FieldVM;

switch fieldVM.FieldType:
{
    case "Text":
         return Application.Current.Resources["TextTemplate"] as DataTemplate;

    case "Enum":
         return Application.Current.Resources["EnumTemplate"] as DataTemplate;

}
Michael S. Scherotter
Thanks for your suggestion. It works, and I'm marking it as the answer, but it's not really an ideal solution... it's more of an ugly hack. I can't help but feel like there has to be a more proper, WPF-like way of doing this.
Alex
I added an additional option that might be a bit prettier:
Michael S. Scherotter
A: 

Silverlight doesn't have a DataTemplateSelector but I used this snippet...

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <client:TemplateSelector Content="{Binding}"/>
    </DataTemplate>
</ItemsControl.ItemTemplate>

Here's the template selector:

public class TemplateSelector : ContentControl
{
    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);

        MyObject f = newContent as MyObject;

        switch (f.MyProperty)
        {
            case "Bool":
            case "String":
            case "Array":
            default:
                ContentTemplate = Application.Current.Resources["MultiSelectDataTemplate"] as DataTemplate;
                break;
        }
    }
}
infamouse
DataTemplateSelector is not available in Silverlight either...But I've seen a few tries to implement this class in Silverlight.Here's one:http://skysigal.xact-solutions.com/Blog/tabid/427/EntryId/1000/Silverlight-a-port-of-the-DataTemplateSelector.aspx
Yuval Peled
@infamouse: DataTemplateSelector is not part of Silverlight 4
Jehof
You're right. I used another snippet as a replacement. Editing.
infamouse
+1  A: 

A variant on the second option from Michael (since I could not get it working directly).

In resources define an additional datatemplate:

<DataTemplate x:Key="DynamicTemplate">
  <ContentPresenter ContentTemplate="{Binding Converter={StaticResource TemplateChooser}}" Content="{Binding}"/>
</DataTemplate>

And then use this data template in the listbox

<ListBox ItemTemplate="{StaticResource DynamicTemplate}">

TemplateChooser must be an IValueConvertor, where the Convert function takes an object and returns the DataTemplate that should be used for that object.

Johan