Как использовать привязки WPF с RelativeSource?

Как использовать RelativeSource с привязками WPF и каковы различные варианты использования?

Ответ 1

Если вы хотите привязать к другому свойству объект:

{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}

Если вы хотите получить свойство у предка:

{Binding Path=PathToProperty,
    RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}

Если вы хотите получить свойство на шаблоном родительском (так что вы можете сделать 2-х меток привязки в ControlTemplate)

{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}

или, короче (это работает только для привязок OneWay):

{TemplateBinding Path=PathToProperty}

Ответ 2

Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

Атрибутом по умолчанию RelativeSource является свойство Mode. Здесь приведен полный набор допустимых значений (из MSDN):

  • PreviousData Позволяет связать предыдущий элемент данных (не тот элемент управления, который содержит элемент данных) в списке отображаемых элементов данных.

  • TemplatedParent Относится к элементу, к которому применяется шаблон (в котором существует элемент, привязанный к данным). Это похоже на установку TemplateBindingExtension и применимо только в том случае, если привязка находится внутри шаблона.

  • Self Указывает на элемент, на котором вы устанавливаете привязку, и позволяет привязать одно свойство этого элемента к другому свойству в том же элементе.

  • FindAncestor Относится к предшественнику в родительской цепочке элемента, привязанного к данным. Вы можете использовать это для привязки к предку определенного типа или его подклассов. Это режим, который вы используете, если хотите указать AncestorType и/или AncestorLevel.

Ответ 3

Здесь более визуальное объяснение в контексте архитектуры MVVM:

enter image description here

Ответ 4

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

<Rectangle Fill="Red" Name="rectangle" 
                    Height="100" Stroke="Black" 
                    Canvas.Top="100" Canvas.Left="100"
                    Width="{Binding ElementName=rectangle,
                    Path=Height}"/>

Но в этом случае мы обязаны указывать имя объекта привязки, а именно прямоугольник. Мы можем достичь той же цели по-разному, используя RelativeSource

<Rectangle Fill="Red" Height="100" 
                   Stroke="Black" 
                   Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Height}"/>

В этом случае мы не обязаны упоминать имя объекта привязки, и ширина будет всегда равна высоте при изменении высоты.

Если вы хотите, чтобы параметр Width был половиной высоты, вы можете сделать это, добавив конвертер в расширение разметки Binding. Представьте себе еще один случай:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Parent.ActualWidth}"/>

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

Ответ 5

Бечир Бежауи раскрывает варианты использования RelativeSources в WPF в своей статье здесь:

RelativeSource - это расширение разметки, которое используется, в частности, когда мы пытаемся связать свойство данного объекта с другое свойство самого объекта, когда мы пытаемся связать свойство объекта другому родственнику, когда он связывает значение свойства зависимостей для части XAML в случае пользовательского контроля развития и, наконец, в случае использования дифференциала серии связанные данные. Все эти ситуации выражаются как относительный источник режимы. Я разоблачу все эти случаи один за другим.

  • Режим Self:

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

<Rectangle Fill="Red" Name="rectangle" 
                Height="100" Stroke="Black" 
                Canvas.Top="100" Canvas.Left="100"
                Width="{Binding ElementName=rectangle,
                Path=Height}"/>

Но в этом выше случае мы обязаны указывать имя объект привязки, а именно прямоугольник. Мы можем достичь той же цели иначе используя RelativeSource

<Rectangle Fill="Red" Height="100" 
               Stroke="Black" 
               Width="{Binding RelativeSource={RelativeSource Self},
               Path=Height}"/>

В этом случае мы не обязаны упоминать имя привязки объект, а ширина будет всегда равна высоте, когда высота изменяется.

Если вы хотите, чтобы параметр Width был половиной высоты, тогда вы можете сделать это, добавив конвертер к расширению разметки Binding. Представьте себе еще один случай:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
               Path=Parent.ActualWidth}"/>

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

  • Режим FindAncestor

В этом случае свойство данного элемента будет привязано к одному из его родителей, Корсики. Основное отличие от вышеуказанного случая - факт что вам решать определить тип предка и предка ранга в иерархии, чтобы связать свойство. Кстати, попробуйте поиграть с этот кусок XAML

<Canvas Name="Parent0">
    <Border Name="Parent1"
             Width="{Binding RelativeSource={RelativeSource Self},
             Path=Parent.ActualWidth}"
             Height="{Binding RelativeSource={RelativeSource Self},
             Path=Parent.ActualHeight}">
        <Canvas Name="Parent2">
            <Border Name="Parent3"
            Width="{Binding RelativeSource={RelativeSource Self},
           Path=Parent.ActualWidth}"
           Height="{Binding RelativeSource={RelativeSource Self},
              Path=Parent.ActualHeight}">
               <Canvas Name="Parent4">
               <TextBlock FontSize="16" 
               Margin="5" Text="Display the name of the ancestor"/>
               <TextBlock FontSize="16" 
                 Margin="50" 
            Text="{Binding RelativeSource={RelativeSource  
                       FindAncestor,
                       AncestorType={x:Type Border}, 
                       AncestorLevel=2},Path=Name}" 
                       Width="200"/>
                </Canvas>
            </Border>
        </Canvas>
     </Border>
   </Canvas>

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

Итак, попробуйте изменить AncestorLevel = 2 на AncestorLevel = 1 и посмотреть, что случается. Затем попробуйте изменить тип предка из AncestorType = Border to AncestorType = Canvas и посмотреть, что произойдет.

Отображаемый текст будет изменяться в соответствии с типом предка и уровень. Затем, что произойдет, если уровень предков не подходит для тип предка? Это хороший вопрос, я знаю, что ты собираешься спросите его. Ответ не будет исключен, и ничего не будет отображаться на уровне TextBlock.

  • TemplatedParent

Этот режим позволяет привязать заданное свойство ControlTemplate к свойству управления, к которому применяется ControlTemplate. Хорошо понять проблему здесь является примером ниже

<Window.Resources>
<ControlTemplate x:Key="template">
        <Canvas>
            <Canvas.RenderTransform>
                <RotateTransform Angle="20"/>
                </Canvas.RenderTransform>
            <Ellipse Height="100" Width="150" 
                 Fill="{Binding 
            RelativeSource={RelativeSource TemplatedParent},
            Path=Background}">

              </Ellipse>
            <ContentPresenter Margin="35" 
                  Content="{Binding RelativeSource={RelativeSource  
                  TemplatedParent},Path=Content}"/>
        </Canvas>
    </ControlTemplate>
</Window.Resources>
    <Canvas Name="Parent0">
    <Button   Margin="50" 
              Template="{StaticResource template}" Height="0" 
              Canvas.Left="0" Canvas.Top="0" Width="0">
        <TextBlock FontSize="22">Click me</TextBlock>
    </Button>
 </Canvas>

Если я хочу применить свойства данного элемента управления к его управлению шаблон, то я могу использовать режим TemplatedParent. Существует также аналогично этому расширению разметки, которое является TemplateBinding которая является своего рода короткой рукой первой, но TemplateBinding оценивается во время компиляции при контрасте TemplatedParent, который оценивается сразу после первого запуска. В виде вы можете заметить на следующем рисунке, фоне и содержании применяются от кнопки к шаблону управления.

Ответ 6

Не забывайте TemplatedParent:

<Binding RelativeSource="{RelativeSource TemplatedParent}"/>

или

{Binding RelativeSource={RelativeSource TemplatedParent}}

Ответ 7

В WPF RelativeSource привязка предоставляет три properties для установки:

1. Режим: это enum которое может иметь четыре значения:

а. PreviousData (value=0): присваивает предыдущее значение property привязке

б. TemplatedParent (value=1): используется для определения templates любого элемента управления и для привязки к значению/свойству элемента control.

Например, определите ControlTemplate:

  <ControlTemplate>
        <CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
 </ControlTemplate>

с. Само (value=2): Когда мы хотим связать с self или property себя.

Например: Послать проверенное состояние checkbox как CommandParameter, установив Command на CheckBox

<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />

д. FindAncestor (value=3): когда требуется привязка из родительского элемента control в Visual Tree.

Например: Привязка checkbox в records, если это в grid, если header checkbox проверяется

<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />

2. AncestorType: когда режим - FindAncestor тогда определите тип предка

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}

3. AncestorLevel: когда режим - FindAncestor то какой уровень предка (если в visual tree есть два родителя одного типа)

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}

Выше приведены все варианты использования RelativeSource binding.

Вот ссылка.

Ответ 8

Стоит отметить, что для тех, кто спотыкается об этом мышлении Silverlight:

Silverlight предлагает только уменьшенное подмножество этих команд

Ответ 9

Я создал библиотеку, чтобы упростить синтаксис привязки WPF, в том числе упростить использование RelativeSource. Вот несколько примеров. До:

{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
{Binding Path=Text, ElementName=MyTextBox}

После:

{BindTo PathToProperty}
{BindTo Ancestor.typeOfAncestor.PathToProperty}
{BindTo Template.PathToProperty}
{BindTo #MyTextBox.Text}

Вот пример того, как упрощается привязка метода. До:

// C# code
private ICommand _saveCommand;
public ICommand SaveCommand {
 get {
  if (_saveCommand == null) {
   _saveCommand = new RelayCommand(x => this.SaveObject());
  }
  return _saveCommand;
 }
}

private void SaveObject() {
 // do something
}

// XAML
{Binding Path=SaveCommand}

После:

// C# code
private void SaveObject() {
 // do something
}

// XAML
{BindTo SaveObject()}

Вы можете найти библиотеку здесь: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

Примечание в примере "BEFORE", который я использую для привязки метода, который уже был оптимизирован с помощью RelayCommand, который последний раз я проверил, не является частью WPF. Без этого пример "ПЕРЕД" был бы еще дольше.

Ответ 10

Некоторые полезные кусочки:

Вот как это сделать в основном в коде:

Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);

Я в значительной степени скопировал это из Binding Relative Source в коде Behind.

Кроме того, страница MSDN довольно хороша, поскольку примеры: RelativeSource Class

Ответ 11

Я просто разместил другое решение для доступа к DataContext родительского элемента в Silverlight, который работает для меня. Он использует Binding ElementName.

Ответ 12

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

<Style.Triggers>
    <DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
        <Setter Property="Background">
            <Setter.Value>
                <VisualBrush Stretch="None">
                    <VisualBrush.Visual>
                        <TextBlock Text="We did't find any matching records for your search..." FontSize="16" FontWeight="SemiBold" Foreground="LightCoral"/>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Setter.Value>
        </Setter>
    </DataTrigger>
</Style.Triggers>

Ответ 13

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

Когда вы используете относительный источник с Mode=FindAncestor, привязка должна быть такой:

Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"

Если вы не добавляете DataContext в свой путь, во время выполнения он не может получить свойство.

Ответ 14

Если элемент не является частью визуального дерева, то RelativeSource никогда не будет работать.

В этом случае вам нужно попробовать другую технику, впервые предложенную Томасом Левеском.

У него есть решение в своем блоге под [WPF] Как привязываться к данным, когда DataContext не унаследован. И он работает абсолютно блестяще!

В маловероятном случае, когда его блог не работает, приложение A содержит зеркальную копию его статьи.

Пожалуйста, не комментируйте здесь, пожалуйста, комментарий непосредственно к его сообщению в блоге.

Приложение A: Зеркало сообщения в блоге

Свойство DataContext в WPF чрезвычайно удобно, поскольку оно автоматически унаследовано всеми дочерними элементами элемента, где вы его назначили; поэтому вам не нужно устанавливать его снова для каждого элемента, который вы хотите связать. Однако в некоторых случаях DataContext недоступен: это происходит для элементов, которые не являются частью визуального или логического дерева. Это может быть очень сложно, чтобы привязать свойство к этим элементам...

Давайте проиллюстрируем простым примером: мы хотим отобразить список продуктов в DataGrid. В сетке мы хотим показать или скрыть столбец "Цена", исходя из значения свойства ShowPrice, открытого ViewModel. Очевидным подходом является привязка видимости столбца к свойству ShowPrice:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding ShowPrice,
                Converter={StaticResource visibilityConverter}}"/>

К сожалению, изменение значения ShowPrice не влияет, и столбец всегда видим... почему? Если мы посмотрим на окно вывода в Visual Studio, мы заметим следующую строку:

Ошибка System.Windows.Data: 2: Не удается найти управляющий элемент FrameworkElement или FrameworkContentElement для целевого элемента. BindingExpression: Path = ShowPrice; DataItem = NULL; целевым элементом является "DataGridTextColumn (HashCode = 32685253); target является" Видимость (тип "Видимость" )

Сообщение довольно загадочное, но смысл на самом деле довольно прост: WPF не знает, какой FrameworkElement использовать для получения DataContext, потому что столбец не принадлежит визуальному или логическому дереву DataGrid.

Мы можем попытаться настроить привязку, чтобы получить желаемый результат, например, установив RelativeSource в сам DataGrid:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding DataContext.ShowPrice,
                Converter={StaticResource visibilityConverter},
                RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

Или мы можем добавить привязку CheckBox к ShowPrice и попытаться привязать видимость столбца к свойству IsChecked, указав имя элемента:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding IsChecked,
                Converter={StaticResource visibilityConverter},
                ElementName=chkShowPrice}"/>

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

На данный момент кажется, что единственным жизнеспособным подходом было бы изменить видимость столбца в кодировке, которую мы обычно предпочитаем избегать при использовании шаблона MVVM... Но я не собираюсь так скоро отказаться, по крайней мере пока нет других вариантов рассмотрения 😉

Решение нашей проблемы на самом деле довольно простое и использует класс Freezable. Основной целью этого класса является определение объектов с изменяемым и доступным только для чтения состоянием, но интересной особенностью в нашем случае является то, что объекты Freezable могут наследовать DataContext, даже если они не находятся в визуальном или логическом дереве. Я не знаю точного механизма, который позволяет это поведение, но собирался воспользоваться этим, чтобы сделать нашу работу с привязкой...

Идея состоит в том, чтобы создать класс (я назвал его BindingProxy по причинам, которые должны стать очевидными очень скоро), который наследует Freezable и объявляет свойство зависимостей данных:

public class BindingProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

Затем мы можем объявить экземпляр этого класса в ресурсах DataGrid и связать свойство Data с текущим DataContext:

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

Последний шаг - указать этот объект BindingProxy (легкодоступный с помощью StaticResource) в качестве источника для привязки:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding Data.ShowPrice,
                Converter={StaticResource visibilityConverter},
                Source={StaticResource proxy}}"/>

Обратите внимание, что путь привязки имеет префикс "Данные", так как путь теперь относится к объекту BindingProxy.

Связывание теперь работает корректно, и столбец правильно отображается или скрывается на основе свойства ShowPrice.