1
votes

I have a Window and a UserControl. The UserControl creates its own viewmodel like this:

<UserControl x:Class="UiInteraction.UserControl3"
             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:local="clr-namespace:UiInteraction"
             mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">

    <UserControl.DataContext>
        <local:UserControl3Vm/>
    </UserControl.DataContext>

    <StackPanel>
        <TextBlock Text="{Binding String1}"/>
    </StackPanel>

</UserControl>

When the Window instantiates the UserControl I want the viewmodel of the Window to be able to retrieve the viewmodel of the UserControl.

<Window x:Class="UiInteraction.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:UiInteraction"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:MainWindowVm/>
    </Window.DataContext>

    <StackPanel>
        <local:UserControl3 DataContext="{Binding UserControl3Vm, Mode=OneWayToSource}"/>
    </StackPanel>

</Window>

The Window's viewmodel has a publicly settable property of type object. With the DataContext binding I'm expecting that once the UserControl3 is created the value of its DataContext (which is a reference to its viewmodel) would be assigned to the UserControl3Vm property on the Window's viewmodel.

What actually happens is that the Window.UserControl3Vm property setter is called with the value null.

Why is this happening, and what is the best way to achieve what I have in mind?

I know it would be easier to instantiate the viewmodel for the UserControl as a property on the viewmodel for the Window and have the UserControl simply bind onto that, (and that would also minimize the coupling of the views to their viewmodels). But where I work they are a bit nutty and prefer view first MVVM instead of viewmodel first, so I'm looking for the most decoupled way to enable viewmodels to collaberate effectively when the viewmodels are instead created by their views.

2

2 Answers

2
votes

I don't think it will work to use a OneWayToSource binding without some code-behind.

Initially, your UserControl.DataContext is set to an instance of UserControl3vm, however you are replacing UserControl3vm with a Binding, so your original UserControl3vm is no longer referenced anywhere.

For a OneWayToSource binding to work, you'd have to first set the DataContext to your OneWayToSource binding, and then set the binding's Source to a new instance of UserControl3vm from inside your UserControl.

If I remember correctly, you can obtain the binding using BindingOperations.GetBindingExpression, and update it's DataItem property. You can't simply set the UserControl.DataContext because it would overwrite your OneWayToSource binding.

Personally I would just do it in the code-behind the Loaded event

If they're insisting on View-First MVVM, then the View is controlling the application flow and I don't see any reason why you should keep application logic out of the View's code-behind.

So just set your Window.DataContext.UserControl3Vm property to UserControl3.DataContext in the Loaded event :)

<Window x:Name="MyWindow"
        Loaded="MyWindow_Loaded" 
        ... >
    <StackPanel>
        <local:UserControl3 x:Name="MyUserControl" />
    </StackPanel>
</Window>

void MyWindow_Loaded(object sender, EventArgs e)
{
    ((MainWindowVm)MyWindow.DataContext).UserControl3Vm 
        = MyUserControl.DataContext;
}
0
votes

This is possible in XAML using some workaround(to hack access of DataContext of host element). The approach is mentioned here. It uses Freezables.

The XAML is

<Window x:Class="VM2VMBindingInXaml.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vw="clr-namespace:VM2VMBindingInXaml.View"
        xmlns:vm="clr-namespace:VM2VMBindingInXaml.ViewModel"
        Title="MainWindow" Height="350" Width="525" >
    <Window.Resources>
             <vm:UserControl1ViewModel x:Key="childVM"></vm:UserControl1ViewModel>
        <vm:DataResource x:Key="childVmBinding" BindingTarget="{Binding ElementName=child, Path=DataContext}"/>
    </Window.Resources>
    <Window.DataContext>
        <vm:MainWindowViewModel x:Name="mainViewModel" >
            <vm:MainWindowViewModel.ChildViewModel>
                <vm:DataResourceBinding DataResource="{StaticResource childVmBinding}">

                </vm:DataResourceBinding>
            </vm:MainWindowViewModel.ChildViewModel>
        </vm:MainWindowViewModel>
    </Window.DataContext>
    <Grid>
        <vw:UserControl1 x:Name="child" DataContext="{Binding Source={StaticResource ResourceKey=childVM}}">
        </vw:UserControl1>
    </Grid>
</Window>