0

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;
        }
    }
}
Nekeniehl
  • 1,633
  • 18
  • 35
  • 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 Mar 22 '18 at 12:37
  • 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 Mar 22 '18 at 12:40
  • I posted an answer. – mm8 Mar 22 '18 at 12:41

2 Answers2

3

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}" />
mm8
  • 163,881
  • 10
  • 57
  • 88
  • 1
    I might explain it wrong, what I need is two different views with one single viewModel, not two viewModels into one single view. – Nekeniehl Mar 22 '18 at 12:49
  • I have tested it and I have the same problem, commands are not working. The thing is that I already have two different viewModels, that is why I didn't got your point at the beginning. One is to search, the other one, to create. I have merged them into `ManagePatientViewModel` and I should use it for both views. Basically I thought that `TemplateSelector` could select which is the correct view at a time. – Nekeniehl Mar 22 '18 at 13:07
  • Basically I had to simplify all the views and viewsModels we have, and it makes sense to have two views (CreateView and SearchView) with a single viewModel (ManagePatientViewModel). I would like to post the code for each view, but is extense and Stack complains about "to much code". I thought that having two views with the same ViewModel would be easy, I will forget this. – Nekeniehl Mar 22 '18 at 13:19
  • I have updated my question, I have also applied your solution but it is not working, the views changes to the correct one, but the commands binded in the viewModel are not being invoke. It might be easy for an expert, but Im just a newbie on WPF. – Nekeniehl Mar 22 '18 at 13:35
  • The problem is that the TemplateSelector.SelectTemplate.Item is what I assign in the MainContent, so I cannot assign the same viewModel because then, the template selector will not know which one is the correct. – Nekeniehl Mar 22 '18 at 13:36
  • Okey, I found the problem and I fixed it thanks to your approach. I will update my answer. – Nekeniehl Mar 22 '18 at 13:57
  • Sorry typo, your answer and my question. I am formating it to close it. – Nekeniehl Mar 22 '18 at 14:01
2

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

Andy
  • 11,864
  • 2
  • 17
  • 20
  • Thanks for your answer, This is the first approach I tried, but I assign a ViewModel to `ContentControl.Content` instead of a view. If I assing the view non of the commands or binded properties works. – Nekeniehl Mar 22 '18 at 13:57
  • That code is from a working sample, I've uploaded it to my onedrive and you can take a look. The binding works. – Andy Mar 23 '18 at 10:44