Як я можу використовувати WPF прив'язки з RelativeSource?


Відповіді:


783

Якщо ви хочете прив’язати до іншого властивості об’єкта:

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

15
Для цього "{Binding Path = PathToProperty, RelativeSource = {RelativeSource AncestorType = {x: Тип typeOfAncestor}}}", схоже, йому потрібно мати "Mode = FindAncestor", перш ніж "AncestorType"
EdwardM

1
Для якої технології? У WPF це робиться під час встановлення AncestorType.
Абе Хайдебрехт

2
Я згоден з @EdwardM. Коли я пропускаю FindAncestorраніше AncestorType, я отримую таку помилку: "RelativeSource не знаходиться в режимі FindAncestor". (У VS2013, версія для спільноти)
kmote

1
@kmote, це працює для мене з .net 3.0, і я ще раз переконався, що він працює таким чином у kaxaml ... Знову ж, яку технологію ви використовуєте? Процесор XAML відрізняється для WPF / Silverlight / UWP, тому у різних технологій можуть бути різні результати. Ви також згадали про співтовариство VS, тож, можливо, це попередження IDE, але воно працює під час виконання?
Абе Хайдебрехт

6
Просто хотілося б відзначити , що якщо ви хочете прив'язати до властивості в DataContext в RelativeSource , то ви повинні явно вказати його: {Binding Path=DataContext.SomeProperty, RelativeSource=.... Це було дещо несподівано для мене як для новачків, коли я намагався прив’язатись до батьківського DataContext у DataTemplate.
DrEsperanto

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

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

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

  • TemplatedParent Посилається на елемент, до якого застосовується шаблон (у якому існує елемент, пов'язаний з даними). Це аналогічно встановленню TemplateBindingExtension і застосовується лише в тому випадку, якщо Прив'язка знаходиться в шаблоні.

  • Self Посилається на елемент, для якого ви встановлюєте прив'язку, і дозволяє вам прив'язувати одну властивість цього елемента до іншої властивості того ж елемента.

  • FindAncestor Посилається на пращура в батьківському ланцюжку елемента, пов'язаного з даними. Ви можете використовувати це для прив'язки до предка певного типу або його підкласів. Це режим, який ви використовуєте, якщо ви хочете вказати AncestorType та / або AncestorLevel.


128

Ось більш наочне пояснення в контексті архітектури MVVM:

введіть тут опис зображення


19
я щось пропустив? Як ви можете вважати це простим і зрозумілим графіком? 1: поля зліва з значенням насправді не пов'язані з тими, що знаходяться праворуч (чому в ViewModel є файл .cs?) 2: на що вказують ці стрілки DataContext? 3: чому властивість повідомлення відсутня у ViewModel1? і найголовніше 5: Навіщо вам потрібно прив'язувати відносне джерело, щоб дістатися до DataContext Window, якщо TextBlock вже має той самий DataContext? Мені тут явно чогось не вистачає, тож я досить тупа або ця графіка не така проста і зрозуміла, як всі думають! Просвітте мене
Маркус Хюттер

2
@ MarkusHütter На схемі показана група вкладених переглядів та відповідних ViewModels. DataContext View1 є ViewModel1, але він хоче прив'язати до властивості BaseViewModel. Оскільки BaseViewModel є DataContext BaseView (що є Вікном), це можна зробити, знайшовши перший батьківський контейнер, який є Вікном, і взявши його DataContext.
mcargille

6
@MatthewCargille Я дуже добре знаю, що це повинно означати, це не було моєї точки зору. Але поставте себе в позицію того, хто добре не знає XAML та MVVM, і ви побачите, що це не просто і зрозуміло .
Маркус Хюттер

1
Я маю згоду з @ MarkusHütter, до речі, прив’язка ліворуч може бути такою ж простою: {Binding Message}(трохи простіше ...)
florien

@florien Я не думаю, що хоча б для мого використання. У мене є DataTemplate, який повинен посилатися на DataContext MainWindow (мій клас viewmodel), щоб отримати список параметрів для випадаючого меню (завантажений із бази даних). План DataTemplate прив’язаний до об'єкта моделі, який також завантажується з бази даних, але він має лише доступ до обраної опції. Довелося чітко налаштуватись, Path=DataContext.Messageщоб отримати прив’язку до роботи. Це має сенс, враховуючи, що ви можете робити відносні прив’язки до ширини / висоти / тощо. контролю.
DrEsperanto

47

Бешир Bejaoui викриває випадки використання RelativeSources в WPF в своїй статті тут :

RelativeSource - це розширення розмітки, яке використовується в особливих обов'язкових випадках, коли ми намагаємось прив’язати властивість даного об'єкта до іншої властивості самого об'єкта, коли ми намагаємося прив’язати властивість об'єкта до іншого його родича, при прив'язуванні значення властивості залежності до фрагмента XAML у разі розробки користувальницького керування та, нарешті, у випадку використання диференціала серії пов'язаних даних. Усі ці ситуації виражаються як режими відносного джерела. Я викладу всі ці випадки по одному.

  1. Режим власного режиму:

Уявіть собі цей випадок, прямокутник, який ми хочемо, щоб його висота завжди дорівнювала його ширині, скажімо, квадрат. Це можна зробити за допомогою імені елемента

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

  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 = Полотно і побачити, що відбувається.

Виведений текст буде змінюватися залежно від типу та рівня предка. Тоді що станеться, якщо рівень предка не підходить до типу предка? Це гарне запитання, я знаю, що ви ось-ось задасте його. Відповідь немає, винятки будуть викинуті, а на рівні 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, який оцінюється відразу після першого часу запуску. Як ви можете зауважити на малюнку нижче, фон і вміст застосовуються за допомогою кнопки до шаблону керування.


Дуже приємні для мене приклади, використовував Find Ancestor для прив’язки до команди в контексті даних батьків ListView. У батьків є ще 2 ListViewрівні під ним. Це допомогло мені запобігти передачі даних у кожен наступний vm кожного ListViewзDataTemplate
Caleb W.

34

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

c. Я ( value=2): Коли ми хочемо зв’язати себеselfабоpropertyз себе.

Наприклад: Відправити перевіряється стан в checkboxякості під CommandParameterчас установки CommandнаCheckBox

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

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

Наприклад: Прив’яжіть checkboxв, recordsякщо a 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. AncestorLevel: коли режим - цеFindAncestorякий рівень предка (якщо в ньому є два однотипних батьківvisual tree)

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

Вище наведено всі випадки використання для RelativeSource binding.

Ось посилання на посилання .


2
Чудово .. це працювало для мене: <DataGridCheckBoxColumn Header = "Оплачений" Ширина = "35" Прив'язка = "{Прив'язка RelativeSource = {Реляційний режим джерела = FindAncestor, AncestorType = {x: Тип вікна}}, Шлях = DataContext.SelectedBuyer.IsPaid , Mode = OneWay} »/> , де я намагався прив'язати до selectedbuyer.IsPaid власності батьківського вікна
Michael K

21

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

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

або

{Binding RelativeSource={RelativeSource TemplatedParent}}


16

Я створив бібліотеку, щоб спростити синтаксис прив’язки 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. Без цього приклад "ДО ПЕРЕД" був би ще довшим.


2
Такі вправи для тримання рук демонструють слабкість XAML; спосіб занадто складний.
dudeNumber4

16

Деякі корисні шматочки:

Ось як це зробити в основному в коді:

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

Я багато в чому скопіював це з прив’язки відносного джерела в коді позаду .

Крім того, сторінка MSDN досить хороша, що стосується прикладів: Клас відносних ресурсів


5
Моя неясна пам'ять про WPF полягає в тому, що робити прив'язки в коді, мабуть, не найкраще.
Натан Купер

12

Я щойно опублікував ще одне рішення для доступу до DataContext батьківського елемента в Silverlight, який працює для мене. Він використовує Binding ElementName.


10

Я не читав кожної відповіді, але просто хочу додати цю інформацію у випадку відносної команди, пов'язаної з кнопкою.

Коли ви використовуєте відносне джерело з Mode=FindAncestor, прив'язка повинна бути такою:

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

Якщо ви не додаєте DataContext у свій шлях, під час виконання він не може отримати властивість.


9

Це приклад використання цього шаблону, який працював для мене на порожніх мережах даних.

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

6

Якщо елемент не є частиною візуального дерева, то RelativeSource ніколи не працюватиме.

У цьому випадку вам потрібно спробувати іншу техніку, піонером якої став Томас Левеск.

Він має рішення у своєму блозі під [WPF] Як прив’язати до даних, коли DataContext не успадковується . І це працює абсолютно геніально!

У тому випадку, коли його блог не працює, додаток А містить дзеркальну копію своєї статті .

Будь ласка, не коментуйте тут, будь ласка, коментуйте його безпосередньо в блозі .

Додаток 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: Шлях = ShowPrice; DataItem = null; цільовим елементом є «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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.