5

I have the following sample data, which works out nicely...

<SampleData:DashboardViewModel xmlns:SampleData="clr-namespace:MyApp.ViewModels">
    <SampleData:DashboardViewModel.Employees>
        <SampleData:EmployeeViewModel FirstName="Aaron" "Adams" />
        <SampleData:EmployeeViewModel FirstName="Billy" "Bob" />
        <SampleData:EmployeeViewModel FirstName="Charlie" "Chaplin" />
    </SampleData:DashboardViewModel.Employees>
</SampleData:DashboardViewModel>

However, I find that it would be useful to be able to reuse that list of sample employees instead of retyping it every time. I can not figure out how to reuse that list. Basically, I want to have another SampleData file (SampleEmployees.xaml) which contains that list of employees, then be able to include that in my other samples...

<SampleData:DashboardViewModel xmlns:SampleData="clr-namespace:MyApp.ViewModels">
    <SampleData:DashboardViewModel.Employees ... /> <!-- What goes in there? -->
</SampleData:DashboardViewModel>

<SampleData:OtherViewModel xmlns:SampleData="clr-namespace:MyApp.ViewModels">
    <SampleData:OtherViewModel.Employees ... /> <!-- What goes in there? -->
</SampleData:OtherViewModel>

Also, how to create the list separately in another XAML file??

ViewModel:

public class DashboardViewModel : NotificationObject
{
    public class DashboardViewModel(IDataService dataService)
    {
        InternalEmployees = new ObservableCollection<EmployeeViewModel>(dataService.GetEmployees());
        Employees = new ReadOnlyObservableCollection<EmployeeViewModel>(InternalEmployees);
    }

    private ObservableCollection<EmployeeViewModel> InternalEmployees { get; set; }
    public ReadOnlyObservableCollection<EmployeeViewModel> Employees { get; private set; }
}
myermian
  • 31,823
  • 24
  • 123
  • 215
  • I don't think it is possible with the default system. I think one would have to create a [CustomTool](http://www.google.com/search?q=visual+studio+custom+tool) to parse a source file which would then generate another design data file. This would prevent having to retype, but the resultant generated file would still contain the full data (not a "reference" to other data). – Jake Berger Nov 22 '11 at 18:45
  • So basically I need to turn this into a suggestion in Microsoft Connect? – myermian Nov 22 '11 at 19:21
  • go for it. note that VS2011 is in dev preview, and Blend 5 is in a similar stage, so if they don't support it yet I don't foresee them implementing this feature... – Jake Berger Nov 22 '11 at 19:49
  • If the `Employees` property has a setter and is of type `IList` this is easy, sadly about everything else not so much... – H.B. Nov 22 '11 at 22:29
  • @H.B.: Well, my Employees is a type of `ObservableCollection` ... and `ObservableCollection` implements `IList` and `IList`. – myermian Nov 22 '11 at 23:13
  • @m-y: That'd be difficult since it is generic. Does it have a setter as well? – H.B. Nov 22 '11 at 23:14
  • Well, lemme update my question with my viewmodel... – myermian Nov 22 '11 at 23:18
  • @m-y: The problem with that is that you would have to create a generic collection in XAML which is not exactly possible currently. (I do not get notified without the `@H.B.`) – H.B. Nov 22 '11 at 23:29

1 Answers1

0

In some cases this is easy, those require:

  1. The collection property is of type IEnumerable or IList (not a class implementing it)
  2. The property has a setter.

e.g. public IEnumerable Employees { get; set; }

First you extract the items into a resource dictionary, e.g.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:obj="clr-namespace:Test.Objects">
    <x:Array x:Key="SampleEmps" Type="obj:Employee">
        <obj:Employee Name="Skeet" Occupation="Programmer" />
        <obj:Employee Name="Skeet" Occupation="Programmer" />
        <obj:Employee Name="Dimitrov" Occupation="Programmer" />
    </x:Array>
</ResourceDictionary>

You then add that to the MergedDictionary of a control which will contain the ViewModels. e.g.

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Objects/SampleData.xaml" />
        </ResourceDictionary.MergedDictionaries>
        <!-- ... -->

Then you can then reference the collection using StaticResource:

<obj:SomeOtherViewModel Employees="{StaticResource SampleEmps}"/>

Now the problem with specialized collections is that you cannot simply create them in XAML. And the problem with a missing setter is that you cannot assign the property using a StaticResource, so i think a setter is always necessary.

If you have a specialized collection you can use a MarkupExtension to create an instance.

[ContentProperty("Items")]
public class GenericCollectionFactoryExtension : MarkupExtension
{
    public Type Type { get; set; }
    public Type T { get; set; }
    public IEnumerable Items { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var genericType = Type.MakeGenericType(T);
        var list = Activator.CreateInstance(genericType) as IList;
        if (list == null) throw new Exception("Instance type does not implement IList");
        foreach (var item in Items)
        {
            list.Add(item);
        }
        return list;
    }
}

You can either directly create for example an ObservableCollection in the resources or you pipe the array through this extension in the place where you need the items:

xmlns:om="clr-namespace:System.Collections.ObjectModel;assembly=System"
<obj:SomeViewModel x:Key="SomeVM">
    <obj:SomeViewModel.Employees>
        <me:GenericCollectionFactory Type="{x:Type om:ObservableCollection`1}"
                                     T="{x:Type obj:Employee}">
            <StaticResource ResourceKey="SampleEmps" />
        </me:GenericCollectionFactory>
    </obj:SomeViewModel.Employees>
</obj:SomeViewModel>

The `1 at the end of om:ObservableCollection is necessary as the type is generic.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • I'll have to try this when I get the chance... Though, it does have a bit of a smell to it. I don't like the idea of having to have my sample data built into the window resources. – myermian Nov 23 '11 at 03:39
  • @m-y: You could extend the markup extension above or create another one which gets the resources on the fly without a hard reference to the resource dictionary in the other code. – H.B. Nov 23 '11 at 08:06