Связывание с UserControl DependencyProperty

Я создал UserControl с некоторыми DependencyProperties (в примере здесь используется только одно свойство string). Когда я создаю экземпляр Usercontrol, я могу установить свойство UserControl, и он отображается как ожидалось. Когда я пытаюсь заменить статический текст на Binding, ничего не отображается.

My UserControl выглядит следующим образом:

<User Control x:Class="TestUserControBinding.MyUserControl"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="30" d:DesignWidth="100">
    <Grid>
    <Label Content="{Binding MyText}"/>
  </Grid>
</UserControl>

Код:

namespace TestUserControBinding {

  public partial class MyUserControl : UserControl {
    public MyUserControl() {
      InitializeComponent();
      this.DataContext = this;
    }

    public static readonly DependencyProperty MyTextProperty = 
                   DependencyProperty.Register(
                         "MyText", 
                          typeof(string), 
                          typeof(MyUserControl));

    public string MyText {
      get {
        return (string)GetValue(MyTextProperty);
      }
      set {
        SetValue(MyTextProperty, value);
      }
    }// MyText

  }
}

Когда я пробую это в своем MainWindow, все будет как ожидалось:

<Window x:Class="TestUserControBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestUserControBinding"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <local:MyUserControl MyText="Hello World!"/>
  </StackPanel>
</Window>

Но это не работает:

<Window x:Class="TestUserControBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestUserControBinding"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <local:MyUserControl MyText="{Binding Path=Text}"/>
    <Label Content="{Binding Path=Text}"/>
  </StackPanel>
</Window>

Поведение метки корректно, поэтому нет проблемы с Свойством "Текст"

В чем моя ошибка? Я размышляю часами, но не могу найти ничего, что забыл.

Ответ 1

Со ссылкой на UserControl:

<Label Content="{Binding MyText}"/>

Я не уверен, как настроить текст непосредственно на свойство MyText. Вы должны установить DataContext на UserControl где-нибудь, чтобы это работало.

Независимо от того, эта привязка является проблемой - поскольку я понимаю ваш сценарий, вы не хотите связываться с DataContext UserControl, потому что это не обязательно будет иметь свойство MyText. Вы хотите привязать себя к UserControl самому, а именно созданному DependencyProperty. Для этого вам нужно использовать привязку RelativeSource, как показано ниже:

<Label Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MyUserControl}}, Path=MyText}"/>

Это приведет к перемещению визуального дерева в MyUserControl, а затем найдет там свойство MyText. Он не будет зависеть от DataContext, который изменится в зависимости от места размещения UserControl.

В этом случае local относится к пространству имен, которое необходимо определить в UserControl:

<UserControl x:Class="TestUserControBinding.MyUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:TestUserControBinding"
         ...>

И ваш второй пример должен работать в этот момент.

Ответ 2

Существует неправильное понимание того, как установлены DataContext. Это работает против вас...

В конечном итоге привязка к MyText в пользовательском элементе управления не связана со свойством зависимостей элемента управления MyText, а со страницей DataContext, и свойства MyText не существует.

Позвольте мне объяснить,


Пояснение Когда пользовательский элемент управления помещается на главную страницу, он наследует родительский элемент управления DataContext (StackPanel). Если родительский элемент DataContext не установлен, он будет перемещаться вверх по цепочке к родительскому элементу StackPanel DataContext (ad Infinium), пока не попадет на страницу DataContext (которая в вашем примере установлена и действительна).

При связывании на главной странице, такой как <local:MyUserControl MyText="{Binding Path=Text}"/>, он ищет свойство Text на главных страницах DataContext и устанавливает для свойства зависимости MyText это значение. Это то, что вы ожидаете, и это работает!

Текущее состояние Таким образом, состояние пользовательского элемента управления в вашем коде таково: его DataContext привязан к странице DataContext и свойство зависимости MyText установлено. Но внутренняя контрольная привязка к MyText не работает. Почему?

Пользовательский элемент управления имеет родительский контекст данных, и вы просите элемент управления связать свойство MyText в этом контексте данных. Нет такого свойства, и оно терпит неудачу.


Разрешение

Чтобы привязать к элементу экземпляр и получить значение из свойства MyText, просто поместите имя (имя элемента) в элемент управления, например

<User Control x:Class="TestUserControBinding.MyUserControl"
             ...
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             x:Name="ucMyUserControl"

а затем правильно направьте привязку от значения по умолчанию DataContext и к элементу именованного экземпляра с именем ucMyUserControl. Например:

  <Label Content="{Binding MyText, ElementName=ucMyUserControl }"/>

Обратите внимание, что VS2017/2019 будет действительно интеллигентным ElementName после того, как вы назвали элемент управления.


Побочный эффект простого использования контекста данных родителей

Побочным эффектом исходной ситуации без упомянутого разрешения является то, что вы можете просто привязать привязку пользовательского элемента управления к Text, и она будет работать, поскольку привязка по умолчанию относится к текстовому тексту страницы. Тонкое...

<User Control x:Class="TestUserControBinding.MyUserControl"
             mc:Ignorable="d" 
             d:DesignHeight="30" d:DesignWidth="100">
 <Grid>
    <Label Content="{Binding Text}"/>

Это работает, и технически вы можете удалить свойство зависимости. Если элемент управления не используется за пределами проекта, он может быть спроектирован так, чтобы связываться с другими именованными свойствами, не оказывая вредного влияния.

Тогда все пользовательские элементы управления могут стать дефактными субэлементами главной страницы, как если бы вы просто вставили внутренний XAML на страницу.