Constructor of the ModelViews being called twice

Jul 2, 2013 at 6:15 PM
Edited Jul 2, 2013 at 6:16 PM
Hello everyone,

I have spent many days stuck in a problem that I cannot solve. My application is as follows:
The main window is divided into two "ContentControls" the left one is a menu with buttons that serves the user to navigate through the different views. On the right I'm showing the different windows that the user is asking for.

The application is running pretty fine; but my problem is that the constructor of my ViewModels are being called twice. The first time, when I instantiate the child view in the main view and the second time is done automatically from the ViewModelLocator.

My code is as follows:
MainViewModel.cs
  public class MainViewModel : ViewModelBase
    {
        private ViewModelBase _currentViewModel;
        private ViewModelBase _leftViewModel;

        readonly static ToolbarViewModel toolbarViewModel = new ToolbarViewModel();        
        // !!!! Here I add all the views I want to show while navigating, and here is where the first instance is created!
        readonly static TelemetryViewModel telemetryViewModel = new TelemetryViewModel();
        readonly static LogsViewModel logsViewModel = new LogsViewModel();
      
        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            LeftViewModel = MainViewModel.toolbarViewModel;
            CurrentViewModel = MainViewModel.telemetryViewModel;
            Messenger.Default.Register<GoToPageMessage>
            (
                this,
                (action) => ReceiveMessage(action)
            );
        }

        public ViewModelBase CurrentViewModel
        {
            get
            {
                return _currentViewModel;
            }
            set
            {
                if (_currentViewModel == value)
                    return;
                _currentViewModel = value;
                RaisePropertyChanged("CurrentViewModel");
            }
        }

        public ViewModelBase LeftViewModel
        {
            get
            {
                return _leftViewModel;
            }
            set
            {
                if (_leftViewModel == value)
                    return;
                _leftViewModel = value;
                RaisePropertyChanged("LeftViewModel");
            }
        }

        private object ReceiveMessage(GoToPageMessage action)
        {
            switch (action.PageName)
            {
                case "LogsView":
                    CurrentViewModel = MainViewModel.logsViewModel;
                    break;
                case "TelemetryView":
                    CurrentViewModel = MainViewModel.telemetryViewModel;
                    break;
                default:
                    break;
            }
            return null;
        }
    }
ViewModelLocator.cs
 public class ViewModelLocator
    {       
        public ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
            SimpleIoc.Default.Register<MainViewModel>();
            SimpleIoc.Default.Register<TelemetryViewModel>();
            SimpleIoc.Default.Register<LogsViewModel>();
            SimpleIoc.Default.Register<ToolbarViewModel>();
        }

        public MainViewModel MainViewModel
        {
            get
            {
               return ServiceLocator.Current.GetInstance<MainViewModel>();                
            }
        }

        public TelemetryViewModel TelemetryViewModel
        {
            get
            {
               // Here is where the second instance is created!
                return ServiceLocator.Current.GetInstance<TelemetryViewModel>();
            }
        }
        
        public LogsViewModel LogsViewModel
        {
            get
            {
                return ServiceLocator.Current.GetInstance<LogsViewModel>();
            }
        }
       
        public ToolbarViewModel ToolbarViewModel
        {
            get
            {
                return ServiceLocator.Current.GetInstance<ToolbarViewModel>();
            }
        }  
}
Any comment will be very grateful. Thanks everybody!!
Jul 3, 2013 at 8:58 AM
I haven't used MVVM Light's IoC, we use Unity but the ViewModelLocator will return a new instance of the viewmodel unless you explicitly register it as a singleton in the IoC.

Our app is pretty large and we have many separate modules and use Prism for the modularity side of things and MVVM LIght for all the viewmodel communication and messaging between modules.

We tend to register the module main parent view in the Ioc and when the child viewmodels get instantiated using a view 1st approach with a viewmodel locator they get the parent viewmodel from the Ioc in their constructor and set a reference to the child vm in the parent so essentially the child view sets the reference, I've checked this with a memory profiler and this works as expected (also releases ok from memory assuming you remove all your handlers etc)

Parent view (code behind cs file)
public DemoMainView(DemoMainViewModel vm)
        {

            IUnityContainer container = ServiceLocator.Current.GetInstance<IUnityContainer>();
            container.RegisterInstance<DemoMainViewModel>(vm, new ContainerControlledLifetimeManager());

            InitializeComponent();
            this.DataContext = vm;

        }

public void Cleanup(object sender, RoutedEventArgs e)
        {
            // Clear vm from memory
            DemoMainViewModel vm = (DemoMainViewModel)this.DataContext;
            if (vm != null)
            {
                //// Remove instance from IoC and allow Prism to unload the module
                if (vm.KeepAlive == false)
                {

                    this.DataContext = null;

                    // Remove parent class from IoC
                    IUnityContainer container = ServiceLocator.Current.GetInstance<IUnityContainer>();
                    var registration = container.Registrations.First(r => r.RegisteredType == typeof(DemoMainViewModel));
                    registration.LifetimeManager.RemoveValue();
                }
                vm = null;
                

            }

        }
In the child view
public class NotificationViewModel : GalaSoft.MvvmLight.ViewModelBase, ICleanup
    {

        
        public NotificationViewModel()
        {
            // Set reference to parent view model so it can alter child vm properties
            DemoMainViewModel parent = ServiceLocator.Current.GetInstance<DemoMainViewModel>();
            parent.Notifications = this;
            
ViewModelLocator
class DemoViewModelLocator
    {
        private IUnityContainer _container;
        private IUnityContainer Container
        {
            get
            {
                if (_container == null)
                    _container = ServiceLocator.Current.GetInstance<IUnityContainer>();
                return _container;
            }
        }

        public NotificationViewModel NotificationDemo
        {
            get { return Container.Resolve<NotificationViewModel>(); }
        }

        public ThreadingViewModel ThreadingDemo
        {
            get { return Container.Resolve<ThreadingViewModel>(); }
        }
In the child view:
<UserControl x:Class="VShips.Framework.Demo.View.ThreadingView"
             x:Name="ucThreadingView"
             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:vm="clr-namespace:CompanyName.ApplicationName.Demo.ViewModel"
             xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
             xmlns:common="clr-namespace:CompanyName.ApplicationName.Common.Model;assembly=VShips.Framework.Common"
             xmlns:mvvm="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF45"
             xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
             xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
             mc:Ignorable="d" 
             d:DesignHeight="300" 
             d:DesignWidth="300">

    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/CompanyName.Application.Resource;Component/SharedResources.xaml"></ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>

            <vm:DemoViewModelLocator x:Key="ViewModelLocator" />
        </ResourceDictionary>
    </UserControl.Resources>

    <UserControl.DataContext>
        <Binding Path="ThreadingDemo" Source="{StaticResource ViewModelLocator}" />
    </UserControl.DataContext>
I think if your register your vm with a key in simple IoC you should be able to get the existing instance rather than a new one, Laurent discusses it here.

http://stackoverflow.com/questions/7297014/does-the-mvvm-light-simpleioc-support-singletons

Hope this is of some assistance,

Taz.
Jul 3, 2013 at 11:10 AM
Thanks very much!! Your code snippets have been very useful.

Following your guides I have realized that I was creating instances explicitly besides the IOC container.

Regards!
Jul 3, 2013 at 11:28 AM
JosepB wrote:
Thanks very much!! Your code snippets have been very useful.

Following your guides I have realized that I was creating instances explicitly besides the IOC container.

Regards!
Your welcome :-)