2
votes

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?

2

2 Answers

4
votes

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}}">
0
votes

Sorry guys I am a little confused about what you are trying to do here... Anyway, from the title it sounds as if you want to a property in your value converter to a property in your value converter. Firstly have a look at an article I have written explaining exactly how you can do that: http://nick-howard.blogspot.com/2011/06/silverlight-4-value-converter.html

So what you would do is create an ObvervableCollection dependency property in your LoadChildrenValueConverter, for arguments sake let’s call it Children.

Then in your xaml you can change the LoadChildrenValueConverter to something like this:

That way you are only calling the web service once from your view model, and you are then sharing that ObvervableCollection in your view model with your value converter.

Hope that helps.