简体   繁体   中英

WPF UserControl doesn't inherit parent DataContext

I'm trying to develop a reusable UserControl but running into problems with binding. I've created a smaller application to test it but unable to sort it out, or at least understand why it's not working how I expect.

Code is below. What I would expect is the instance of the TestUserControl I put on MainWindow.xaml would inherit the DataContext there just like the TextBlock bellow it. Instead it's DataContext seems to be null. Is there a reason the DataContext doesn't pass down? Do I have to set it automatically?

MainWindow.xaml

<Window x:Class="WpfTestApp.MainWindow"
        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:WpfTestApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel Orientation="Vertical">
        <local:TestUserControl TextFromParent="{Binding SomeText}" />
        <TextBlock Name="TestTextBlock" Text="{Binding SomeText}" />

    </StackPanel>
</Window>

MainWindow.xaml.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;

namespace WpfTestApp
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string _someText;

        public MainWindow()
        {
            DataContext = this;

            InitializeComponent();

            SomeText = "New Text!";
        }

        public string SomeText
        {
            get { return _someText; }
            set
            {
                _someText = value;
                NotifyOnPropertyChanged();
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyOnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

TestUserControl.xaml

<UserControl x:Class="WpfTestApp.TestUserControl"
             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:WpfTestApp"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <TextBlock Name="TheTextBlock" Text="{Binding TextFromParent}" />

    </Grid>
</UserControl>

TestUserControl.xaml.cs

using System.Windows;
using System.Windows.Controls;

namespace WpfTestApp
{
    public partial class TestUserControl : UserControl
    {
        public TestUserControl()
        {
            InitializeComponent();
        }

        public string TextFromParent
        {
            get { return (string)GetValue(TextFromParentProperty); }
            set { SetValue(TextFromParentProperty, value); }
        }

        public static readonly DependencyProperty TextFromParentProperty = DependencyProperty.Register(
            "TextFromParent", typeof(string), typeof(TestUserControl), new PropertyMetadata());
    }
}

Program looks like the following when run, first text is blank followed by TextBlock with working binding: 在此处输入图片说明

The UserControl is actually inheriting the DataContext from its parent element. There is however no TextFromParent property in that DataContext (because it is the MainWindow instance).

The Binding in the UserControl's XAML is supposed to bind to a property of the UserControl itself, not one of the current DataContext. Hence it must use the UserControl instance as source object:

<TextBlock Text="{Binding TextFromParent,
                  RelativeSource={RelativeSource AncestorType=UserControl}}" />

Setting the UserControl's DataContext to itself is not an option, because it prevents that a DataContext value is inherited from the parent element of the control.

You may however set the DataContext of the root element in the UserControl's XAML to avoid setting RelativeSource on potentially many Bindings:

<UserControl ...>
    <Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
        <TextBlock Text="{Binding TextFromParent}" />
    </Grid>
</UserControl>

Try this and you don't need to use any RelativeSource in binding:

<Grid Name="GrdContent">
    <TextBlock Name="TheTextBlock" Text="{Binding TextFromParent}" />

</Grid>

...

public MainWindow()
        {
            GrdContent.DataContext = this;

            InitializeComponent();

            SomeText = "New Text!";
        }

I have tried to implement the solution stated above (using the same code) and cannot get it to work.

MainWindow XAML and Code Behind:

<Grid>
    <StackPanel Orientation="Horizontal">
        <StackPanel Orientation="Vertical">
            <TextBlock Name="TestTextBlock1" Text="{Binding SomeText}" />
            <local:TestUserControl DataContext="{Binding SomeText}" />
            <TextBlock Name="TestTextBlock3" Text="{Binding SomeText}" />
        </StackPanel>
    </StackPanel>
</Grid>



public partial class MainWindow : Window, INotifyPropertyChanged
{
    private string _someText;

    public MainWindow()
    {
        DataContext = this;

        InitializeComponent();

        SomeText = "Hello World!";
    }
    public string SomeText
    {
        get { return _someText; }
        set
        {
            _someText = value;
            NotifyOnPropertyChanged();
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyOnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

My User Control

<UserControl x:Class="ControlDataContext.TestUserControl"
         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:ControlDataContext"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
    <TextBlock Text="{Binding TextFromParent}" />
</Grid>

Code Behind User Control

public partial class TestUserControl : UserControl
{
    public TestUserControl()
    {
        InitializeComponent();
    }
    public string TextFromParent
    {
        get => (string)GetValue(TextFromParentProperty);
        set => SetValue(TextFromParentProperty, value);
    }

    public static readonly DependencyProperty TextFromParentProperty = DependencyProperty.Register(
        "TextFromParent", typeof(string), typeof(TestUserControl), new PropertyMetadata());
}

Picture showing the missing line with yellow highlight

结果

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