【问题标题】:Best practice MVVM navigation using Master Detail page?使用 Master Detail 页面的最佳实践 MVVM 导航?
【发布时间】:2015-08-05 10:21:44
【问题描述】:

我想尽可能地遵循MVVM模式,但我不知道我的导航做得是否很好。请注意,我使用的是 MasterDetail 页面,并且我想维护 Master 页面,在导航时仅更改 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 页面的最佳导航方法吗?

谢谢。

【问题讨论】:

标签: mvvm navigation master-detail xamarin-forms


【解决方案1】:

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

首先,您不应该在视图模型中实例化视图。一旦你的视图模型意识到这个视图,那么你就已经打破了这个模式。

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

这当然是一个非常简单的基础视图模型,你可以在以后扩展它,这样做的主要原因是允许你创建一个 ma​​ster 视图模型来负责显示您当前的页面。下面是一个主视图模型的简单示例:

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 中,可以将 view 分配给 Type,例如,您可以将 MovieView 分配给 XAML 中的 MovieViewModel,如下所示:

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

... 

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

好极了!现在要让主视图真正显示当前页面的视图,您只需创建一个ContentPresenter,并将其Content 绑定到CurrentPage。您的主视图将如下所示:

<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 是 UI,Data 是 Data
  2. 视图模型的责任在于拥有它们的人,对于父子关系,让父管理子关系而不是视图模型管理器是有意义的。

最后一点是,当然还有不止一种其他方法可以实现这一点,正如我刚才提到的,某种视图和视图模型管理器负责创建/删除视图和视图模型。

【讨论】:

  • 哇,非常感谢您的解释,但我必须说我正在使用 Xamarin.Forms...我可以应用这种方法吗?
  • 没问题 :-) 当然,同样的原则也适用。
  • 我不相信 Xamarin.Forms 有 ContentPresenter 的概念,这是我遇到问题的地方。
  • 看来 Xamarin.Forms 有 ContentPresenter 和 ContentView 如果你需要的话。
猜你喜欢
  • 1970-01-01
  • 2017-03-25
  • 1970-01-01
  • 2017-08-09
  • 2018-01-30
  • 1970-01-01
  • 1970-01-01
  • 2020-01-09
  • 2021-09-24
相关资源
最近更新 更多