Skip to main content
edited tags
Link
Mast
  • 13.9k
  • 12
  • 57
  • 128
Source Link
Yoav
  • 319
  • 5
  • 18

WPF MVVM Navigation

Following a lot of research and mainly based on [this tutorial][1] I came up with the following structure in my application:
ObjectBase - all view models inherit it:

public abstract class ObjectBase : INotifyPropertyChanged
{
    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected void SetProperty<T>(ref T member, T val,
       [CallerMemberName]string propertyName = null)
    {
        if (object.Equals(member, val)) return;

        member = val;
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion INotifyPropertyChanged

    public abstract ObjectBase BackLocation { get; }

    public abstract event Action<ObjectBase> NavigateTo;

    public abstract string ViewHeader { get; }
}

Main Content ViewModel:

public class MainContentViewModel : ObjectBase
{
    private ObjectBase _selectedView;
    public ObjectBase SelectedView
    {
        get { return _selectedView; }
        set
        {
            if (SelectedView != null)
            {
                SelectedView.NavigateTo -= SelectedView_NavigateTo;
            }
            if (SelectedView is IDisposable)
            {
                (SelectedView as IDisposable).Dispose();
            }

            SetProperty(ref _selectedView, value);
            SelectedView.NavigateTo += SelectedView_NavigateTo;
        }
    }

    public MainContentViewModel()
    {
        SelectedView = new ViewModels.FirstViewModel();
    }

    private void SelectedView_NavigateTo(ObjectBase viewToNavigateTo)
    {
        SelectedView = viewToNavigateTo;
    }

    #region Back Command
    private CommandBase _backCommand;

    public CommandBase BackCommand
    {
        get { return _backCommand ?? (_backCommand = new CommandBase(Back)); }
    }

    private void Back(object obj)
    {
        if (SelectedView.BackLocation != null)
        {
            SelectedView = SelectedView.BackLocation;
        }
        else
        {
            Application.Current.MainWindow.Close();
        }
    }
    #endregion Back Command

    #region ObjectBase Fields
    public override event Action<ObjectBase> NavigateTo;
    public override string ViewHeader { get { return "Main View"; } }
    public override ObjectBase BackLocation { get { return null; } }
    #endregion
}

Main Content View:

<Window x:Class="WpfNavigationTest.Views.MainContentView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:views="clr-namespace:WpfNavigationTest.Views"
    xmlns:viewModels="clr-namespace:WpfNavigationTest.ViewModels">
<Window.DataContext>
    <viewModels:MainContentViewModel/>
</Window.DataContext>
<Window.Resources>
    <DataTemplate DataType="{x:Type viewModels:FirstViewModel}">
        <views:FirstView/>
    </DataTemplate>
    
    <DataTemplate DataType="{x:Type viewModels:SecondViewModel}">
        <views:SecondView/>
    </DataTemplate>
</Window.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Button Content="Go Back" Command="{Binding BackCommand}"/>
    <Separator Margin="0,15" Grid.Row="1"/>
    <ContentPresenter Content="{Binding SelectedView}" Grid.Row="2"/>
</Grid>

Sample View Model 1:

public class FirstViewModel : ObjectBase
{
    #region GoForward Command
    private CommandBase _goForwardCommand;
    public CommandBase GoForwardCommand
    {
        get { return _goForwardCommand ?? (_goForwardCommand = new CommandBase(GoForward)); }
    }
    private void GoForward(object obj)
    {
        NavigateTo(new ViewModels.SecondViewModel(DateTime.Now.ToString()));
    }
    #endregion
    public override ObjectBase BackLocation { get { return null; } }
    public override string ViewHeader { get { return "First View"; } }

    public override event Action<ObjectBase> NavigateTo;
}

Sample View Model 2:

public class SecondViewModel : ObjectBase
{
    private string _addMessage;

    public SecondViewModel(string addedMessage)
    {
        _addMessage = addedMessage;
    }

    public override ObjectBase BackLocation { get { return new ViewModels.FirstViewModel(); } }

    public override string ViewHeader { get { return $"Second View. Message: {_addMessage}"; } }

    public override event Action<ObjectBase> NavigateTo;
}

In general, this works as expected but I would like to know if I'm at the right direction and how can I improve this? [1]: https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/