Search code examples
c#wpfmvvmviewviewmodel

MVVM: How to avoid ressetting the view content after switching to another view


I am very new to the MVVM concept and try to set up a WPF application which controls laboratory equipment remotely. After setting up the following views and view models:

App.xaml

<Application x:Class="VirtualLab.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:VirtualLab"
             xmlns:viewModel="clr-namespace:VirtualLab.MVVM.ViewModel"
             xmlns:views="clr-namespace:VirtualLab.MVVM.Views"
             StartupUri="MainWindow.xaml">

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Theme/MenuButtonTheme.xaml"/>
                <ResourceDictionary Source="Theme/FuncGenButtonStyles.xaml"/>
                <ResourceDictionary Source="Theme/OsziButtonTheme.xaml"/>
            </ResourceDictionary.MergedDictionaries>

            <DataTemplate DataType="{x:Type viewModel:CircuitViewModel}">
                <views:CircuitView/>
            </DataTemplate>

            <DataTemplate DataType="{x:Type viewModel:OszilloskopViewModel}">
                <views:OszilloskopView/>
            </DataTemplate>

            <DataTemplate DataType="{x:Type viewModel:FunctionGeneratorViewModel}">
                <views:FunctionGeneratorView/>
            </DataTemplate>

        </ResourceDictionary>
    </Application.Resources>
</Application>

MainViewModel.cs

using VirtualLab.Core;
using VirtualLab.MVVM.Model;
using VirtualLab.MVVM.ViewModel;

namespace VirtualLab.MVVM.ViewModel
{
    public class MainViewModel : ObservableObject
    {
        public SafetyCheck safetyCheck = new SafetyCheck();

        // Commands for Relays 
        public RelayCommands CircuitViewCommand { get; set; }
        public RelayCommands OszilloskopViewCommand { get; set; }
        public RelayCommands FunctionGeneratorCommand { get; set; }


        // All View Models
        public CircuitViewModel CircuitVM { get; set; } 
        public OszilloskopViewModel OszilloskopVM { get; set; }
        public FunctionGeneratorViewModel FunctionGeneratorVM { get; set; }

        // Current View
        public object _currentView;

        public object CurrentView
        {
            get { return _currentView; }
            set 
            { 
                _currentView = value;
                OnPropertyChanged();
            }
        }
        CircuitViewModel GetCircuitVM()
        {
            return this.CircuitVM;
        }

        // Constructor MainViewModel
        public MainViewModel()
        {
            CircuitVM = new CircuitViewModel();
            OszilloskopVM = new OszilloskopViewModel(this);
            FunctionGeneratorVM = new FunctionGeneratorViewModel(this); 
            
            CurrentView = CircuitVM;
            
            CircuitViewCommand = new RelayCommands(o =>
            {
                CurrentView = CircuitVM;
            });

            OszilloskopViewCommand = new RelayCommands(o =>
            {
                CurrentView = OszilloskopVM;
            });

            FunctionGeneratorCommand = new RelayCommands(o =>
            {
                CurrentView = FunctionGeneratorVM;
            });
        }
    }
}

MainWindow.xaml.cs

using System;
using System.Windows;
using System.Windows.Input;
using VirtualLab.MVVM.Views;

namespace VirtualLab
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        
        private void minimizeApp(object sender, RoutedEventArgs e)
        {
            try
            {
                this.WindowState = WindowState.Minimized;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void closeApp(object sender, RoutedEventArgs e)
        {
            try
            {
                Close();
            }
            catch (Exception ex)
            { 
                MessageBox.Show(ex.Message);
            }
        }

        private void Border_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if(e.LeftButton == MouseButtonState.Pressed )
            {
                DragMove(); 
            }
        }
    }
}

FunctionGeneratorViewModel.cs

namespace VirtualLab.MVVM.ViewModel
{
    public class FunctionGeneratorViewModel : ObservableObject
    {
        public Functiongenerator funcGen; // the actual model that contains the functions for the functionGenerator
        public MainViewModel MainVM;   
  

        //A lot of RelayCommands to access the functions for funcGen are implemented here......

        public FunctionGeneratorViewModel(MainViewModel MainVM)
        {
            //Objectdefinition
            this.funcGen = new Functiongenerator(MainVM);
    }
}

FunctionGeneratorView.cs

<UserControl x:Class="VirtualLab.MVVM.Views.FunctionGeneratorView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:VirtualLab.MVVM.Views"
             mc:Ignorable="d" 
             d:DesignHeight="790" d:DesignWidth="1400">
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <TextBlock Text="Funktionsgenerator"
                   Foreground="White"
                   FontSize="25"
                   HorizontalAlignment="Left"
                   Margin="20,20,0,20"
                   FontFamily="/Fonts/#Poppins"/>
       
     <!--a lot of  Code for all elements in the view --> 
     
    </Grid>
</UserControl>

I encountered the following problem.... every time I navigate between the views, it seems as if the new page is reset, or to put it in other words, the page change causes a reset of the "old" view page.

How can I avoid this behaviour?

Hopefully the given code sufficient information. Otherwise I can provide further code excerpts.

Thanks in advance!:)


Solution

  • In the simplest case, you need a similar converter:

    using System;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace Core2022.Converters
    {
        public class ObjectToUIElementConverter : IValueConverter
        {
            public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                if (value is null)
                    return DependencyProperty.UnsetValue;
                if (!uiElements.TryGetValue(value, out var element))
                {
                    if (value is UIElement elm)
                    {
                        element = elm;
                    }
                    else
                    {
                        element = new ContentControl()
                        { DataContext = value, Content = value };
                    }
                    uiElements.Add(value, element);
                }
                return element;
            }
    
           private readonly ConditionalWeakTable<object, UIElement> uiElements
               = new ConditionalWeakTable<object, UIElement>();
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
    
            public static ObjectToUIElementConverter Default { get; } = new ObjectToUIElementConverter();
        }
    }
    

    Use it in the CurrentView property binding:

        <ContentControl Content="{Binding CurrentView, Converter={x:Static vms:ObjectToUIElementConverter.Default}}"/>
    

    But I immediately warn you, such a converter is not suitable for all scenarios, it may have memory leaks (when VMs are created dynamically and there are a lot of them), in some cases it is possible to reinitialize remembered UIElements and other problems.