Error de WPF: no se puede encontrar FrameworkElement gobernante para el elemento de destino

87

tengo un DataGrid con una fila que tiene una imagen. Esta imagen está vinculada con un disparador a un cierto estado. Cuando el estado cambia, quiero cambiar la imagen.

La propia plantilla se establece en el HeaderStyle de a DataGridTemplateColumn. Esta plantilla tiene algunos enlaces. El primer día vinculante muestra qué día es y el estado cambia la imagen con un disparador.

Estas propiedades se establecen en un ViewModel.

Propiedades:

public class HeaderItem
{
    public string Day { get; set; }
    public ValidationStatus State { get; set; }
}

this.HeaderItems = new ObservableCollection<HeaderItem>();
for (int i = 1; i < 15; i++)
{
    this.HeaderItems.Add(new HeaderItem()
    {
        Day = i.ToString(),
        State = ValidationStatus.Nieuw,
    });
}

Cuadrícula de datos:

<DataGrid x:Name="PersoneelsPrestatiesDataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
              AutoGenerateColumns="False" SelectionMode="Single" ItemsSource="{Binding CaregiverPerformances}" FrozenColumnCount="1" >

    <DataGridTemplateColumn HeaderStyle="{StaticResource headerCenterAlignment}" Header="{Binding HeaderItems[1]}" Width="50">
        <DataGridTemplateColumn.CellEditingTemplate>
            <DataTemplate>
                <TextBox Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter},Mode=TwoWay}"/>
            </DataTemplate>
        </DataGridTemplateColumn.CellEditingTemplate>

        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock TextAlignment="Center" Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter}}"/>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn> 
</DataGrid>

Datagrid HeaderStyleTemplate:

<Style x:Key="headerCenterAlignment" TargetType="{x:Type DataGridColumnHeader}">
    <Setter Property="HorizontalContentAlignment" Value="Center"/>

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>

                    <TextBlock Grid.Row="0" Text="{Binding Day}" />
                    <Image x:Name="imageValidation" Grid.Row="1" Width="16" Height="16" Source="{StaticResource imgBevestigd}" />
                </Grid>

                <ControlTemplate.Triggers>
                    <MultiDataTrigger >
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding State}" Value="Nieuw"/>                                 
                        </MultiDataTrigger.Conditions>
                        <Setter TargetName="imageValidation" Property="Source" Value="{StaticResource imgGeenStatus}"/>
                    </MultiDataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Ahora, cuando inicio el proyecto, las imágenes no se muestran y aparece este error:

Error de System.Windows.Data: 2: No se puede encontrar FrameworkElement o FrameworkContentElement para el elemento de destino. BindingExpression: Path = HeaderItems [0]; DataItem = null; el elemento de destino es 'DataGridTemplateColumn' (HashCode = 26950454); la propiedad de destino es 'Encabezado' (tipo 'Objeto')

¿Por qué se muestra este error?

KDP
fuente
4
Verifiqué la solución respondida anteriormente, pero no funciona en mi caso. Cuando cambio a otra solución como en el enlace thomaslevesque.com/2011/03/21/… . La idea es la misma que la solución, en lugar de usar FrameworkElement, crearon otra clase. Entonces funciona para mí.
leo 5 de
Para otros que terminan aquí buscando el mensaje de error: la respuesta de esta pregunta similar me ayudó a resolver el problema con bastante facilidad stackoverflow.com/a/18657986/4961688
Tim Pohlmann

Respuestas:

165

Lamentablemente, cualquier DataGridColumnalojado debajo DataGrid.Columnsno es parte del Visualárbol y, por lo tanto, no está conectado al contexto de datos de la cuadrícula de datos. Así que las fijaciones no trabajan con sus propiedades, tales como Visibilityo Headeretc (aunque estas propiedades son las propiedades de dependencia válidos!).

Ahora te preguntarás cómo es eso posible. ¿No se Bindingsupone que su propiedad está vinculada al contexto de datos? Bueno, simplemente es un truco. La unión no funciona realmente. En realidad, son las celdas de la cuadrícula de datos las que copian / clonan este objeto de enlace y lo usan para mostrar su propio contenido.

Entonces, volviendo a resolver su problema, supongo que HeaderItemses una propiedad del objeto que se establece como la DataContextVista principal. Nos podemos conectar el DataContextde la vista para cualquier DataGridColumnmedio de algo que llamamos unaProxyElement .

El siguiente ejemplo ilustra cómo conectar un hijo lógico como ContextMenuo DataGridColumncon la vista principalDataContext

 <Window x:Class="WpfApplicationMultiThreading.Window5"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
         xmlns:vb="http://schemas.microsoft.com/wpf/2008/toolkit"
         Title="Window5" Height="300" Width="300" >
  <Grid x:Name="MyGrid">
    <Grid.Resources>
        <FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
    </Grid.Resources>
    <Grid.DataContext>
         <TextBlock Text="Text Column Header" Tag="Tag Columne Header"/>
    </Grid.DataContext>
    <ContentControl Visibility="Collapsed"
             Content="{StaticResource ProxyElement}"/>
    <vb:DataGrid AutoGenerateColumns="False" x:Name="MyDataGrid">
        <vb:DataGrid.ItemsSource>
            <x:Array Type="{x:Type TextBlock}">
                <TextBlock Text="1" Tag="1.1"/>
                <TextBlock Text="2" Tag="1.2"/>
                <TextBlock Text="3" Tag="2.1"/>
                <TextBlock Text="4" Tag="2.2"/>
            </x:Array>
        </vb:DataGrid.ItemsSource>
        <vb:DataGrid.Columns>
            <vb:DataGridTextColumn
                       Header="{Binding DataContext.Text,
                                     Source={StaticResource ProxyElement}}"
                       Binding="{Binding Text}"/>
            <vb:DataGridTextColumn
                       Header="{Binding DataContext.Tag,
                                     Source={StaticResource ProxyElement}}"
                       Binding="{Binding Tag}"/>
        </vb:DataGrid.Columns>
    </vb:DataGrid>
  </Grid>
</Window>

La vista anterior encontró el mismo error de enlace que ha encontrado si no implementé el truco ProxyElement. El ProxyElement es cualquier FrameworkElement que roba el DataContextde la Vista principal y lo ofrece al hijo lógico como ContextMenuo DataGridColumn. Para eso debe ser alojado como un Contenten un invisibleContentControl que está bajo la misma Vista.

Espero que esto le oriente en la dirección correcta.

WPF-it
fuente
25
Creo que tener que usar este proxy hacky es realmente decepcionante, pero no puedo encontrar otra manera de lograr la misma funcionalidad de otra manera ... Gracias.
Alex Hope O'Connor
2
Esto no funcionó para mí, pero después de leer el artículo de Josh Smith sobre Virtual Branches, intenté agregar el enlace OneWayToSource en mi control raíz para configurar el DataContext "ProxyElement" y funcionó.
jpierson
1
No La solución anterior se adapta muy bien a .NET 3.5.
WPF-it
1
Esta respuesta es antigua, pero sigue siendo útil contra .NET 4.0. Muchas de las respuestas relacionadas con la copia de DataContext en la columna no parecen funcionar. Necesitaba mostrar / ocultar una columna según la propiedad del modelo de vista y esta solución funcionó bien. Y sin ningún código detrás no causará un incidente diplomático en la revisión del código.
James_UK_DEV
3
Para su información, el menú contextual no es el mismo y tiene una solución sin proxy. El menú contextual tiene una propiedad expuesta, Parentmientras que el DataGridTextColumnno expone su DataGridOwnerpropiedad. Vea cómo se logra un enlace de elementos de contexto a través del enlace RelativeSource en mi respuesta Enlace del menú contextual al contexto de datos de la ventana principal
ΩmegaMan
8

Una alternativa un poco más corta a usar StaticResourcecomo en la respuesta aceptada es x:Reference:

<StackPanel>

    <!--Set the DataContext here if you do not want to inherit the parent one-->
    <FrameworkElement x:Name="ProxyElement" Visibility="Collapsed"/>

    <DataGrid>
        <DataGrid.Columns>
            <DataGridTextColumn
                Header="{Binding DataContext.Whatever, Source={x:Reference ProxyElement}}"
                Binding="{Binding ...}" />
        </DataGrid.Columns>
    </DataGrid>

</StackPanel>

La principal ventaja de esto es: si ya tiene un elemento que no es un ancestro de DataGrid (es decir, no es el StackPaneldel ejemplo anterior), puede simplemente darle un nombre y usarlo como el x:Referenceen su lugar, por lo tanto, no necesita definir ningún ficticio FrameworkElementen absoluto.

Si intenta hacer referencia a un antepasado, obtendrá un XamlParseExceptionen tiempo de ejecución debido a una dependencia cíclica.

FernAndr
fuente