Reputation: 737
I want to follow the MVVM pattern as much as possible, but I don't know if I am doing the navigation quite well. Note that I am using a MasterDetail page and I want to maintain the Master page, changing only the Detail side when I navigate.
Here is the way I navigate from my ViewModel. In this example, from ViewModelOne to ViewModelTwo:
public class ViewModelOne : ViewModelBase
{
private void GoToViewTwo()
{
var viewTwo = new ViewTwo(new ViewModelTwo());
((MasterView)Application.Current.MainPage).NavigateToPage(viewTwo);
}
}
MasterView implementation:
public class MasterView : MasterDetailPage
{
public void NavigateToPage(Page page)
{
Detail = new NavigationPage(page);
IsPresented = false;
}
}
ViewTwo implementation:
public partial class ViewTwo : PageBase
{
public MenuView(ViewModelTwo vm)
: base(vm)
{
InitializeComponent();
}
}
PageBase implementation:
public class PageBase : ContentPage
{
public PageBase(ViewModelBase vmb)
{
this.BindingContext = vmb;
}
}
Is this the best approach (and best performance) for do the navigation? When I do some navigations, the app starts to run slower and maybe there is something I am not doing fine.
Is this the best approach for do the navigation showing always a MasterDetail page?
Thanks.
Upvotes: 3
Views: 6467
Reputation: 9713
I think you're certainly on the right track, however there are a few issues here:
Firstly, you should not be instantiating Views in your view model. As soon as your View Model becomes aware of the view, then you've pretty much broken the pattern.
var viewTwo = new ViewTwo(new ViewModelTwo());
Your view creation should be the responsibility of the master view. In fact, you don't even need to worry about creating views, as you can use a DataTemplate
for that. I'll explain that later.
Firstly, we need to separate your View Models from the Views, here is what I propose:
You'll need some kind of base class
or interface
for your view models in order to keep things generic, you'll see why in a moment. Let's start out with a simple example:
public abstract class ViewModel : INotifyPropertyChanged
{
public event EventHandler OnClosed;
public event EventHandler OnOpened;
//Don't forget to implement INotifyPropertyChanged.
public bool IsDisplayed { get; private set; }
public void Open()
{
IsDisplayed = true;
//TODO: Raise the OnOpened event (Might be a better idea to put it in the IsDisplayed getter.
}
public void Close()
{
IsDisplayed = false;
//TODO: Raise the OnClosed event.
}
}
This of course is a very simple base view model, you can extend on this later, the main reason for this is to allow you to create a master view model which will be responsible for displaying your current page. Here's a simple example of a master view model:
public class MasterViewModel : INotifyPropertyChanged
{
//Don't forget to implement INotifyPropertyChanged.
public ViewModel CurrentPage { get; private set; }
public MasterViewModel()
{
//This is just an example of how to set the current page.
//You might want to use a command instead.
CurrentPage = new MovieViewModel();
}
//TODO: Some other master view model functionality, like exiting the application.
}
Please note that INotifyPropertyChanged
would probably be better in some kind of base class, instead of having to re-implement the same code over and over.
Now the MasterViewModel
is pretty simple, it just holds the current page, however the purpose of having the master is to allow for application level code to be executed, like closing the app, that way you're keeping this logic away from your other view models.
Right, now onto the good stuff.
Your detail has a relationship to it's parent, therefore it would make sense to say that it is the responsibility of the parent to manage it. In this case, your master-detail view model would look something like this:
public class MovieViewModel : ViewModel
{
protected PickGenreViewModel ChildViewModel { get; private set; }
public MovieViewModel()
{
ChildViewModel = new PickGenreViewModel();
//TODO: Perhaps subscribe to the closed event?
}
//Just an example but an important thing to note is that
//this method is protected because it's the MovieViewModel's
//responsibility to manage it's child view model.
protected void PickAGenre()
{
ChildViewModel.Open();
}
//TODO: Other view model functionality.
}
So, now we've got some kind of view model structure here, I bet you're asking "What about the views?", well, that's where the DataTemplate
comes in.
In WPF, it's possible to assign a view to a Type
, for example, you can assign the MovieView
to the MovieViewModel
in XAML, like this:
xmlns:Views="clr-namespace:YourNamespace.Views"
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels"
...
<DataTemplate DataType="{x:Type ViewModels:MovieViewModel}">
<Views:MovieView/>
</DataTemplate>
Ok great!, now to get the Master View to actually display the current page's view, you simply need to create a ContentPresenter
, and bind it's Content
to the CurrentPage
. Your Master View will look something like this:
<Window
...
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels">
<Window.DataContext>
<ViewModels:MasterViewModel/>
</Window.DataContext>
<Grid>
<ContentPresenter Content="{Binding CurrentPage}"/>
</Grid>
To extend this further, it's not only the MasterView
that needs to contain a ContentPresenter
for it's child, it is also the MovieView
which needs one for it's child PickGenreViewModel
. You can use the same method again:
<Grid>
<!-- The main view code for the movie view -->
...
<Border Visibility="{Binding ChildViewModel.IsDisplayed, Converter=...">
<ContentPresenter Content="{Binding ChildViewModel}"/>
</Border>
</Grid>
Note: Use a boolean to Visibility converter to determine whether to display the child content.
Using this method you don't have to worry about instantiating any views, as the DataTemplate
and ContentPresenter
handles that for you, all you need to worry about is mapping the view models to the appropriate view.
Phew! That was a lot to take in.
The main points to take away from this are:
A final note is that there are certainly more than one other ways of achieving this, as I just mentioned, some kind of view and view model manager to be responsible for creating/removing views and view models.
Upvotes: 5