简体   繁体   English

从UserControl访问窗口ViewModel

[英]Access Window ViewModel from UserControl

I have a Window with a ContentControl inside. 我有一个带有ContentControl的窗口。 I want to show multiple view-filling UserControls like a wizard with multiple steps. 我想显示多个视图填充用户控件,如具有多个步骤的向导。 Those UserControls need their own ViewModel and the possibility to replace themselves with another UserControl in the Window's ContentControl. 这些UserControl需要它们自己的ViewModel,并有可能用Window的ContentControl中的另一个UserControl替换它们。

I want to work with the MVVM pattern and am currently struggling how to access the Window's ViewModel from the ViewModel of the UserControl. 我想使用MVVM模式,目前正在努力如何从UserControl的ViewModel访问窗口的ViewModel。

Here is the simplified code I have so far. 这是到目前为止的简化代码。 The content changing works without any problem when I change it inside the main ViewModel: 当我在主ViewModel中更改内容时,内容更改可以正常工作:

Window XAML: 视窗XAML:

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

Window ViewModel: 窗口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: UserControl1 XAML:

<UserControl>
    <Grid>
        <Button Command="{Binding SwitchToUserControl2}">Switch content</Button>
    </Grid>
</UserControl>

Now I have the following "thinking problems": 现在,我有以下“思考问题”:

  • If I set the DataContext of the UserControl to its ViewModel, I cannot access the MainWindowViewModel to change the CurrentView Property to UserControl2. 如果将UserControl的DataContext设置为其ViewModel,则无法访问MainWindowViewModel将CurrentView属性更改为UserControl2。
  • If I don't set the DataContext of the UserControl, I automatically inherit the correct ViewModel for Binding the Command to change the Content but haven't instantiated the ViewModel of the UserControl. 如果未设置UserControl的DataContext,我将自动继承正确的ViewModel以绑定命令以更改内容,但尚未实例化UserControl的ViewModel。 I need this because many actions of the UserControl should be handled within it's own ViewModel. 我需要这样做是因为UserControl的许多操作都应在其自己的ViewModel中处理。

In my understanding it is neccessary to have access to both ViewModels from the view but have no clue how to achieve this. 以我的理解,有必要从视图访问两个ViewModel,但是不知道如何实现。

I would not have the MainWindowViewModel create a view , but rather create your first ViewModel. 我不会让MainWindowViewModel创建一个视图 ,而是创建您的第一个ViewModel。 The ViewModel could then use events or any other mechanism to notify that it should transition to the next step. 然后,ViewModel可以使用事件或任何其他机制来通知它应过渡到下一步。

The View portion can be handled easily in that case via DataTemplates that map the ViewModel to the appropriate View. 在这种情况下,可以通过将ViewModel映射到适当的View的DataTemplates轻松处理View部分。 The advantage here is that the ViewModel never knows about the View used to render it, which stays "pure" in an MVVM perspective. 这样做的好处是ViewModel永远不知道用于渲染它的View,它在MVVM透视图中保持“纯净”状态。 Right now, your ViewModel is manipulating the View layer, which is an MVVM violation. 现在,您的ViewModel正在操纵View层,这是MVVM违规。

Reed's answer is correct, and is one way to solve your problem, create the ViewModel of the control in the MainWindow, hook up the events and bind the ViewModel to the user control via a DependencyProperty. Reed的回答是正确的,并且是解决问题的一种方法,可以在MainWindow中创建控件的ViewModel,连接事件,然后通过DependencyProperty将ViewModel绑定到用户控件。

To allow the binding of the ViewModel to work, make sure you do not set the DataContext in the Constructor of the UserControl or on the Root element of the Xaml of the UserControl. 若要使ViewModel绑定起作用,请确保未在UserControl的构造函数中或UserControl Xaml的Root元素上未设置DataContext。 Instead, set the DataContext on the first content element of the UserControl. 而是在UserControl的第一个内容元素上设置DataContext。 This will allow external bindings to the UserControl to continue working while the DataContext of the UserControl is what you want. 这将允许与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>

With regards to swapping controls in and out, Reed is again correct and DataTemplates are the way to go. 关于换入和换出控件,Reed还是正确的,DataTemplates是必经之路。

Another way to solve your communications problems is to use RoutedEvents . 解决通信问题的另一种方法是使用RoutedEvents Create a RoutedEvent with in the application and since the event will have no real association to a ui element, lets create a class to publish the routed event. 在应用程序中创建一个RoutedEvent,由于该事件与ui元素没有真正的关联,因此让我们创建一个类来发布路由事件。

public static class EventManagement
{
    public static readonly RoutedEvent ChangeViewEvent = EventManager.RegisterRoutedEvent("ChangeView", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl));
}

Now, in each of the UserControls (and it must be done within the code behind of a UserControl), you can call RaiseEvent which is implemented in the UIElement class. 现在,在每个UserControls中(必须在UserControl后面的代码中完成),您可以调用RaiseEvent,该事件在UIElement类中实现。 In the following code, I am picking up an event from the ViewModel of my UserControl and firing the RoutedEvent 在下面的代码中,我从UserControl的ViewModel中拾取一个事件,并触发RoutedEvent

    private void ViewModel_ChangeEvent(object sender, EventArgs e)
    {
        RaiseEvent(new RoutedEventArgs(EventManagement.ChangeViewEvent));
    }

In my main window, without know where the RoutedEvent is going to be fired from, I can add a handler to the Routed event like so 在我的主窗口中,不知道将从何处触发RoutedEvent,我可以像这样将处理程序添加到Routed事件中

    public MainWindow()
    {
        InitializeComponent();
        this.AddHandler(EventManagement.ChangeViewEvent, new RoutedEventHandler(SomeControl_ChangeView));
    }

    private void SomeControl_ChangeView(object sender, RoutedEventArgs routedEventArgs)
    {
    }

.Net will handle the routing of the event for you based on the RoutedEvent registration. .Net将根据RoutedEvent注册为您处理事件的路由。

The advantage of this approach is the separation the functionality. 这种方法的优点是功能分离。 Everything works without knowing anything else. 一切正常,无需任何其他操作。 You can use triggers to insert UserControl into the MainWindow, they can all Raise the same RoutedEvent, and the MainWindow will handle them all. 您可以使用触发器将UserControl插入到MainWindow中,它们都可以引发相同的RoutedEvent,并且MainWindow将处理它们。

To Summarise the flow of control. 总结控制流。 The ViewModel of the UserControl raises a standard CLR event that the UserControl handles. UserControl的ViewModel引发UserControl处理的标准CLR事件。 The UserControl Raises the RoutedEvent. UserControl引发RoutedEvent。 .Net Bubbles the event up to the main window. .Net将事件冒泡到主窗口。 The main window receives the event via its handler. 主窗口通过其处理程序接收事件。

A couple of points to note. 需要注意的几点。 1. The default routing strategy for RoutedEvents is Bubbling (from lowest element, say a button, to the highest, say MainWindow). 1. RoutedEvents的默认路由策略是“冒泡”(从最低元素(例如按钮)到最高元素(例如MainWindow))。 1. An event will stop once a handler has flagged the event as Handled. 1.一旦处理程序将事件标记为“已处理”,事件将停止。 1. Routing is mostly done via the Visual Tree . 1.路由主要通过可视树完成

If necessary, I can post the component parts of my example. 如有必要,我可以发布示例的组成部分。

I hope this helps. 我希望这有帮助。

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

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