Question

I'm not sure how to describe my scenario in the title, so forgive me for the bad title.

My scenario:

MainView:

 <Grid>
    <TabControl ItemsSource="{Binding Tabs}"
                SelectedIndex="0">
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding ViewName}"/>
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ContentControl x:Name="SamplesContentControl"
                                Content="{Binding View}"
                                VerticalContentAlignment="Stretch"
                                HorizontalContentAlignment="Stretch"/>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Grid>

MainViewModel:

public  class MainViewModel
{
   public  List<Tab> Tabs { get; set; }
    IUnityContainer container;
    public MainViewModel(IUnityContainer container)
    { 
        this.container=container;
        Tabs = new List<Tab>();
        Tabs.Add(new Tab() { ViewName = "Test1", View = this.container.Resolve<TestView>() });
        Tabs.Add(new Tab() { ViewName = "Test2", View = this.container.Resolve<TestView>() });
        Tabs.Add(new Tab() { ViewName = "Test3", View = this.container.Resolve<TestView>() });
    }
}

TestView is a ListView, I want the 3 views have different data. For example, Test1 view has Test1's data and Test2View has Test2's data. But I don't know how to achieve this.

TestViewModel:

 public class TestViewModel
{
    public ObservableCollection<Test> Tests{ get; set; }

    public TestViewModel(ITestDataService testDataService)
    {
        Tests= new ObservableCollection<Test>(testDataService.GetTests());
    }
}

TestView:

  <ListView ItemsSource="{Binding Samples}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Vertical">
                    <TextBlock Text="{Binding Title}" Margin="8"/>
                    <TextBlock Text="{Binding Summary}" Margin="8,0,8,8"/>                       
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

Anyone can help?

Was it helpful?

Solution

You may consider two alternatives for the issue you mentioned:

  • A simple workaround but not completely elegant, would be to rename the TestView and create the 3 different Test views, where each one would know what ViewModel would bind to its DataContext.

  • However, on the other hand you could keep the one only generic TestView and handle each instance's DataContext from the MainViewModel constructor. As the MainViewModel class is adding all the TestView instances into the TabList, that would be the place where DataContext of every TestView instance would be set. The MainViewModel would be responsible for the creation of every TestView and the manager of the corresponding ViewModel's DataContext of the Views.

    Therefore, you could resolve the TestView instance and set its DataContext with the proper ViewModel before the NewTab sentences.

As a personal opinion, the second approach may be cleaner. Speccially if a fourth TestView would be needed and you would not need to create a new View type.

UPDATE:

Regarding the second solution of setting the DataContext in the MainViewModel, the code may look as follows:

public  class MainViewModel
{
   public  List<Tab> Tabs { get; set; }
   IUnityContainer container;
   public MainViewModel(IUnityContainer container)
   { 
       this.container = container;
    
       TestView view1 = this.container.Resolve<TestView>();
       view1.DataContext = this.container.Resolve<Test1ViewModel>(); 
       TestView view2 = this.container.Resolve<TestView>();
       view2.DataContext = this.container.Resolve<Test2ViewModel>();
       TestView view3 = this.container.Resolve<TestView>();
       view3.DataContext = this.container.Resolve<Test3ViewModel>();

       Tabs = new List<Tab>();
       Tabs.Add(new Tab() { ViewName = "Test1", View = view1 });
       Tabs.Add(new Tab() { ViewName = "Test2", View = view2 });
       Tabs.Add(new Tab() { ViewName = "Test3", View = view3 });
   }
}

As you may see, the concept would be that the MainViewModel creates every tab with each TestView as described in the question, and it would also manage the configuration of their DataContext Property. Taking into account that setting the DataContext would be part of the creation of the View, the MainViewModel would remain responsible for the complete creation of every TestView with its corresponding DataContext.

I would like to clarify that the ViewModel being set on each DataContext would be the corresponding TestViewModel and not the MainViewModel itself. This way, the MainViewModel would be able to resolve every Test instance with the specific settings for each TestView.

Trying to use a generic ViewModel instead, it would also be necessary to configure each instance, which would add more unclean code than just setting the DataContext. Based on my understanding, it would be good to encapsulate each Test behavior on different ViewModels with descriptive names rather than one generic ViewModel.

I hope I have clarified the suggested approach.

Regards.

OTHER TIPS

I am not sure if I do 100% understand your question but I give it a try.

ObservableCollection<Test1Value> data1 = new ObservableCollection<Test1Value>(new Test1Value[]
{
    new Test1Value("Location1", 23.5),
    new Test1Value("Location2", 52.5),
    new Test1Value("Location3", 85.2)

});
ObservableCollection<Test2Value> data2 = new ObservableCollection<Test2Value>(new Test2Value[]
{
    new Machine("Machine1", "OK"),
    new Machine("Machine2", "not OK"),
    new Machine("Machine3", "OK"),
    new Machine("Machine4", "open")
});
CompositeCollection coll = new CompositeCollection();
coll.Add(new CollectionContainer() { Collection = data1 });
coll.Add(new CollectionContainer() { Collection = data2 });
Data = coll;

<ItemsControl ItemsSource="{Binding Data}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.Resources>
        <DataTemplate DataType="{x:Type local:Test1Value}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Name}"/>
                <TextBlock Text=" ("/>
                <TextBlock Text="{Binding MessuredValue}"/>
                <TextBlock Text=")"/>
            </StackPanel>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Test2Value}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Machine}"/>
                <TextBlock Text=" - "/>
                <TextBlock Text="{Binding Status}"/>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.Resources>
</ItemsControl>

You can solve this with 1 viewmodel which holds different collections of different test values. With binding it to a CompositeCollection the ListView or ItemsControl will pick up the right view (data template) for the right class (model).

Find more infos on the CompositeCollection here: http://msdn.microsoft.com/en-us/library/system.windows.data.compositecollection.aspx

Or look at how to bind a How do you bind a CollectionContainer to a collection in a view model? here: How do you bind a CollectionContainer to a collection in a view model?

I think you need to transfer this to prism but the concept should work the same way... =)...

HTH

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top