简体   繁体   中英

Access Window ViewModel from UserControl

I have a Window with a ContentControl inside. 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.

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.

Here is the simplified code I have so far. The content changing works without any problem when I change it inside the main 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>

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.
  • 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. I need this because many actions of the UserControl should be handled within it's own ViewModel.

In my understanding it is neccessary to have access to both ViewModels from the view but have no clue how to achieve this.

I would not have the MainWindowViewModel create a view , but rather create your first ViewModel. The ViewModel could then use events or any other mechanism to notify that it should transition to the next step.

The View portion can be handled easily in that case via DataTemplates that map the ViewModel to the appropriate View. The advantage here is that the ViewModel never knows about the View used to render it, which stays "pure" in an MVVM perspective. Right now, your ViewModel is manipulating the View layer, which is an MVVM violation.

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.

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. Instead, set the DataContext on the first content element of the UserControl. This will allow external bindings to the UserControl to continue working while the DataContext of the UserControl is what you want.

<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.

Another way to solve your communications problems is to use 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.

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. In the following code, I am picking up an event from the ViewModel of my UserControl and firing the 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

    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.

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.

To Summarise the flow of control. The ViewModel of the UserControl raises a standard CLR event that the UserControl handles. The UserControl Raises the RoutedEvent. .Net Bubbles the event up to the main window. 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. An event will stop once a handler has flagged the event as Handled. 1. Routing is mostly done via the Visual Tree .

If necessary, I can post the component parts of my example.

I hope this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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