Bien Baldonado
Bien Baldonado

Reputation: 87

Set the datacontext of a usercontrol within a usercontrol no matter how many layers of usercontrol

I'm currently creating an application that would test the flexibility of WPF. I have some usercontrols that were intended to be quite transparent when it comes to its ViewModel. What I mean by transparent is that the usercontrol can use any type of ViewModel provided that the ViewModel has all the required properties that would be binded to the controls within that usercontrol. I do this by assigning the ViewModel as datacontext for the certain usercontrol.

This works when there are only two usercontrols (one has access to ViewModelLocator, one requires a datacontext declaration from the former). I don't know what to do when it reaches 3 layers of usercontrols or more. Is there a way to set a datacontext of a usercontrol within a usercontrol which resides in a usercontrol that has access to the ViewModelLocator?

Below are some code that would clarify my question.

This code is the parent usercontrol. It was intended to be used on an application that utilizes MAF. I used a non-static ViewModelLocator to make sure that each plugin instance uses a different instance of ViewModelLocator and because the plugin would not have its own app.xaml (thus no global resource). As you can see, I placed a usercontrol from a separate assembly in the grid, then declared its datacontext so that the said usercontrol would interact with the parent usercontrol's ViewModelLocator.

<UserControl x:Class="TestApp.Inventory.Common.Views.MaterialsNewView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:vm="clr-namespace:TestApp.Inventory.Common.ViewModel"
             xmlns:views="clr-namespace:TestApp.Inventory.Common.Views"
             xmlns:viewsSupp="clr-namespace:TestApp.Supplier.Common.Views;assembly=TestApp.Supplier.Common"
             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" 
             mc:Ignorable="d" Height="607" Width="616" Loaded="UserControl_Loaded">

    <UserControl.Resources>
        <vm:ViewModelLocator x:Key="Locator" />
    </UserControl.Resources>

    <UserControl.DataContext>
        <Binding Path="MaterialsNewView" Source="{StaticResource Locator}" />
    </UserControl.DataContext>

    <Grid>
        <views:SupplierView x:Name="supplierView" Margin="145,306,0,0" HorizontalAlignment="Left" Width="328" Height="258" VerticalAlignment="Top" DataContext="{Binding Source={StaticResource Locator}, Path=SupplierView}" />
    </Grid>
</UserControl>

Then I have the code for the child usercontrol. Like what I said earlier, the child usercontrol was intended to be transparent in terms of the ViewModel. That's why the datacontext should be declared in the parent usercontrol every time.

<UserControl x:Class="TestApp.Supplier.Common.Views.SupplierView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ignore="http://www.ignore.com"
        mc:Ignorable="d ignore" Height="289" Width="352" 
        xmlns:my="clr-namespace:TestApp.Lookup.Common.Views;assembly=TestApp.Lookup.Common">

    <Grid>
        <my:MaterialTypeListView Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Grid, AncestorLevel=1}, Path=ActualHeight}" HorizontalAlignment="Left" Name="materialTypeListView1" VerticalAlignment="Top" Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Grid, AncestorLevel=1}, Path=ActualWidth}" />
    </Grid>
</UserControl>

My problem is when the child usercontrol has its own child usercontrol. I don't know how to declare its datacontext from the parent usercontrol. The objective is no matter how many layers of usercontrol are there, they should interact with the ViewModelLocator of the parent usercontrol.

Upvotes: 2

Views: 8192

Answers (1)

Ralf de Kleine
Ralf de Kleine

Reputation: 11734

Add a DependencyProperty to the UserControl SupplierView named MaterialTypeList.

public partial class SupplierView
{
    public List<string> MaterialTypeList
    {
        get { return (List<string>)GetValue(MaterialTypeListProperty); }
        set { SetValue(MaterialTypeListProperty, value);}
    }

    public static readonly DependencyProperty MaterialTypeListProperty =
            DependencyProperty.Register("MaterialTypeList", typeof(string), typeof(SupplierView),
            new PropertyMetadata(null));
}

Add binding on UserControl MaterialNewView on SupplierView to MaterialTypeList

 <UserControl x:Class="TestApp.Supplier.Common.Views.SupplierView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:ignore="http://www.ignore.com"
            mc:Ignorable="d ignore" Height="289" Width="352" 
            xmlns:my="clr-namespace:TestApp.Lookup.Common.Views;assembly=TestApp.Lookup.Common">

        <Grid>
            <my:MaterialTypeListView 
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}, Path=MaterialTypeList}"
Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Grid, AncestorLevel=1}, Path=ActualHeight}" HorizontalAlignment="Left" Name="materialTypeListView1" VerticalAlignment="Top" Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Grid, AncestorLevel=1}, Path=ActualWidth}" />
        </Grid>
    </UserControl>

Add binding on UserControl SupplierView on MaterialTypeListView to MaterialTypeList.

    <UserControl x:Class="TestApp.Inventory.Common.Views.MaterialsNewView"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:vm="clr-namespace:TestApp.Inventory.Common.ViewModel"
                 xmlns:views="clr-namespace:TestApp.Inventory.Common.Views"
                 xmlns:viewsSupp="clr-namespace:TestApp.Supplier.Common.Views;assembly=TestApp.Supplier.Common"
                 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" 
                 mc:Ignorable="d" Height="607" Width="616" Loaded="UserControl_Loaded">

        <UserControl.Resources>
            <vm:ViewModelLocator x:Key="Locator" />
        </UserControl.Resources>

        <UserControl.DataContext>
            <Binding Path="MaterialsNewView" Source="{StaticResource Locator}" />
        </UserControl.DataContext>

        <Grid>
            <views:SupplierView x:Name="supplierView" Margin="145,306,0,0" 
MaterialTypeList="{Binding [HERE YOUR LIST OF MATERIALTYPE]}"
HorizontalAlignment="Left" Width="328" Height="258" VerticalAlignment="Top" DataContext="{Binding Source={StaticResource Locator}, Path=SupplierView}" />
        </Grid>
    </UserControl>

Upvotes: 2

Related Questions