wpf - Acceda al DataContext principal desde DataTemplate
xaml data-binding (5)
Tengo un ListBox
que se une a una colección secundaria en un ViewModel. Los elementos de la lista se diseñan en una plantilla de datos basada en una propiedad en ViewModel principal:
<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>
Aparece el siguiente error de salida:
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'')
Entonces, si cambio la expresión de enlace a "Path=DataContext.CurrentItem.CurveSpeedMustBeSpecified"
funciona, pero solo mientras el contexto de datos del control de usuario principal sea BindingListCollectionView
. Esto no es aceptable porque el resto del control de usuario se une automáticamente a las propiedades del elemento CurrentItem
en el BindingList
.
¿Cómo puedo especificar la expresión de enlace dentro del estilo para que funcione independientemente de que el contexto de datos principal sea una vista de colección o un solo elemento?
Estaba buscando cómo hacer algo similar en WPF y obtuve esta solución:
<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>
Espero que esto funcione para otra persona. Tengo un contexto de datos que se establece automáticamente en ItemsControls, y este contexto de datos tiene dos propiedades: MyItems
que es una colección, y un comando ''CustomCommand''. Debido a que ItemTemplate
usa una DataTemplate
, no se puede acceder directamente al DataContext
de niveles superiores. A continuación, la solución alternativa para obtener el DC del elemento primario es utilizar una ruta relativa y filtrar por tipo de tipo ItemsControl
de ItemsControl
.
Puede usar RelativeSource
para encontrar el elemento padre, como este -
Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified,
RelativeSource={RelativeSource AncestorType={x:Type local:YourParentElementType}}}"
Vea esta pregunta ASÍ para más detalles sobre RelativeSource
.
Tuve problemas con la fuente relativa en Silverlight. Después de buscar y leer, no encontré una solución adecuada sin usar una biblioteca de enlace adicional. Pero, aquí hay otro enfoque para obtener acceso al DataContext principal al hacer referencia directamente a un elemento del que conoce el contexto de datos. Utiliza Binding ElementName
y funciona bastante bien, siempre que respetes tu propio nombre y no tengas una gran reutilización de templates
/ styles
todos los componentes:
<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>
Esto también funciona si pones el botón en 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>
Al principio pensé que x:Names
de los elementos principales no son accesibles desde un elemento con plantilla, pero como no encontré una solución mejor, lo intenté y funciona bien.
el problema es que un DataTemplate no es parte de un elemento aplicado a él.
esto significa que si se vincula a la plantilla, se vincula a algo que no tiene contexto.
sin embargo, si coloca un elemento dentro de la plantilla, cuando ese elemento se aplica a la matriz, gana un contexto y la vinculación funciona
entonces esto no funcionará
<DataTemplate >
<DataTemplate.Resources>
<CollectionViewSource x:Key="projects" Source="{Binding Projects}" >
pero esto funciona a la perfección
<DataTemplate >
<GroupBox Header="Projects">
<GroupBox.Resources>
<CollectionViewSource x:Key="projects" Source="{Binding Projects}" >
porque después de aplicar la plantilla de datos, la casilla de grupo se coloca en la matriz y tendrá acceso a su contexto
entonces todo lo que tiene que hacer es eliminar el estilo de la plantilla y moverlo a un elemento en la plantilla
tenga en cuenta que el contexto para un control de elementos es el elemento, no el control, es decir, ComboBoxItem para ComboBox, no el ComboBox mismo, en cuyo caso debe usar los controles ItemContainerStyle en su lugar
RelativeSource vs. ElementName
Estos dos enfoques pueden lograr el mismo resultado,
RelativeSrouce
Binding="{Binding Path=DataContext.MyBindingProperty,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Este método busca un control de un tipo Window (en este ejemplo) en el árbol visual y cuando lo encuentra, básicamente puede acceder a DataContext
usando Path=DataContext....
Los profesionales sobre este método es que no necesita estar vinculado a un nombre y es algo dinámico, sin embargo, los cambios realizados en su árbol visual pueden afectar este método y posiblemente romperlo.
ElementName
Binding="{Binding Path=DataContext.MyBindingProperty, ElementName=MyMainWindow}
Este método se refiere a un Name
estático sólido, por lo que siempre y cuando su alcance lo pueda ver, está bien. Debe seguir su convención de nombres para no romper este método, por supuesto. El enfoque es simple y todo lo que necesita es especifique un Name="..."
para su Ventana / UserControl.
Aunque los tres tipos ( RelativeSource, Source, ElementName
) son capaces de hacer lo mismo, pero de acuerdo con el siguiente artículo de MSDN, cada uno será mejor utilizado en su propia área de especialidad.
Cómo: especificar la fuente de enlace
Encuentre la breve descripción de cada uno más un enlace a uno más detalles uno en la tabla en la parte inferior de la página.