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

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

14 ответов


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

{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}

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

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

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

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

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

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


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

enter image description here


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

<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}"/>

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

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

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

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


Bechir Bejaoui предоставляет варианты использования RelativeSources в WPF в его статью здесь:

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

  1. 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}"/>

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

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

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

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

  1. Режим 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 с тех встроены в пределах ряда границ и элементов canvas те представляют их иерархические родители. Второй TextBlock отобразит имя данный родитель на относительном уровне источника.

поэтому попробуйте изменить AncestorLevel=2 на AncestorLevel=1 и посмотрите, что происходит. Затем попробуйте изменить тип предка с AncestorType=граница с AncestorType=холст и посмотреть, что происходит.

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

  1. 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, который оценивается сразу после первого времени выполнения. Как вы можете отметить на рисунке ниже, фон и содержание применяются изнутри кнопки к шаблону управления.


Не забудьте TemplatedParent:

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

или

{Binding RelativeSource={RelativeSource TemplatedParent}}

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

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

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

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

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

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

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

например: отправить проверенное состояние checkbox as CommandParameter при установке Command on CheckBox

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

d. 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.

вот ссылка.


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

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


Я создал библиотеку для упрощения синтаксиса привязки 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

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


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

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

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

Я в основном скопировал это из привязка относительного источника в коде за.

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


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


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

<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>

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

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

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

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


если элемент не является частью визуального дерева, 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, мы заметим следующую строку: