views:

4074

answers:

1

Let's pretend I have the following xaml...

<UserControl.Resources>
    <local:ViewModel x:Name="viewModel" />
    <local:LoadChildrenValueConverter x:Name="valueConverter" />
</UserControl.Resources>

<UserControl.DataContext>
    <Binding Source="{StaticResource viewModel}" />
</UserControl.DataContext>

<Grid x:Name="LayoutRoot" Background="White">
    <control:TreeView ItemsSource="{Binding Root}">
        <control:TreeView.ItemTemplate>
            <control:HierarchicalDataTemplate ItemsSource="{Binding Converter={StaticResource valueConverter}}">
                <TextBlock Text="{Binding}" />
            </control:HierarchicalDataTemplate>
        </control:TreeView.ItemTemplate>
    </control:TreeView>
</Grid>

...and the following code to go with it...

using System;
using System.Collections.ObjectModel;
using System.Windows.Data;

namespace SilverlightViewModelSpike
{
    public class ViewModel
    {
        public ViewModel()
        {
            Root = new ObservableCollection() { "Item 1", "Item 2", "Item 3", };
        }

        public ObservableCollection Root { get; private set; }        
    }

    public class LoadChildrenValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return new ObservableCollection() { "Item 1", "Item 2", "Item 3", };
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

This works as expected, but it feels wrong that I have two separate classes that are required in order to grab the needed data for my view (imagine that ViewModel and LoadChildrenValueConverter pulled data from a web service instead of returning hard coded data). Is there a better solution here? I was thinking maybe something like this...

using System;
using System.Collections.ObjectModel;
using System.Windows.Data;

namespace SilverlightViewModelSpike
{
    public class ViewModel
    {
        public ViewModel()
        {
            Root = new ObservableCollection() { "Item 1", "Item 2", "Item 3", };
            ValueConverter = new LoadChildrenValueConverter();
        }

        public ObservableCollection Root { get; private set; }
        public LoadChildrenValueConverter ValueConverter { get; private set; }
    }

    public class LoadChildrenValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return new ObservableCollection() { "Item 1", "Item 2", "Item 3", };
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

... but then i can't get this line to work...

<control:HierarchicalDataTemplate ItemsSource="{???}">

...and even that doesn't seem like a great solution. Does anyone have a nice clean solution for this?

+4  A: 

Since you're using a ViewModel to sit between your actual model and your view, I wonder if it's easier just to implement the IValueConverter logic directly in there. Sort of like:

public class ViewModel
{
    public ObservableCollection Root { get; set: }

    public ObservableCollection Children
    {
        get { /* return children items */ }
    }
}

Then you can simply bind directly to your second property:

<control:HierarchicalDataTemplate ItemsSource="{Binding Children}">

I think the main purpose of a ViewModel object is to limit the number of "tricks" (such as IValueConverters) you need to pull to get the data you need from the original model. Since you have one, you might as well make use of it.

Edit 1

... and of course now that I re-read your post I see that there's more to it. You're getting the children for each item in your "Root" collection.

How about implementing the IValueConverter as a static instance in your ViewModel itself?

public class ViewModel : IValueConverter
{ 
    public static readonly IValueConverter ChildrenConverter
        = new LoadChildrenValueConverter();
}

Now you should be able to say:

<control:HierarchicalDataTemplate 
    ItemsSource="{Binding Converter={x:Static local:ViewModel.ChildrenConverter}}">

Edit 2

Ok, you're using Silverlight so {x:Static} isn't available to you.

The only other option I can think of that will let you reuse the one static resource instead of having to declare two is to implement the IValueConverter directly in your ViewModel. This is no good if you will need to do more than one type of conversion, but if your ViewModel is very narrow-focused then it could do the job. So:

public class ViewModel : IValueConverter
{
    // move your Convert and ConvertBack methods into here
}

Now you can do this:

<control:HierarchicalDataTemplate
    ItemsSource="{Binding Converter={StaticResource ViewModel}}">
Matt Hamilton
That's kind of what I'm going for. However, I don't think Silverlight allows for x:Static in bindings. Is that not true?
herbrandson
Ah you're right. I hadn't noticed that you had tagged your question as Silverlight.I'll append one more option to the answer just in case it's viable for you.
Matt Hamilton
Thanks for the help. The idea you present in Edit2 is actually where I started. I was hoping there was a cleaner solution, but I doesn't seem like it at this point. It does help some to see someone else come to the same conclusion. At least I don't feel like it's a dumb solution :)
herbrandson