0
votes

PROBLEM: Use one single viewModel with two different views.

I have a Window with a control ContentControl which is binded to a property in the DataContext, called Object MainContent {get;set;}. Base on a navigationType enum property, I assign other ViewModels to it to show the correct UserControl.

I need to merge two views into one ViewModel, and because I'm assigning a ViewModel to the ContentControl mentioned before, the TemplateSelector is not able to identify which is the correct view as both shares the same viewModel

If I assign the view instead the ViewModel to the ContentControl, the correct view is shown, however, non of the commands works.

Any Help? Thanks in advance.




SOLUTION: based on @mm8 answer and https://stackoverflow.com/a/5310213/2315752:

ManagePatientViewModel.cs

public class ManagePatientViewModel : ViewModelBase
{
    public ManagePatientViewModel (MainWindowViewModel inMainVM) : base(inMainVM) {}
} 

ViewHelper.cs

public enum ViewState
{
    SEARCH,
    CREATE,
}

MainWindowViewModel.cs

public ViewState State {get;set;}
public ManagePatientViewModel VM {get;set;}

private void ChangeView(ViewState inState)
{
    State = inState;

    // This is need to force the update of Content.
    var copy = VM;
    MainContent = null;
    MainContent = copy;
}

public void NavigateTo (NavigationType inNavigation)
{
    switch (inNavigationType)
    {
        case NavigationType.CREATE_PATIENT:
            ChangeView(ViewState.CREATE);
            break;
        case NavigationType.SEARCH_PATIENT:
            ChangeView(ViewState.SEARCH);
            break;
        default:
            throw new ArgumentOutOfRangeException(nameof(inNavigationType), inNavigationType, null);
    }
}

MainWindow.xaml

<DataTemplate x:Key="CreateTemplate">
        <views:CreateView />
</DataTemplate>

<DataTemplate x:Key="SearchTemplate">
        <views:SearchView/>
</DataTemplate>

<TemplateSelector x:Key="ViewSelector"
    SearchViewTemplate="{StaticResource SearchTemplate}"
    CreateViewTemplate="{StaticResource CreateTemplate}"/>

<ContentControl
        Grid.Row="1"
        Content="{Binding MainContent}"
        ContentTemplateSelector="{StaticResource ViewSelector}" />

TemplateSelector.cs

public class TemplateSelector : DataTemplateSelector
{
    public DataTemplate SearchViewTemplate {get;set;}
    public DataTemplate CreateViewTemplate {get;set;}
}

public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
    if (!(item is SelectLesionViewModel vm))
    {
        return null;
    }

    switch (vm.ViewType)
    {
        case ViewState.CREATE:
            return CreateViewTemplate;
        case ViewState.SEARCH:
            return SearchViewTemplate;
        default:
            return null;
        }
    }
}
2
How is TemplateSelector supposed to know which template to use when there are two view model types mapped to a single view type? This makes no sense at all. You should use two different types. - mm8
Thanks for your answer @mm8, that is the reason I am asking, I do not know how to proceed with this, or what should I change.Could you point me to the right direction? - Nekeniehl
I posted an answer. - mm8

2 Answers

2
votes

How is the TemplateSelector supposed to know which template to use when there are two view types mapped to a single view model type? This makes no sense I am afraid.

You should use two different types. You could implement the logic in a common base class and then define two marker types that simply derive from this implementation and add no functionality:

public class ManagePatientViewModel { */put all your code in this one*/ }

//marker types:

public class SearchPatientViewModel { }

public class CreatePatientViewModel { }

Also, you don't really need a template selector if you remove the x:Key attributes from the templates:

<DataTemplate DataType="{x:Type viewModels:SearchPatientViewModel}">
     <views:SearchPatientView />
</DataTemplate>

<DataTemplate DataType="{x:Type viewModels:CreatePatientViewModel}">
    <views:CreatePatientView />
</DataTemplate>

...
<ContentControl
    Grid.Row="1"
    Content="{Binding MainContent}" />
2
votes

Maybe the requirement is to switch out the views and retain the one viewmodel. Datatemplating is just one way to instantiate a view. You could instead set the datacontext of the contentcontrol to the instance of your viewmodel and switch out views as the content. Since views are rather a view responsibility such tasks could be done completely in the view without "breaking" mvvm. Here's a very quick and dirty approach illustrating what I mean. I build two usercontrols, UC1 and UC2. These correspond to your various patient views. Here's the markup for one:

<StackPanel>
    <TextBlock Text="User Control ONE"/>
    <TextBlock Text="{Binding HelloString}"/>
</StackPanel>

I create a trivial viewmodel.

public class OneViewModel
{
    public string HelloString { get; set; } = "Hello from OneViewModel";
}

My mainwindow markup:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <StackPanel>
        <Button Content="UC1" Click="UC1_Click"/>
        <Button Content="UC2" Click="UC2_Click"/>
    </StackPanel>
    <ContentControl Name="parent"
                    Grid.Column="1"
                    >
        <ContentControl.DataContext>
            <local:OneViewModel/>
        </ContentControl.DataContext>
    </ContentControl>
</Grid>

The click events switch out the content: private void UC1_Click(object sender, RoutedEventArgs e) { parent.Content = new UC1(); }

    private void UC2_Click(object sender, RoutedEventArgs e)
    {
        parent.Content = new UC2();
    }

The single instance of oneviewmodel is retained and the view shown switches out. The hellostring binds and shows ok in both.

In your app you will want a more sophisticated approach to setting that datacontext but this sample is intended purely as a proof of concept to show you another approach.

Here's the working sample: https://1drv.ms/u/s!AmPvL3r385QhgpgMZ4KgfMWUnxkRzA