Доступ к родительскому DataContext из DataTemplate
У меня есть ListBox
, который привязывается к дочерней коллекции в ViewModel. Элементы listbox оформлены в виде datatemplate на основе свойства родительского 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"
, оно работает, но только до тех пор, пока datacontext родительского элемента управления пользователя BindingListCollectionView
. Это неприемлемо, так как остальная часть пользовательского элемента управления автоматически связывается со свойствами CurrentItem
на BindingList
.
Как я могу указать выражение привязки внутри стиля, чтобы он работал независимо от того, какой родительский контекст данных является представлением коллекции или отдельным элементом?
Ответы
Ответ 1
У меня были проблемы с относительным источником в Silverlight. После поиска и чтения я не нашел подходящего решения без использования дополнительной библиотеки Binding. Но вот еще один подход к получению доступа к родительскому 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
родительских элементов недоступны из шаблонного элемента, но поскольку я не нашел лучшего решения, я просто попробовал, и он отлично работает.
Ответ 2
Вы можете использовать RelativeSource
, чтобы найти родительский элемент, например:
Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified,
RelativeSource={RelativeSource AncestorType={x:Type local:YourParentElementType}}}"
Подробнее о RelativeSource
см. этот вопрос SO.
Ответ 3
RelativeSource vs. ElementName
Эти два подхода могут достичь одного и того же результата,
RelativeSrouce
Binding="{Binding Path=DataContext.MyBindingProperty,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Этот метод ищет элемент управления типа Window (в этом примере) в визуальном дереве, и когда он находит его, вы в основном можете получить к нему доступ DataContext
используя Path=DataContext....
Преимущества этого метода в том, что вам не нужно привязываться к имени, и это отчасти динамично, однако изменения, внесенные в ваше визуальное дерево, могут повлиять на этот метод и, возможно, нарушить его.
ElementName
Binding="{Binding Path=DataContext.MyBindingProperty, ElementName=MyMainWindow}
Этот метод ссылается на твердое статическое Name
поэтому, пока ваша область видимости его видит, у вас все в порядке. Вы должны придерживаться соглашения об именах, чтобы не нарушать этот метод, конечно. Подход довольно прост, и все, что вам нужно, это укажите Name="..."
для вашего Window/UserControl.
Хотя все три типа (RelativeSource, Source, ElementName
) способны выполнять одно и то же, но в соответствии со следующей статьей MSDN каждый из них лучше использовать в своей области специализации.
Как: указать источник привязки
Найти краткое описание каждого плюс ссылку на более подробную информацию в таблице в нижней части страницы.
Ответ 4
Я искал, как сделать что-то подобное в 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>
Надеюсь, это сработает для кого-то другого. У меня есть контекст данных, который автоматически устанавливается в ItemsControls, и этот контекст данных имеет два свойства: MyItems
, который представляет собой коллекцию, и одну команду "CustomCommand". Из-за ItemTemplate
используется DataTemplate
, верхние уровни DataContext
не доступны напрямую. Тогда обходной путь для получения DC родителя использует относительный путь и фильтр по типу ItemsControl
.
Ответ 5
проблема в том, что DataTemplate не является частью элемента, к которому он применяется.
это означает, что если вы привязываетесь к шаблону, который вы привязываете к тому, что не имеет контекста.
однако, если вы помещаете элемент внутри шаблона, тогда, когда этот элемент применяется к родительскому объекту, он получает контекст, и привязка затем работает
так что это не сработает
<DataTemplate >
<DataTemplate.Resources>
<CollectionViewSource x:Key="projects" Source="{Binding Projects}" >
но это прекрасно работает
<DataTemplate >
<GroupBox Header="Projects">
<GroupBox.Resources>
<CollectionViewSource x:Key="projects" Source="{Binding Projects}" >
потому что после применения набора данных групповой пакет помещается в родительский элемент и будет иметь доступ к его контексту
поэтому все, что вам нужно сделать, это удалить стиль из шаблона и перенести его в элемент в шаблоне
обратите внимание, что контекст для itemcontrol - это элемент, а не элемент управления, т.е. ComboBoxItem для ComboBox, а не сам ComboBox, в этом случае вы должны использовать элементы управления ItemContainerStyle вместо