Home > Enterprise >  How to Bind a DataContext to ViewModel inside window's DataContext?
How to Bind a DataContext to ViewModel inside window's DataContext?

Time:09-05

I currently have a view that looks like the following:

    <Window>
        <DockPanel>
            <Menu>
                ...
            </Menu>
            <StackPanel>
                <Grid>
                    ...
                </Grid>
                <Separator>
                <Grid>
                    <Frame Source="ADifferentView.xaml" DataContext="{Binding ADifferentViewModel}">
                    <Frame...>
                    <Frame...>
                </Grid>
            </StackPanel>
        </DockPanel>
    </Window>

The ViewModel is declared as:

public ResultsViewModel ADifferentViewModel{ get; set; }

and initialised in the Constructor.

The ADifferentViewModel is a variable in the Window's ViewModel (which is correctly linked and working). However, the binding for the frame to that variable doesn't seem to be linking.

My instinct suggests it's due to needing to get the Window's context. I tried:

DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ADifferentViewModel}"

DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}, Path=ADifferentViewModel}"

However neither of these worked. I also tried changing Window to DockPanel, StackPanel, and Grid for each to no avail.

Any advice would be appreciated, and hopefully this is clear enough. Please ask if you need any details

CodePudding user response:

It seems like the Frame's DataContext does not fall down into its content for some reason.. However, You can take benefit from the Frame's LoadCompleted event to assign the DataContext manually like this:

  1. In .xaml
<Frame 
   x:Name="MyFrame"
   LoadCompleted="MyFrame_OnLoadCompleted"
   Source="ADifferentView.xaml" DataContext="{Binding ADifferentViewModel}"/>
  1. In .xaml.cs
private void MyFrame_OnLoadCompleted(object sender, NavigationEventArgs e)
{
  if (MyFrame.Content is FrameworkElement content)
        content.DataContext = MyFrame.DataContext;
}

CodePudding user response:

Your explanations and demonstrated code are not enough to answer your question. I'll show you some binding examples.
I hope this will be enough for you to understand the cause of your problem.

Example one.
Setting the DataContext and using the TextBox to check its value.

using System.Windows;

namespace Core2022.SO.HarryAdams
{
    public partial class DataContextBindingWindow : Window
    {
        private readonly MainViewModel viewModel;
        public DataContextBindingWindow()
        {
            InitializeComponent();
            viewModel = new MainViewModel();
            viewModel.ADifferentViewModel = new ResultsViewModel();
            viewModel.ADifferentViewModel.Number = 12345678;
            DataContext = viewModel;
        }
    }

    public class ResultsViewModel
    {
        public int Number { get; set; }
    }

    public class MainViewModel
    {
        public ResultsViewModel? ADifferentViewModel { get; set; }
    }
}
<Window x:Class="Core2022.SO.HarryAdams.DataContextBindingWindow"
        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:local="clr-namespace:Core2022.SO.HarryAdams"
        mc:Ignorable="d"
        Title="DataContextBindingWindow" Height="450" Width="800">
    <StackPanel>
        <Frame x:Name="frame" Height="100" DataContext="{Binding ADifferentViewModel}"/>
        <TextBlock Text="{Binding DataContext.Number, ElementName=frame}"/>
    </StackPanel>
</Window>

In this example, you can see that the DataContext binds correctly and displays the correct value.

Second example.
Since the DataContext binding works correctly, I'm assuming that you meant DataContext not Frame, but Page «ADifferentView».

<Page x:Class="Core2022.SO.HarryAdams.ADifferentView"
      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:Core2022.SO.HarryAdams"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="ADifferentView">

    <Grid Background="LightSkyBlue">
        <TextBlock Text="{Binding Number}"/>
    </Grid>
</Page>
        <Frame x:Name="frame" Height="100" DataContext="{Binding ADifferentViewModel}"
               Source="/SO/HarryAdams/ADifferentView.xaml"/>

In this example, the Page TextBox receives an anchoring error. The reason for this is that Page is used to represent the Frame.Content property. And since nothing is set in this property, there is no binding object for the Page DataContext.

At the same time, even an explicit binding of the FindAncestor type to a Frame or Window does not solve the problem:

<Page x:Class="Core2022.SO.HarryAdams.ADifferentView"
      DataContext="{Binding DataContext,
                            RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Frame}}}"

Third example.
Binding Page DataContext to object in the navigation event.

    <StackPanel>
        <Frame x:Name="frame" Height="100" DataContext="{Binding ADifferentViewModel}"
               Source="/SO/HarryAdams/ADifferentView.xaml"
               Navigated="OnNavigated"/>
        <TextBlock Text="{Binding DataContext.Number, ElementName=frame}"/>
    </StackPanel>
    <x:Code>
        <![CDATA[
        private void OnNavigated(object sender, NavigationEventArgs e)
        {
            FrameworkElement element = (FrameworkElement)sender;
            FrameworkElement child = (FrameworkElement)e.Content;

            child.SetBinding(DataContextProperty, new Binding("DataContext") { Source = element});
        }
        ]]>
    </x:Code>
</Window>

Fourth example.
This example is more typical for WPF pages.
They either create their own DataContext or get it from App.

    <Application.Resources>
        <harryadams:MainViewModel x:Key="mainVM">
            <harryadams:MainViewModel.ADifferentViewModel>
                <harryadams:ResultsViewModel Number="1234567"/>
            </harryadams:MainViewModel.ADifferentViewModel>
        </harryadams:MainViewModel>
    </Application.Resources>
<Page x:Class="Core2022.SO.HarryAdams.ADifferentView"
      DataContext="{Binding ADifferentViewModel, Source={StaticResource mainVM}}"

Fifth example.
This example is more typical of WPF MVVM pages. In this case, the anchor object is passed in Frame Content, and the page type is specified in the DataTemplate:

    <StackPanel>
        <Frame x:Name="frame" Height="100" Content="{Binding ADifferentViewModel}">
            <Frame.ContentTemplate>
                <DataTemplate>
                    <local:ADifferentView/>
                </DataTemplate>
            </Frame.ContentTemplate>
        </Frame>
        <TextBlock Text="{Binding Content.Number, ElementName=frame}"/>
    </StackPanel>
</Window>

You can also use the default data templates:

    <Window.Resources>
        <DataTemplate DataType="{x:Type local:ResultsViewModel}">
            <local:ADifferentView/>
        </DataTemplate>
    </Window.Resources>
    <StackPanel>
        <Frame x:Name="frame" Height="100" Content="{Binding ADifferentViewModel}"/>
        <TextBlock Text="{Binding Content.Number, ElementName=frame}"/>
    </StackPanel>
</Window>

Sixth example.
Very often Frame Page is used in the wrong place.
In the vast majority of cases, UserControl should be used instead. In this case, all bindings and passing DataContext start to work in a typical way for most elements.

<UserControl x:Class="Core2022.SO.HarryAdams.ADifferentView"
      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:Core2022.SO.HarryAdams"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="LightSkyBlue">
        <TextBlock Text="{Binding Number}"/>
    </Grid>
</UserControl>
<Window x:Class="Core2022.SO.HarryAdams.DataContextBindingWindow"
        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:local="clr-namespace:Core2022.SO.HarryAdams"
        mc:Ignorable="d"
        Title="DataContextBindingWindow" Height="450" Width="800">
    <StackPanel>
        <local:ADifferentView x:Name="frame" Height="100" DataContext="{Binding ADifferentViewModel}"/>
        <TextBlock Text="{Binding DataContext.Number, ElementName=frame}"/>
    </StackPanel>
</Window>
  • Related