我认为你肯定是在正确的轨道上,但是这里有一些问题:
首先,您不应该在视图模型中实例化视图。一旦你的视图模型意识到这个视图,那么你就已经打破了这个模式。
var viewTwo = new ViewTwo(new ViewModelTwo());
您的视图创建应该是主视图的责任。事实上,您甚至不必担心创建 视图,因为您可以为此使用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.
}
}
这当然是一个非常简单的基础视图模型,你可以在以后扩展它,这样做的主要原因是允许你创建一个 master 视图模型来负责显示您当前的页面。下面是一个主视图模型的简单示例:
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>
注意:使用布尔到可见性转换器来确定是否显示子内容。
使用这种方法,您不必担心实例化任何视图,因为 DataTemplate 和 ContentPresenter 会为您处理,您只需要担心映射 视图模型到适当的视图。
呸!有很多东西要吸收。
要从这里带走的要点是:
- 您不应该在视图模型中创建视图,请记住,UI 是 UI,Data 是 Data。
- 视图模型的责任在于拥有它们的人,对于父子关系,让父管理子关系而不是视图模型管理器是有意义的。
最后一点是,当然还有不止一种其他方法可以实现这一点,正如我刚才提到的,某种视图和视图模型管理器负责创建/删除视图和视图模型。