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



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

2199   14  

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

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

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

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

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

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

RelativeSource-это расширение разметки, которое используется в частности случаи привязки, когда мы пытаемся привязать свойство данного объекта к другое свойство самого объекта, когда мы пытаемся привязать свойство объекта к другому из его относительных родителей, при привязке a значение свойства зависимостей для части 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}"/>

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

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

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

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

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

  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 to связанный

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

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

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

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

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

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

d. FindAncestor (value=3): когда хотите привязать от родителя control в Visual Tree.

например: связать checkbox на records если a grid,если headercheckbox проверен

<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

обратите внимание в Примере' BEFORE', что Я использую для привязки метода, что код уже был оптимизирован с помощью 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 Class

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

Comments

    Ничего не найдено.