Доступ до батьківського DataContext з DataTemplate


112

У мене є ListBoxприв'язка до дочірньої колекції на ViewModel. Елементи списку списку стильовані у шаблоні даних на основі властивості батьківського ViewModel:

<Style x:Key="curveSpeedNonConstantParameterCell">
   <Style.Triggers>
      <DataTrigger Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, 
          ElementName=someParentElementWithReferenceToRootDataContext}" 
          Value="True">
          <Setter Property="Control.Visibility" Value="Hidden"></Setter>
      </DataTrigger>
   </Style.Triggers>
</Style>

Я отримую таку вихідну помилку:

System.Windows.Data Error: 39 : BindingExpression path error: 
 'CurveSpeedMustBeSpecified' property not found on 
   'object' ''BindingListCollectionView' (HashCode=20467555)'. 
 BindingExpression:Path=DataContext.CurveSpeedMustBeSpecified; 
 DataItem='Grid' (Name='nonConstantCurveParametersGrid');
 target element is 'TextBox' (Name=''); 
 target property is 'NoTarget' (type 'Object')

Отже, якщо я зміню вираз прив'язки до "Path=DataContext.CurrentItem.CurveSpeedMustBeSpecified"нього, він працює, але лише до тих пір, поки контекст даних батьківського керування користувачем є BindingListCollectionView. Це не прийнятно, оскільки решта елементів керування користувача автоматично прив'язується до властивостей CurrentItemувімкнення BindingList.

Як я можу вказати вираз прив'язки всередині стилю, щоб він працював незалежно від батьківського контексту даних, який є переглядом колекції чи окремим елементом?

Відповіді:


161

У мене були проблеми з відносним джерелом у Silverlight. Після пошуку та читання я не знайшов підходящого рішення без використання додаткової бібліотеки прив’язки. Але ось ще один підхід для отримання доступу до батьківського DataContext шляхом прямого посилання на елемент, про який ви знаєте контекст даних. Він використовує Binding ElementNameта працює досить добре, якщо ви поважаєте власне іменування та не потребуєте значного повторного використання templates/ для stylesрізних компонентів:

<ItemsControl x:Name="level1Lister" ItemsSource={Binding MyLevel1List}>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Button Content={Binding MyLevel2Property}
              Command={Binding ElementName=level1Lister,
                       Path=DataContext.MyLevel1Command}
              CommandParameter={Binding MyLevel2Property}>
      </Button>
    <DataTemplate>
  <ItemsControl.ItemTemplate>
</ItemsControl>

Це також працює, якщо ви кладете кнопку в Style/ Template:

<Border.Resources>
  <Style x:Key="buttonStyle" TargetType="Button">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="Button">
          <Button Command={Binding ElementName=level1Lister,
                                   Path=DataContext.MyLevel1Command}
                  CommandParameter={Binding MyLevel2Property}>
               <ContentPresenter/>
          </Button>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</Border.Resources>

<ItemsControl x:Name="level1Lister" ItemsSource={Binding MyLevel1List}>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Button Content="{Binding MyLevel2Property}" 
              Style="{StaticResource buttonStyle}"/>
    <DataTemplate>
  <ItemsControl.ItemTemplate>
</ItemsControl>

Спочатку я подумав, що x:Namesбатьківські елементи не доступні з шаблону, але, оскільки я не знайшов кращого рішення, я просто спробував, і він працює добре.


1
У мене є такий точний код у моєму проекті, але він протікає ViewModels (Finalizer не називається; прив'язка команд, схоже, зберігає DataContext). Чи можете ви переконатися, що ця проблема існує і для вас?
Йоріс Веймар

@Juve це працює, але чи можна це зробити так, щоб він запускав усі елементи управління, які реалізують один і той же шаблон? Ім'я унікальне, тому нам знадобиться окремий шаблон для кожного, якщо я щось не пропускаю.
Кріс

1
@Juve не зважаючи на моє останнє, я змусив його працювати, використовуючи родинні джерела з пошуковою історією та шукаючи по роду предків (так що все одно, за винятком пошуку по імені). У моєму випадку я використовую повторюваною ItemsControls кожен реалізує шаблон так , моє виглядає наступним чином : Command = "{Binding RelativeSource = {RelativeSource FindAncestor, AncestorType = {х: Тип ItemsControl}}, Path = DataContext.OpenDocumentBtnCommand}"
Chris

48

Ви можете використовувати RelativeSourceдля пошуку батьківського елемента, як-от цього -

Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, 
RelativeSource={RelativeSource AncestorType={x:Type local:YourParentElementType}}}"

Дивіться це питання ТА для отримання більш детальної інформації про RelativeSource.


10
Мені довелося вказати, Mode=FindAncestorщоб він працював, але це працює і набагато краще в сценарії MVVM, оскільки він уникає іменування елементів керування. Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:YourParentElementType}}}"
Афекс

1
працює як шарм <3 і не потрібно було вказувати режим, .net 4.6.1
user2475096

30

Відносні джерела проти ElementName

Ці два підходи можуть досягти однакового результату,

Відноснапоширення

Binding="{Binding Path=DataContext.MyBindingProperty, 
          RelativeSource={RelativeSource AncestorType={x:Type Window}}}"

Цей метод шукає керування типом Window (у цьому прикладі) у візуальному дереві, і коли він знаходить, ви в основному можете отримати доступ до нього за DataContextдопомогою Path=DataContext..... Плюси щодо цього методу полягають у тому, що вам не потрібно прив’язуватись до імені, і це свого роду динамічно, проте зміни, внесені до вашого візуального дерева, можуть вплинути на цей метод і, можливо, порушити його.

ElementName

Binding="{Binding Path=DataContext.MyBindingProperty, ElementName=MyMainWindow}

Цей метод відноситься до твердої статики Name, доки ваша область може бачити це, ви все добре. Ви повинні дотримуватися своєї конвенції про іменування, щоб не порушувати цей метод, звичайно. a Name="..."для вашого вікна / UserControl.

Хоча всі три типи ( RelativeSource, Source, ElementName) здатні робити те саме, але згідно з наступною статтею MSDN, кожен з них краще використовувати у власній області спеціальності.

Як: Вкажіть джерело прив'язки

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


18

Я шукав, як зробити щось подібне в WPF, і отримав таке рішення:

<ItemsControl ItemsSource="{Binding MyItems,Mode=OneWay}">
<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel Orientation="Vertical" />
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
    <DataTemplate>
        <RadioButton 
            Content="{Binding}" 
            Command="{Binding Path=DataContext.CustomCommand, 
                        RelativeSource={RelativeSource Mode=FindAncestor,      
                        AncestorType={x:Type ItemsControl}} }"
            CommandParameter="{Binding}" />
    </DataTemplate>
</ItemsControl.ItemTemplate>

Я сподіваюся, що це працює для когось іншого. У мене є контекст даних, який автоматично встановлюється у ItemControls, і цей контекст даних має два властивості: MyItems-що колекція- та одна команда 'CustomCommand'. Через те ItemTemplate, що використовується a DataTemplate, DataContextверхні рівні не доступні безпосередньо. Тоді вирішення для отримання постійного струму батьків є використання відносного шляху та фільтра за ItemsControlтипом.


0

проблема полягає в тому, що DataTemplate не є частиною застосованого до нього елемента.

це означає, що якщо ви прив'язуєте до шаблону, який ви прив'язуєте до чогось, що не має контексту.

однак якщо ви помістите елемент всередині шаблону, тоді, коли цей елемент застосовано до батьківського, він набуває контексту, а прив'язка працює

тому це не вийде

<DataTemplate >
    <DataTemplate.Resources>
        <CollectionViewSource x:Key="projects" Source="{Binding Projects}" >

але це працює чудово

<DataTemplate >
    <GroupBox Header="Projects">
        <GroupBox.Resources>
            <CollectionViewSource x:Key="projects" Source="{Binding Projects}" >

тому що після застосування шаблону даних групове поле розміщується в батьківському і матиме доступ до його контексту

тому все, що вам потрібно зробити, - це видалити стиль із шаблону та перемістити його в елемент шаблону

зауважте, що контекстом для елементів управління є елемент, а не контроль, тобто ComboBoxItem для ComboBox, а не сам ComboBox; у цьому випадку слід використовувати елементи керування ItemContainerStyle замість


0

Так, ви можете вирішити це за допомогою ElementName=Something запропонованого Юве.

АЛЕ!

Якщо дочірній елемент (для якого ви використовуєте такий вид прив'язки) є користувальницьким елементом управління, який використовує те саме ім’я елемента, яке ви вказали в батьківському елементі управління, то прив'язка переходить до неправильного об'єкта !!

Я знаю, що ця публікація не є рішенням, але я вважав, що кожен, хто використовує ElementName у прив'язці, повинен знати це, оскільки це можлива помилка під час виконання.

<UserControl x:Class="MyNiceControl"
             x:Name="TheSameName">
   the content ...
</UserControl>

<UserControl x:Class="AnotherUserControl">
        <ListView x:Name="TheSameName">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <MyNiceControl Width="{Binding DataContext.Width, ElementName=TheSameName}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
</UserControl>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.