我认为您肯定走在正确的轨道上,但是这里有一些问题:
首先,您不应该在视图模型中实例化视图。一旦你的视图模型意识到这个视图,那么你就已经打破了这个模式。
var viewTwo = new ViewTwo(new ViewModelTwo());
您的视图创建应该是主视图的责任。事实上,您甚至不必担心创建视图,因为您可以使用 a DataTemplate
。我稍后会解释。
首先,我们需要将您的View Models与Views分开,这是我的建议:
为了保持通用性,您将需要某种基础class
或视图模型,稍后您会明白为什么。interface
让我们从一个简单的例子开始:
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
,例如,您可以将 分配给MovieView
XAMLMovieViewModel
中的 ,如下所示:
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
需要包含 a ContentPresenter
for it's child,它还需要包含 a for it MovieView
's child 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,Data 是 Data。
- 视图模型的责任在于拥有它们的人,对于父子关系,让父管理孩子是有意义的,而不是视图模型管理器。
最后一点是,肯定有不止一种其他方法可以实现这一点,正如我刚才提到的,某种视图和视图模型管理器负责创建/删除视图和视图模型。