Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

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

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

{TemplateBinding Path=PathToProperty}

Другие советы

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

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

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

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

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

  • Найти предка Относится к предку в родительской цепочке элемента, привязанного к данным.Вы можете использовать это для привязки к предку определенного типа или его подклассам.Это режим, который вы используете, если хотите указать 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}"/>

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

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

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

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

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

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

  1. Автономный режим:

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

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

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

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

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

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

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

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

  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 отобразит имя данного родителя на относительном уровне источника.

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

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

  1. ШаблонныйРодитель

Этот режим позволяет связывать заданное свойство 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>

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

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

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

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

б.Шаблондродитель(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}" />

д.Найтипредка(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.Тип Предка: когда режим FindAncestor затем определите, какой тип предка

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

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

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

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

Вот ссылочная ссылка.

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

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

или

{Binding RelativeSource={RelativeSource TemplatedParent}}

Стоит отметить, что для тех, кто наткнулся на такое представление о 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 довольно хороша с примерами: Класс относительного источника

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

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

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

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

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

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

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

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

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

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

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

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

Приложение:Зеркало поста в блоге

Свойство 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:Путь=ShowPrice;DataItem = ноль;целевым элементом является DataGridTextColumn (HashCode=32685253);целевое свойство — «Видимость» (тип «Видимость»).

Сообщение довольно загадочное, но смысл на самом деле довольно простой: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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top