John Ervin
John Ervin

Reputation: 1

How to cache UserControls when switching between them in WPF MVVM ContentControl?

WPF / MVVM / ContentControl

Implementing an app that uses buttons to switch between UserControls: App Diagram

The approach assigns UserControl DataTemplates to the different view models and switching view models with the buttons. Problem is each UserControl is reinstantiated, which causes problems with a legacy Windows Forms control that's integrated. Is there a way to implement this approach that caches the UserControls? I suppose I could just load everything and change visibility as a fall back, but wondering if there was something I'm missing.

MainWindow.xaml

<Window x:Class="ContentControl.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:ContentControl.ViewModel"
        xmlns:local="clr-namespace:ContentControl.View"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <vm:MainViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate DataType="{x:Type vm:UC1ViewModel}">
            <local:UC1UserControl/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:UC2ViewModel}">
            <local:UC2UserControl/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="0">
            <Button Command="{Binding UpdateContentCommand}" CommandParameter="uc1">UC 1</Button>
            <Button Command="{Binding UpdateContentCommand}" CommandParameter="uc2">UC 2</Button>
        </StackPanel>
        <ContentControl Grid.Column="1" Content="{Binding SelectedViewModel}"/>
    </Grid>
</Window>

UpdateContentCommand.cs

internal class UpdateContentCommand : ICommand
{
   public event EventHandler? CanExecuteChanged;
   private MainViewModel vm;

   public bool CanExecute(object? parameter)
   {
       return true;
   }

   public void Execute(object? parameter)
   {
       if (parameter is string p)
       {
           if (p.ToLower() == "uc1")
           {
                vm.SelectedViewModel = vm.UC1VM;
           }
           else if (p.ToLower() == "uc2")
           {
               vm.SelectedViewModel = vm.UC2VM;
           }
       }
}

   public UpdateContentCommand(MainViewModel vm)
   {
       this.vm = vm;
   }
}

Upvotes: 0

Views: 166

Answers (1)

user21970328
user21970328

Reputation:

You can define the control instance you wish to reuse as a resource. This way the XAML engine will only create a single shared instance. Only keep in mind that this instance can only be added to the visual tree in a single location. this means, you can't use this particular instance in the DataTemplate and make it the child of e.g. a StackPanel at the same time. This also wouldn't work if multiple instances of the DataTemplate are required to instantiated (for example for items in a ListBox).

<Window.Resources>
  <local:UC1UserControl x:Key="SharedUC1UserControl" />
  <local:UC1UserControl x:Key="SharedUC2UserControl" />

  <DataTemplate DataType="{x:Type vm:UC1ViewModel}">
    <ContentControl Content={StaticResource SharedUC1UserControl}" />
  </DataTemplate>
  <DataTemplate DataType="{x:Type vm:UC2ViewModel}">
    <ContentControl Content={StaticResource SharedUC2UserControl}" />
  </DataTemplate>
</Window.Resources>

An alternative solution would be to create a control that manages the content host and generates/recycles the actual data containers (your controls) like the ListBox is doing it. This will give you more flexibility.

Upvotes: 0

Related Questions