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

时间:2015-08-05 10:21:45

标签: mvvm navigation master-detail xamarin-forms

我想尽可能地遵循 MVVM 模式 ,但我不知道我是否正在进行导航。请注意,我正在使用MasterDetail页面,我想维护主页面,在我导航时仅更改详细信息侧。

以下是我从 ViewModel 导航的方式。在此示例中,从 ViewModelOne ViewModelTwo

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页面的最佳方法吗?

感谢。

1 个答案:

答案 0 :(得分:4)

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

首先,您不应在视图模型中实例化视图。一旦您的视图模型意识到该视图,那么您几乎已经破坏了该模式。

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

您的视图创建应该是主视图的责任。事实上,您甚至不必担心创建视图,因为您可以使用DataTemplate。我稍后会解释。

首先,我们需要将查看模型视图分开,以下是我的建议:

您的视图模型需要某种基础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非常简单,它只保存当前页面,但是拥有主页的目的是允许执行应用程序级代码,例如关闭应用程序这样你就可以将这种逻辑与其他视图模型保持一致。

是的,现在就好了。

您的详细信息与其父母有关系,因此有必要说父母负责管理它。在这种情况下,您的主 - 详细视图模型将如下所示:

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,例如,您可以将MovieView分配给MovieViewModel在XAML中,像这样:

xmlns:Views="clr-namespace:YourNamespace.Views"
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels"

... 

<DataTemplate DataType="{x:Type ViewModels:MovieViewModel}">
    <Views:MovieView/>
</DataTemplate>

好的!现在要让主视图实际显示当前页面的视图,您只需要创建一个ContentPresenter,并将其绑定即可。 s ContentCurrentPage。您的主视图将如下所示:

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

为了进一步扩展这一点,不仅MasterView需要为其孩子包含ContentPresenter,它也是MovieView需要一个孩子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是用户界面,数据是数据
  2. 查看模型职责取决于谁拥有它们,对于父子关系,让父母管理孩子是有意义的,而不是视图模型管理者。
  3. 最后要注意的是,正如我刚才提到的,当然还有不止一种其他方法可以实现这一点,某种视图和视图模型管理器负责创建/删除视图和查看模型。