繁体   English   中英

从UserControl访问窗口ViewModel

[英]Access Window ViewModel from UserControl

我有一个带有ContentControl的窗口。 我想显示多个视图填充用户控件,如具有多个步骤的向导。 这些UserControl需要它们自己的ViewModel,并有可能用Window的ContentControl中的另一个UserControl替换它们。

我想使用MVVM模式,目前正在努力如何从UserControl的ViewModel访问窗口的ViewModel。

这是到目前为止的简化代码。 当我在主ViewModel中更改内容时,内容更改可以正常工作:

视窗XAML:

<Grid>
    <ContentControl Content="{Binding CurrentView}" />
</Grid>

窗口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>

现在,我有以下“思考问题”:

  • 如果将UserControl的DataContext设置为其ViewModel,则无法访问MainWindowViewModel将CurrentView属性更改为UserControl2。
  • 如果未设置UserControl的DataContext,我将自动继承正确的ViewModel以绑定命令以更改内容,但尚未实例化UserControl的ViewModel。 我需要这样做是因为UserControl的许多操作都应在其自己的ViewModel中处理。

以我的理解,有必要从视图访问两个ViewModel,但是不知道如何实现。

我不会让MainWindowViewModel创建一个视图 ,而是创建您的第一个ViewModel。 然后,ViewModel可以使用事件或任何其他机制来通知它应过渡到下一步。

在这种情况下,可以通过将ViewModel映射到适当的View的DataTemplates轻松处理View部分。 这样做的好处是ViewModel永远不知道用于渲染它的View,它在MVVM透视图中保持“纯净”状态。 现在,您的ViewModel正在操纵View层,这是MVVM违规。

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));
}

现在,在每个UserControls中(必须在UserControl后面的代码中完成),您可以调用RaiseEvent,该事件在UIElement类中实现。 在下面的代码中,我从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的默认路由策略是“冒泡”(从最低元素(例如按钮)到最高元素(例如MainWindow))。 1.一旦处理程序将事件标记为“已处理”,事件将停止。 1.路由主要通过可视树完成

如有必要,我可以发布示例的组成部分。

我希望这有帮助。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM