使用主控详细信息页面进行MVVM导航的最佳实践?

使用主控详细信息页面进行MVVM导航的最佳实践?,mvvm,navigation,master-detail,xamarin-forms,Mvvm,Navigation,Master Detail,Xamarin Forms,我想尽可能多地遵循MVVM模式,但是我不知道我的导航是否做得很好。请注意,我使用的是母版详细信息页面,我希望维护母版页面,在导航时只更改细节侧 以下是我从视图模型中导航的方式。在本例中,从视图模型一到视图模型二: public class ViewModelOne : ViewModelBase { private void GoToViewTwo() { var viewTwo = new ViewTwo(new ViewModelTwo());

我想尽可能多地遵循MVVM模式,但是我不知道我的导航是否做得很好。请注意,我使用的是母版详细信息页面,我希望维护母版页面,在导航时只更改细节侧

以下是我从视图模型中导航的方式。在本例中,从视图模型一视图模型二

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

public class MasterView : MasterDetailPage
{
    public void NavigateToPage(Page page)
    {
        Detail = new NavigationPage(page);
        IsPresented = false;
    }
}
public partial class ViewTwo : PageBase
{
    public MenuView(ViewModelTwo vm)
        : base(vm)
    {
        InitializeComponent();
    }
}
public class PageBase : ContentPage
{
    public PageBase(ViewModelBase vmb)
    {
        this.BindingContext = vmb;
    }
}
查看两个实施:

public class MasterView : MasterDetailPage
{
    public void NavigateToPage(Page page)
    {
        Detail = new NavigationPage(page);
        IsPresented = false;
    }
}
public partial class ViewTwo : PageBase
{
    public MenuView(ViewModelTwo vm)
        : base(vm)
    {
        InitializeComponent();
    }
}
public class PageBase : ContentPage
{
    public PageBase(ViewModelBase vmb)
    {
        this.BindingContext = vmb;
    }
}
PageBase实施:

public class MasterView : MasterDetailPage
{
    public void NavigateToPage(Page page)
    {
        Detail = new NavigationPage(page);
        IsPresented = false;
    }
}
public partial class ViewTwo : PageBase
{
    public MenuView(ViewModelTwo vm)
        : base(vm)
    {
        InitializeComponent();
    }
}
public class PageBase : ContentPage
{
    public PageBase(ViewModelBase vmb)
    {
        this.BindingContext = vmb;
    }
}
这是进行导航的最佳方法(和最佳性能)吗?当我做一些导航时,应用程序开始运行较慢,可能是我做得不好

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


谢谢。

我认为你的思路是正确的,但是这里有一些问题:

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

var viewTwo = new ViewTwo(new ViewModelTwo());
主视图应负责创建视图。事实上,您甚至不需要担心创建视图,因为您可以使用
DataTemplate
来实现这一点。我以后再解释

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

您需要为视图模型提供某种基本的
接口
,以便保持通用性,稍后您将了解原因。让我们从一个简单的例子开始:

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.
}
xmlns:Views="clr-namespace:YourNamespace.Views"
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels"

... 

<DataTemplate DataType="{x:Type ViewModels:MovieViewModel}">
    <Views:MovieView/>
</DataTemplate>
<Window 
    ...
    xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels">
<Window.DataContext>
    <ViewModels:MasterViewModel/>
</Window.DataContext>
<Grid>
    <ContentPresenter Content="{Binding CurrentPage}"/>
</Grid>
现在我们有了某种视图模型结构,我打赌你会问“视图怎么样?”,这就是
DataTemplate
的用武之地

在WPF中,可以将视图分配给
类型
,例如,您可以在XAML中将
电影视图
分配给
电影视图模型
,如下所示:

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.
}
xmlns:Views="clr-namespace:YourNamespace.Views"
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels"

... 

<DataTemplate DataType="{x:Type ViewModels:MovieViewModel}">
    <Views:MovieView/>
</DataTemplate>
<Window 
    ...
    xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels">
<Window.DataContext>
    <ViewModels:MasterViewModel/>
</Window.DataContext>
<Grid>
    <ContentPresenter Content="{Binding CurrentPage}"/>
</Grid>
为了进一步扩展这一点,不仅
主视图
需要为其子对象包含
内容演示者
,而且
电影视图
也需要为其子对象包含一个
PickGenreViewModel
。您可以再次使用相同的方法:

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

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

使用此方法,您不必担心实例化任何视图,因为
DataTemplate
ContentPresenter
为您处理这些问题,您只需担心将视图模型映射到适当的视图

呸!这是一个很大的收获

要从中吸取的要点是:

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

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

    如果您有很多视图,您可以使用具有缓存的PageFactory,如下所示:哇,非常感谢您的解释,但我必须说,我正在使用Xamarin.Forms…我可以应用这种方法吗?没问题:-)当然,同样的原则也适用。我不相信Xamarin.Forms有ContentPresenter的概念,而这正是我遇到问题的地方。似乎Xamarin.Forms有ContentPresenter,如果需要的话,还有ContentView。