使用主控详细信息页面进行MVVM导航的最佳实践?
我想尽可能多地遵循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());
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
为您处理这些问题,您只需担心将视图模型映射到适当的视图
呸!这是一个很大的收获
要从中吸取的要点是:
最后需要注意的是,实现这一点的方法肯定不止一种,正如我刚才提到的,某种视图和视图模型管理器负责创建/删除视图和视图模型。如果您有很多视图,您可以使用具有缓存的PageFactory,如下所示:哇,非常感谢您的解释,但我必须说,我正在使用Xamarin.Forms…我可以应用这种方法吗?没问题:-)当然,同样的原则也适用。我不相信Xamarin.Forms有ContentPresenter的概念,而这正是我遇到问题的地方。似乎Xamarin.Forms有ContentPresenter,如果需要的话,还有ContentView。