2010-08-04 24 views
84

Tengo un ListBox que se une a una colección secundaria en un ViewModel. Los elementos de cuadro de lista están decoradas en un DataTemplate basado en una propiedad en el modelo de vista de los padres:Acceso principal DataContext de DataTemplate

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

me sale el siguiente mensaje de 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') 

Así que si cambio el 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 vincula automáticamente a las propiedades del 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 padre sea una vista de colección o un solo elemento?

Respuesta

135

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í está otro enfoque para obtener acceso al DataContext padre al hacer referencia directamente a un elemento del que conoce el contexto de datos. Utiliza Binding ElementName y funciona bastante bien, siempre y cuando se respete su propia denominación y no tienen pesada reutilización de templates/styles través de 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 se pone 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 el x:Names de elementos primarios no son accesibles desde el interior de un elemento de plantilla, pero ya que no he encontrado ninguna solución mejor, he intentado, y funciona bien.

+0

Tengo este código exacto en mi proyecto pero está perdiendo ViewModels (no se llama al Finalizer, el enlace del comando parece retener DataContext). ¿Puedes verificar que este problema existe para ti también? –

+0

@Juve esto funciona, pero ¿es posible hacer esto para que se active para todos los controles de elementos que implementan la misma plantilla? El nombre es único, entonces necesitaríamos una plantilla separada para cada uno, a menos que me falta algo. – Chris

+1

@Juve sin tener en cuenta mi último, lo tengo que trabajar mediante el uso de relativesource con findancestor y la búsqueda por ancestortype, (así todo lo mismo, excepto no buscar por nombre). En mi caso, repito el uso de ItemsControls cada uno implementando una plantilla para que el mío se vea así: Command = "{Binding RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: Type ItemsControl}}, Path = DataContext.OpenDocumentBtnCommand}" – Chris

37

Puede utilizar RelativeSource para encontrar el elemento padre, como esto -

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

Ver this SO question para más detalles acerca RelativeSource.

+8

Tuve que especificar 'Mode = FindAncestor' para que funcione, pero esto funciona y es mucho mejor en un escenario MVVM porque evita los controles de nomenclatura. 'Binding =" {Binding Path = DataContext.CurveSpeedMustBeSpecified, RelativeSource = {RelativeSource Mode = FindAncestor, AncestorType = {x: Type local: YourParentElementType}}} "' – Aphex

+1

funciona como un charm <3 y no tiene que especificar el modo, .net 4.6.1 – user2475096

16

que estaba buscando cómo hacer algo similar en WPF y me dieron 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 está utilizando un DataTemplate, no se puede acceder directamente al DataContext de los niveles superiores. A continuación, la solución para obtener el DC de la matriz es usar una ruta relativa y filtrar por tipo ItemsControl.

14

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 una ventana de tipo (en este ejemplo) en el visual árbol y cuando lo encuentra, básicamente puede acceder a él es DataContext usando el 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 referes a un sólido estática Name así el tiempo que su alcance puede verlo, que son fine.You debe pegar a su convención de nombres para no romper este método, por supuesto. El enfoque es simple y todo lo que necesita es especificar 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 se utilizará mejor en su propia área de especialidad.

How to: Specify the Binding Source

Encuentra la breve descripción de cada uno, más un enlace a una más detalles uno en la tabla de la parte inferior de la página.

Cuestiones relacionadas