10
\$\begingroup\$

I did a project in WPF MVVM. Just a single Window with single Frame control and few Pages. My problem was communication between pages without not violating MVVM principles. I'd like to use all bestprogramming-practices.

Can you check if this code agrees with the all best principles?

My solution has two projects: WPF client, ViewModels PCL. I want to have my ViewModels separated from the Views.

Here is the code for WPF Client:

GitHub

App.xaml.cs

namespace NaviWPFApp
{
    using System.Windows;
    using NaviWPFApp.Views;
    using NaviWPFApp.Views.Pages;   

    public partial class App : Application
    {
        public static NavigationService Navigation; 

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);  

            MainWindow mainWindow = new MainWindow();
            mainWindow.Show();  

            Navigation = new NavigationService(mainWindow.MyFrame);
            Navigation.Navigate<FirstPage>();
        }
    }
}

App.xaml It's just:

<Application x:Class="NaviWPFApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:NaviWPFApp"
             x:Name="Application">
    <Application.Resources>
        <local:ViewModelLocator x:Key="ViewModelLocator"/>
    </Application.Resources>
</Application>

I have one main window with frame, and two very similar pages (no code-behind):

<Window x:Class="NaviWPFApp.Views.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"
        mc:Ignorable="d"
        Title="NaviWPFApp" Height="300" Width="300">
    <Grid>
        <Frame x:Name="MyFrame" Margin="10" />
    </Grid>
</Window>   

<Page x:Class="NaviWPFApp.Views.Pages.FirstPage"
      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"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300" Title="FirstPage" 
      DataContext="{Binding FirstPageViewModel, Source={StaticResource ViewModelLocator}}">
    <Grid>
        <Button Command="{Binding Go2}"  Height="30" Content="Go to second page" />
    </Grid>
</Page> 

<Page x:Class="NaviWPFApp.Views.Pages.SecondPage"
      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"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
      Title="SecondPage"
      DataContext="{Binding Path=SecondPageViewModel, Source={StaticResource ViewModelLocator}}">
    <Grid>
        <Button Command="{Binding Go1}" Content="Go back to page 1" Height="30" />
    </Grid>
</Page>

I my client I have also two additional classes ViewModelLocator and NavigationService - used for naviation between pages:

namespace NaviWPFApp
{
    using NaviWPFApp.ViewModels.Pages;  

    public class ViewModelLocator
    {
        public FirstPageViewModel FirstPageViewModel => new FirstPageViewModel(App.Navigation);
        public SecondPageViewModel SecondPageViewModel => new SecondPageViewModel(App.Navigation);
    }
}   

namespace NaviWPFApp
{
    using System;
    using System.Linq;
    using System.Reflection;
    using System.Windows.Controls;
    using NaviWPFApp.ViewModels.Common; 

    public class NavigationService : INavigationService
    {
        readonly Frame frame;   

        public NavigationService(Frame frame)
        {
            this.frame = frame;
        }   

        public void GoBack()
        {
            frame.GoBack();
        }   

        public void GoForward()
        {
            frame.GoForward();
        }   

        public bool Navigate(string page)
        {
            var type = Assembly.GetExecutingAssembly().GetTypes().SingleOrDefault(a => a.Name.Equals(page));
            if (type == null) return false; 

            var src = Activator.CreateInstance(type);
            return frame.Navigate(src);
        }

        public bool Navigate<T>(object parameter = null)
        {
            var type = typeof(T);
            return Navigate(type, parameter);
        }

        public bool Navigate(Type source, object parameter = null)
        {
            var src = Activator.CreateInstance(source);
            return frame.Navigate(src, parameter);
        }
    }
}

Here is my ViewModels (Portable) project:

It's only two ViewModel classes for each Page in UI, INavigationService (I don't want to know anything about NavigationService implementation and UI client), MyObservableObject and MyCommand.

MyObservableObject and MyCommand are typical implementations of INotifyPropertyChanged and ICommand interfaces.

This is an interface and two viewmodels:

public interface INavigationService
{
    void GoForward();
    void GoBack();
    bool Navigate(string page);
}

public class FirstPageViewModel : MyObservableObject
{
    private readonly INavigationService navigationService;

    public FirstPageViewModel(INavigationService navigationService)
    {
        this.navigationService = navigationService;
    }

    public MyCommand Go2
    {
        get { return new MyCommand(x => navigationService.Navigate("SecondPage")); }
    }
}

public class SecondPageViewModel : MyObservableObject
{
    private readonly INavigationService navigationService;

    public SecondPageViewModel(INavigationService navigationService)
    {
        this.navigationService = navigationService;
    }

    public MyCommand Go1
    {
        get { return new MyCommand(x => navigationService.Navigate("FirstPage")); }
    }
}

My biggest concern is this: navigationService.Navigate("FirstPage")); I pass view name as a string. That's because I don't want my ViewModel knows anything about View. But my navigation service HAS TO KNOW about View. That's why I did that interface with string parameter.

What do you think?

\$\endgroup\$
  • \$\begingroup\$ In my opinion, I don't see anything that violates Mvvm pattern. \$\endgroup\$ – Francesco Bonizzi Jan 28 '16 at 15:32
  • \$\begingroup\$ Could you tell me how to get the parameters on the page? \$\endgroup\$ – Edson Mata Dec 18 '18 at 19:41
3
\$\begingroup\$

My biggest concern is this: navigationService.Navigate("FirstPage")); I pass view name as a string. That's because I don't want my ViewModel knows anything about View. But my navigation service HAS TO KNOW about View. That's why I did that interface with string parameter.

In a given design, independently from any implementation, a dependency either exist or not. Trying to solve this "problem" in the implementation is impossible.

You have to choose if your navigation service helps navigating through xaml views, or through some higher level, more abstract "navigation items".

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for an answer. I'd like to navigate in viewmodels via commands. But viewmodels are located in separated project (they don't see Views at all). And this project is PCL. I've put the code into repo here: github.com/AntwanReno/navi \$\endgroup\$ – tobiasznowak Feb 1 '16 at 20:24

Not the answer you're looking for? Browse other questions tagged or ask your own question.