Доступ к родительскому DataContext из DataTemplate
У меня есть 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
автоматически.
Как указать выражение привязки внутри стиля, чтобы оно работало независимо от того, является ли родительский контекст данных представлением коллекции или отдельным элементом?
7 ответов
У меня были проблемы с относительным источником в 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
родительские элементы не доступны из шаблона элемента, но так как я не нашел лучшего решения, я просто попытался, и он работает нормально.
Ты можешь использовать RelativeSource
найти родительский элемент, как это -
Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified,
RelativeSource={RelativeSource AncestorType={x:Type local:YourParentElementType}}}"
Посмотрите этот ТАК вопрос для более подробной информации о RelativeSource
,
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="..."
для вашего окна /UserControl.
Хотя все три типа (RelativeSource, Source, ElementName
) способны делать то же самое, но, согласно следующей статье MSDN, лучше использовать каждую из них в своей области.
Как: указать источник привязки
Найдите краткое описание каждого плюс ссылку на более подробную информацию в таблице внизу страницы.
Я искал, как сделать что-то подобное в 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
тип.
Да, вы можете решить эту проблему с помощью 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>
Также можно сделать через.
. С помощью этого оператора вы можете получить доступ кSource
. здесь
При необходимости путь с точкой (.) можно использовать для привязки к текущему источнику. Например, Text="{Binding}" эквивалентен Text="{Binding Path=.}".
Например, мне нравится делать так (поместить внутрьTag
а затем получить доступ к родительскому DataContext
):
ResourceDictionary.xaml:
<Style x:Key="SomeControl" TargetType="ItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Tag="{Binding .}">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag.DataContext.SomeName}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml:
<ItemsControl Style="{StaticResource SomeControl}"/>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
//...
SomeName = "Your value";
}
public string SomeName { get; set; }
}
Проблема в том, что 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