8
\$\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
2
\$\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".

\$\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

protected by Jamal Dec 18 '18 at 23:50

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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