Erreur WPF: Impossible de trouver FrameworkElement de gouvernance pour l'élément cible

87

J'ai un DataGridavec une ligne qui a une image. Cette image est liée à un déclencheur à un certain état. Lorsque l'état change, je veux changer l'image.

Le modèle lui-même est défini sur le HeaderStylefichier DataGridTemplateColumn. Ce modèle a quelques liaisons. Le premier jour de liaison montre quel jour il est et l'état change l'image avec un déclencheur.

Ces propriétés sont définies dans un ViewModel.

Propriétés:

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,
    });
}

Grille de données:

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

Maintenant, lorsque je démarre le projet, les images ne s'affichent pas et j'obtiens cette erreur:

Erreur System.Windows.Data: 2: Impossible de trouver FrameworkElement ou FrameworkContentElement pour l'élément cible. BindingExpression: Path = HeaderItems [0]; DataItem = null; l'élément cible est 'DataGridTemplateColumn' (HashCode = 26950454); la propriété cible est 'Header' (type 'Object')

Pourquoi cette erreur s'affiche-t-elle?

KDP
la source
4
J'ai vérifié la solution ci-dessus, mais cela ne fonctionne pas dans mon cas. Quand je passe à une autre solution comme dans le lien thomaslevesque.com/2011/03/21/… . L'idée est la même que la solution, au lieu d'utiliser FrameworkElement, ils ont créé une autre classe. Ensuite, cela fonctionne pour moi.
leo5th
Pour les autres qui se retrouvent ici en recherchant le message d'erreur: La réponse à cette question similaire m'a aidé à résoudre le problème assez facilement stackoverflow.com/a/18657986/4961688
Tim Pohlmann

Réponses:

165

Malheureusement, tout DataGridColumnhébergé sous DataGrid.Columnsne fait pas partie de l' Visualarborescence et n'est donc pas connecté au contexte de données de la grille de données. Les liaisons ne fonctionnent donc pas avec leurs propriétés telles que Visibilityou Headeretc (bien que ces propriétés soient des propriétés de dépendance valides!).

Maintenant, vous vous demandez peut-être comment est-ce possible? Leur Bindingpropriété n'est-elle pas censée être liée au contexte de données? Eh bien, c'est tout simplement un hack. La reliure ne fonctionne pas vraiment. Ce sont en fait les cellules datagrid qui copient / clonent cet objet de liaison et l'utilisent pour afficher leur propre contenu!

Revenons maintenant à la résolution de votre problème, je suppose que HeaderItemsc'est une propriété de l'objet qui est définie comme la vue DataContextde votre parent. Nous pouvons connecter la DataContextvue à n'importe quel DataGridColumnvia quelque chose que nous appelons un ProxyElement.

L'exemple ci-dessous illustre comment connecter un enfant logique tel que ContextMenuou DataGridColumnà la vue parentDataContext

 <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 vue ci-dessus a rencontré la même erreur de liaison que vous avez trouvée si je n'avais pas implémenté le hack ProxyElement. Le ProxyElement est n'importe quel FrameworkElement qui vole le DataContextde la vue principale et l'offre à l'enfant logique tel que ContextMenuou DataGridColumn. Pour cela, il doit être hébergé en tant que Contentdans un invisible ContentControlqui se trouve sous la même vue.

J'espère que cela vous guide dans la bonne direction.

WPF-it
la source
25
Je trouve vraiment décevant d'utiliser ce truc de proxy hacky mais je ne trouve pas d'autre moyen d'obtenir les mêmes fonctionnalités sinon ... Merci.
Alex Hope O'Connor
2
Cela n'a pas fonctionné pour moi, mais après avoir lu l'article de Josh Smith sur les branches virtuelles, j'ai essayé d'ajouter la liaison OneWayToSource sur mon contrôle racine pour définir le DataContext "ProxyElement" et cela a fonctionné.
jpierson
1
Nan. La solution ci-dessus convient très bien à .NET 3.5.
WPF-it
1
Cette réponse est ancienne, mais toujours utile contre .NET 4.0. Beaucoup de réponses concernant la copie du DataContext dans la colonne ne semblent pas fonctionner. J'avais besoin d'afficher / masquer une colonne en fonction d'une propriété de modèle de vue et cette solution fonctionnait bien. Et sans code derrière, cela ne provoquera pas d'incident diplomatique dans la révision du code.
James_UK_DEV
3
Le menu contextuel FYI n'est pas le même et a une solution de contournement non proxy. Le menu contextuel a une propriété exposée Parenttandis que le DataGridTextColumnn'expose pas sa DataGridOwnerpropriété. Voyez comment une liaison d'éléments de contexte est accomplie via la liaison RelativeSource dans ma réponse Liaison du menu contextuel au contexte de données de la fenêtre parente
ΩmegaMan
8

Une alternative légèrement plus courte à l'utilisation de a StaticResourcecomme dans la réponse acceptée est 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>

Le principal avantage de ceci est: si vous avez déjà un élément qui n'est pas l'ancêtre d'un DataGrid (c'est-à-dire pas le StackPaneldans l'exemple ci-dessus), vous pouvez simplement lui donner un nom et l'utiliser à la x:Referenceplace, donc pas besoin de définir de mannequin FrameworkElementdu tout.

Si vous essayez de référencer un ancêtre, vous obtiendrez un XamlParseExceptionà l'exécution en raison d'une dépendance cyclique.

FernAndr
la source