我有一个内置ContentControl的窗口。我想显示多个视图填充UserControls,就像一个包含多个步骤的向导。那些UserControl需要自己的ViewModel,并且可以用Window的ContentControl中的另一个UserControl替换自己。
我想使用MVVM模式,目前正在努力如何从UserControl的ViewModel访问Window的ViewModel。
这是我到目前为止的简化代码。当我在主ViewModel中更改内容时,内容更改没有任何问题:
Window XAML:
<Grid>
<ContentControl Content="{Binding CurrentView}" />
</Grid>
Window ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private object currentView;
public object CurrentView
{
get { return currentView; }
private set
{
currentView = value;
OnPropertyChanged(); // <- Property name is set automatically, so no parameter needed
}
}
public MainWindowViewModel()
{
this.CurrentView = new UserControl1(); // Initial view to show within the ContentControl
}
}
UserControl1 XAML:
<UserControl>
<Grid>
<Button Command="{Binding SwitchToUserControl2}">Switch content</Button>
</Grid>
</UserControl>
现在我有以下“思考问题”:
根据我的理解,从视图中访问两个ViewModel是必要的,但不知道如何实现这一点。
答案 0 :(得分:3)
我不会MainWindowViewModel
创建视图,而是创建您的第一个ViewModel。然后,ViewModel可以使用事件或任何其他机制来通知它应该转换到下一步。
在这种情况下,可以通过将ViewModel映射到相应视图的DataTemplates轻松处理View部分。这里的优点是ViewModel永远不会知道用于呈现它的View,它在MVVM透视图中保持“纯粹”。现在,您的ViewModel正在操纵View层,这是一个MVVM违规。
答案 1 :(得分:1)
Reed的答案是正确的,是解决问题的一种方法,在MainWindow中创建控件的ViewModel,挂钩事件并通过DependencyProperty将ViewModel绑定到用户控件。
要允许ViewModel绑定工作,请确保不要在UserControl的构造函数中或UserControl的Xaml的Root元素上设置DataContext。而是在UserControl的第一个内容元素上设置DataContext。这将允许UserControl的外部绑定继续工作,而UserControl的DataContext就是你想要的。
<UserControl x:Class="StackOverflow._20914503.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:this="clr-namespace:StackOverflow._20914503"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
</Grid>
</UserControl>
关于交换控制进出,Reed再次正确,DataTemplates是可行的方法。
解决通信问题的另一种方法是使用RoutedEvents。在应用程序中创建一个RoutedEvent,因为该事件与ui元素没有真正的关联,所以我们创建一个类来发布路由事件。
public static class EventManagement
{
public static readonly RoutedEvent ChangeViewEvent = EventManager.RegisterRoutedEvent("ChangeView", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl));
}
现在,在每个UserControl中(并且必须在UserControl后面的代码中完成),您可以调用在UIElement类中实现的RaiseEvent。在下面的代码中,我从UserControl的ViewModel中获取一个事件并触发RoutedEvent
private void ViewModel_ChangeEvent(object sender, EventArgs e)
{
RaiseEvent(new RoutedEventArgs(EventManagement.ChangeViewEvent));
}
在我的主窗口中,我不知道RoutedEvent将从何处被触发,我可以像这样添加一个处理程序给Routed事件
public MainWindow()
{
InitializeComponent();
this.AddHandler(EventManagement.ChangeViewEvent, new RoutedEventHandler(SomeControl_ChangeView));
}
private void SomeControl_ChangeView(object sender, RoutedEventArgs routedEventArgs)
{
}
.Net将根据RoutedEvent注册处理事件的路由。
这种方法的优点是分离功能。一切都有效,不知道其他任何事情。您可以使用触发器将UserControl插入MainWindow,它们都可以提升相同的RoutedEvent,MainWindow将全部处理它们。
总结控制流程。 UserControl的ViewModel引发UserControl处理的标准CLR事件。 UserControl引发RoutedEvent。 .Net将事件冒泡到主窗口。主窗口通过其处理程序接收事件。
有几点需要注意。 1. RoutedEvents的默认路由策略是Bubbling(从最低元素,比如按钮,到最高元素,比如MainWindow)。 1.一旦处理程序将事件标记为已处理,事件将停止。 1.路由主要通过Visual Tree完成。
如有必要,我可以发布我的示例的组成部分。
我希望这会有所帮助。