marko devcic

Software Engineer
  • github:
    deva666
  • email:
    madevcic {at} gmail.com
Beginner MVVM Navigation Part 3

Beginner MVVM Navigation Part 3

Posted on 04/23/2014

In first two parts of this series (part 1, part 2) we had one HostViewModel that held on to its children and one that disposed them as soon as they got out of view. In this part we're going to use a combination of that two. We're going to build them using weak references and let the GC decide when and if should they be collected. Since the GC will be determining the life time of our child view models, we can't call Dispose on them.

Here's the complete code.

internalabstractclass HostViewModel : ViewModelBase
    {
        privatereadonly Dictionary<Type, ViewModelResolver> _childrenMap = new Dictionary<Type, ViewModelResolver>();

        protected ViewModelBase _selectedChild;

        public ViewModelBase SelectedChild
        {
            get { return _selectedChild; }
            set
            {
                if (_selectedChild != null && _selectedChild.Title == value.Title)
                    return;

                SetPropertyValue(ref _selectedChild, value);
            }
        }

        protectedvoid RegisterChild<T>(Func<T> getter) where T : ViewModelBase
        {
            Contract.Requires(getter != null);

            if (_childrenMap.ContainsKey(typeof(T)))
                return;

            var resolver = new ViewModelResolver(getter);
            _childrenMap.Add(typeof(T), resolver);
        }

        protected ViewModelBase GetChild(Type type)
        {
            Contract.Requires(type != null);
            if (_childrenMap.ContainsKey(type) == false)
                thrownew InvalidOperationException("Can't resolve type " + type.ToString());

            var resolver = _childrenMap[type];
            ViewModelBase viewModel = null;
            resolver.Reference.TryGetTarget(out viewModel);
            if (viewModel == null)
            {
                viewModel = resolver.Getter();
                resolver.Reference.SetTarget(viewModel);
            }
            return viewModel;
        }

        privateclass ViewModelResolver
        {
            public WeakReference<ViewModelBase> Reference { get; privateset; }
            public Func<ViewModelBase> Getter { get; privateset; }

            public ViewModelResolver(Func<ViewModelBase> getter)
            {
                Reference = new WeakReference<ViewModelBase>(null);
                Getter = getter;
            }
        }
    }

And the main view model implementing it.

internalsealedclass MainViewModel : HostViewModel
    {
        privatereadonly Timer _timer;

        publicoverridestring Title { get { return"Main"; } }

        publicstring Version
        {
            get { returnthis.GetType().Assembly.GetName().Version.ToString(); }
        }

        privatestring _time;
        publicstring Time
        {
            get { return _time; }
            set { SetPropertyValue(ref _time, value); }
        }

        private ICommand _goToBooksCommand;
        public ICommand GoToBooksCommand
        {
            get { return _goToBooksCommand ?? (_goToBooksCommand = new DelegateCommand(GoToBooks)); }
        }

        private ICommand _goToAuthorsCommand;
        public ICommand GoToAuthorsCommand
        {
            get { return _goToAuthorsCommand ?? (_goToAuthorsCommand = new DelegateCommand(GoToAuthors)); }
        }

        private ICommand _goToSettingsCommand;
        public ICommand GoToSettingsCommand
        {
            get { return _goToSettingsCommand ?? (_goToSettingsCommand = new DelegateCommand(GoToSettings)); }
        }

        public MainViewModel()
        {
            this.RegisterChild<BooksViewModel>(() => new BooksViewModel());
            this.RegisterChild<AuthorsViewModel>(() => new AuthorsViewModel());
            this.RegisterChild<SettingsViewModel>(() => new SettingsViewModel());

            this.SelectedChild = GetChild(typeof(BooksViewModel));

            _timer = new Timer((s) => Time = DateTime.Now.ToLongTimeString(), this, 500, 500);
        }

        privatevoid GoToBooks()
        {
            this.SelectedChild = GetChild(typeof(BooksViewModel));
        }

        privatevoid GoToAuthors()
        {
            this.SelectedChild = GetChild(typeof(AuthorsViewModel));
        }

        privatevoid GoToSettings()
        {
            this.SelectedChild = GetChild(typeof(SettingsViewModel));
        }

        protectedoverridevoid OnDispose()
        {
            _timer.Dispose();
            base.OnDispose();
        }
    }

And that's it, three ways how you can implement the MVVM navigation between view models. Each way has it's advantages and disadvantages, so use whatever suits your design best.

In one of the next posts I'll show you how you can easily add animation to view changes.