0

I'm trying to learn MVVM but am finding it a nightmare trying to understand how to correctly navigate between views in an application using MVVM. After some time researching and trying to understand different techniques I have come across an approach from Rachel Lim's blog. This technique uses a ViewModel for the application itself and keeps track of the application state such as the current page. I feel this would be a nice approach to follow for my application.

Now moving onto my problem..

What I want to achieve

I want an application that has a one main application view that will store a LoginView and a HomeView as DataTemplates and have a content control that sets the LoginView as the view displayed when the application is started. The LoginView will have a button that when pressed will open another window that has a button. When the button in the pop up window is pressed I want to change the view in the main application window from LoginView to the HomeView.

What I have so far

I have a set up the ApplicationView which works fine.

<Window x:Class="WPF_Navigation_Practice.Views.ApplicationView"
        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:ignore="http://www.galasoft.ch/ignore"
        xmlns:vm="clr-namespace:WPF_Navigation_Practice.ViewModels"
        xmlns:views="clr-namespace:WPF_Navigation_Practice.Views"
        mc:Ignorable="d ignore"
        DataContext="{StaticResource ApplicationViewModel}">

    <Window.Resources>
        <DataTemplate DataType="{x:Type vm:LoginViewModel}">
            <views:LoginView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:HomeViewModel}">
            <views:HomeView />
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <ContentControl Content="{Binding CurrentPageViewModel}" />
    </Grid>
</Window>

And have set up the ApplicationViewModel as follows. Setting the current page to the LoginViewModel.

using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using WPF_Navigation_Practice.Interfaces;

namespace WPF_Navigation_Practice.ViewModels
{
    /// <summary>
    /// This class contains properties that a View can data bind to.
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class ApplicationViewModel : ViewModelBase
    {
        #region Fields

        private ICommand _changePageCommand;

        private IPageViewModel _currentPageViewModel;
        private List<IPageViewModel> _pageViewModels;

        #endregion

        public ApplicationViewModel()
        {
            // Add available pages
            PageViewModels.Add(new LoginViewModel());
            PageViewModels.Add(new HomeViewModel());
            PageViewModels.Add(new CodeViewModel());


            // Set starting page
            CurrentPageViewModel = PageViewModels[0];
        }

        #region Properties / Commands

        public ICommand ChangePageCommand
        {
            get
            {
                if (_changePageCommand == null)
                {
                    _changePageCommand = new RelayCommand<object>(
                        p => ChangeViewModel((IPageViewModel)p),
                        p => p is IPageViewModel);
                }

                return _changePageCommand;
            }
        }

        public List<IPageViewModel> PageViewModels
        {
            get
            {
                if (_pageViewModels == null)
                    _pageViewModels = new List<IPageViewModel>();

                return _pageViewModels;
            }
        }

        public IPageViewModel CurrentPageViewModel
        {
            get
            {
                return _currentPageViewModel;
            }
            set
            {
                if (_currentPageViewModel != value)
                {
                    _currentPageViewModel = value;
                    RaisePropertyChanged("CurrentPageViewModel");
                }
            }
        }


        #endregion

        #region Methods

        private void ChangeViewModel(IPageViewModel viewModel)
        {
            if (!PageViewModels.Contains(viewModel))
                PageViewModels.Add(viewModel);

            CurrentPageViewModel = PageViewModels
                .FirstOrDefault(vm => vm == viewModel);
        }

        #endregion
    }
}

When I run the application it will display my main Application window which displays the loginView which is a UserControl and is set as the currentPageViewModel with ContentPresenter.

When the button in the LoginView UserControl is clicked it will open another window. As per the image below.

enter image description here

Here is the XAML for that window.

<Window x:Class="WPF_Navigation_Practice.Views.CodeView"
    x:Name="CodeWindow"
    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:ignore="http://www.galasoft.ch/ignore"
    xmlns:z="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:viewModels="clr-namespace:WPF_Navigation_Practice.ViewModels"
    mc:Ignorable="d ignore"
    d:DesignWidth="623.224" d:DesignHeight="381.269"
    DataContext="{Binding CodeViewModel, Source={StaticResource ApplicationViewModel}}">

<Grid>
    <Button Content="Ok" 
            HorizontalAlignment="Left" 
            Margin="235,166,0,0" 
            VerticalAlignment="Top"
            Width="138" 
            FontSize="20" 
            Height="67"/>
    <Label Content="Second Window" HorizontalAlignment="Left" Margin="166,56,0,0" VerticalAlignment="Top" FontSize="36"/>
</Grid>

My Problem

What I want to achieve is when the 'Ok' button in the secondView window is clicked, I want to change the currentPageViewModel in the ApplicationView Window from the LoginView to display the HomeView but am confused on how I would go about achieving this. Any help would be greatly appreciated.

slugster
  • 49,403
  • 14
  • 95
  • 145
TimoNZ
  • 49
  • 1
  • 4
  • 1
    I might give the loginview a LoggedIn event that the main view subscribes to. Then when that's raised, the main view can tell the application viewmodel that somebody logged in, by setting a property on it (it == app vm), calling a method, whatever. There should be One Viewmodel to Rule Them all, who makes decision about stuff like what to do when the user logs in -- I'm assuming that's the application viewmodel. – 15ee8f99-57ff-4f92-890c-b56153 Jul 13 '16 at 02:22
  • 1
    MVVM is honestly not well designed for having multiple windows (including dialogs etc). If possible, use a single window, and plug in different UserControls (each considered a View). – Jai Jul 13 '16 at 02:49
  • Any issues with passing main-window as parameter to the constructor of popup window, and setting view from there using main-window's property. – AnjumSKhan Jul 13 '16 at 06:27

1 Answers1

0

I see that you are already using MVVMLight. There is a Messenger class which can help you here. Register to the messenger in your ApplicationViewModel Constructor and in the code handling the button click in CodeViewModel use Send to send a message. In the action you pass on to register change the viewmodels as you wish.

See http://www.mvvmlight.net/help/WP8/html/9fb9c53a-943a-11d7-9517-c550440c3664.htm and Use MVVM Light's Messenger to Pass Values Between View Model

I don't have MVVMLight to write you a sample code. I've written a ViewModelMessenger from scratch and mine is like this:

public static void Register(string actionName, object registerer, Action<object, object> action)
{
    var actionKey = new Tuple<string, object>(actionName, registerer);
    if (!RegisteredActions.ContainsKey(actionKey))
    {
        RegisteredActions.Add(actionKey, action);
    }
    else
    {
        RegisteredActions[actionKey] = action;
    }
}

Used like:

VMMessenger.Register("ChangeViewModel",this,ChangeViewModelAction)

and

public static void SendMessage(string messageName, object message, object sender)
{
    var actionKeys = RegisteredActions.Keys.ToList();
    foreach (Tuple<string, object> actionKey in actionKeys)
    {
        if (actionKey.Item1 == messageName)
        {
            Action<object, object> action;
            if (RegisteredActions.TryGetValue(actionKey, out action))
            {
                action?.Invoke(message, sender);
            }
        }
    }
}

Used like:

VMMessenger.SendMessage("ChangeViewModel","HomeViewModel",this);

and in ChangeViewModelAction you can check for ViewModel names and change the CurrentPageViewModel to one with a matching name.

Community
  • 1
  • 1
Pouya Abadi
  • 255
  • 1
  • 7