使用Master Detail页面进行MVVM导航的最佳实践



我想尽可能地遵循MVVM模式,但我不知道我的导航是否做得很好。请注意,我使用的是MasterDetail页面,并且我希望维护Masterpage,在导航时只更改Detail侧。

以下是我在ViewModel中导航的方式。在本例中,从ViewModelOneViewModelTwo

public class ViewModelOne : ViewModelBase
{
    private void GoToViewTwo()
    {
        var viewTwo = new ViewTwo(new ViewModelTwo());
        ((MasterView)Application.Current.MainPage).NavigateToPage(viewTwo);
    }
}

MasterView实现:

public class MasterView : MasterDetailPage
{
    public void NavigateToPage(Page page)
    {
        Detail = new NavigationPage(page);
        IsPresented = false;
    }
}

ViewTwo实现:

public partial class ViewTwo : PageBase
{
    public MenuView(ViewModelTwo vm)
        : base(vm)
    {
        InitializeComponent();
    }
}

PageBase实现:

public class PageBase : ContentPage
{
    public PageBase(ViewModelBase vmb)
    {
        this.BindingContext = vmb;
    }
}

这是进行导航的最佳方法(和最佳性能(吗?当我做一些导航时,应用程序开始运行得更慢,也许是我做得不好。

这是导航显示MasterDetail页面的最佳方法吗?

谢谢。

我认为你肯定走在了正确的轨道上,但这里有一些问题:

首先,您不应该在视图模型中实例化视图。一旦您的视图模型意识到视图,那么您就基本上打破了这种模式。

var viewTwo = new ViewTwo(new ViewModelTwo());

您的视图创建应由主视图负责。事实上,您甚至不需要担心创建视图,因为您可以使用DataTemplate。我稍后再解释。

首先,我们需要将View ModelsViews

为了保持通用性,视图模型需要某种基础classinterface,稍后您就会明白原因。让我们从一个简单的例子开始:

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.
    }
}

当然,这是一个非常简单的基本视图模型,您可以稍后对此进行扩展,其主要原因是允许您创建一个主视图模型,该视图模型将负责显示您当前的页面。以下是主视图模型的一个简单示例:

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.
}

请注意,INotifyPropertyChanged在某种基类中可能会更好,而不必一遍又一遍地重新实现相同的代码

现在MasterViewModel非常简单,它只保存当前页面,但是拥有master的目的是允许执行应用程序级代码,就像关闭应用程序一样,这样你就可以将此逻辑与其他视图模型隔离开来。

好的,现在谈好东西。

您的详细信息与其父级有关系,因此,可以说管理它是父级的责任。在这种情况下,您的主详细信息视图模型看起来像这样:

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.
}

所以,现在我们有了某种视图模型结构,我敢打赌你会问"视图呢?",这就是DataTemplate的作用所在

在WPF中,可以将视图分配给Type,例如,您可以在XAML中将MovieView分配给MovieViewModel,如下所示:

xmlns:Views="clr-namespace:YourNamespace.Views"
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels"
... 
<DataTemplate DataType="{x:Type ViewModels:MovieViewModel}">
    <Views:MovieView/>
</DataTemplate>

好的,太好了!,现在,要使Master View实际显示当前页面的视图,只需创建一个ContentPresenter,并将其Content绑定到CurrentPage。您的主视图将如下所示:

<Window 
    ...
    xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels">
<Window.DataContext>
    <ViewModels:MasterViewModel/>
</Window.DataContext>
<Grid>
    <ContentPresenter Content="{Binding CurrentPage}"/>
</Grid>

为了进一步扩展,不仅MasterView需要为其子级包含一个ContentPresenterMovieView也需要为其子级PickGenreViewModel包含一个。你可以再次使用相同的方法:

<Grid>
    <!-- The main view code for the movie view -->
    ...
    <Border Visibility="{Binding ChildViewModel.IsDisplayed, Converter=...">
        <ContentPresenter Content="{Binding ChildViewModel}"/>
    </Border>
</Grid>

注意:使用布尔值到可见性转换器来确定是否显示子内容

使用这种方法,您不必担心实例化任何视图,因为DataTemplateContentPresenter为您处理了这一问题,您只需要将视图模型映射到适当的视图。

呜呜!这是一个很大的收获。

要从中吸取的要点是:

  1. 您不应该在视图模型中创建视图,请记住,UI就是UI,Data就是Data
  2. 视图模型的责任在于拥有它们的人,对于父子关系,让父母管理孩子是有意义的,而不是视图模型管理器

最后要注意的是,正如我刚才提到的,实现这一点的方法肯定不止一种,某种视图和视图模型管理器负责创建/删除视图和视图模式。

最新更新