102 votes

Erreur WPF : Impossible de trouver le FrameworkElement qui gouverne pour l'élément cible

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

Le modèle lui-même est défini dans l'onglet HeaderStyle d'un DataGridTemplateColumn . Ce modèle a plusieurs reliures. La première liaison Day indique quel jour nous sommes et State 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,
    });
}

Datagrid :

<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 le FrameworkElement ou le FrameworkContentElement régissant 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 ?

4 votes

J'ai vérifié la solution proposée ci-dessus, mais elle ne fonctionne pas dans mon cas. Lorsque 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.

0 votes

Pour les autres qui ont atterri ici en cherchant le message d'erreur : La réponse à cette question similaire m'a permis de résoudre le problème assez facilement. stackoverflow.com/a/18657986/4961688

182voto

WPF-it Points 10977

Malheureusement, tout DataGridColumn hébergé sous DataGrid.Columns ne fait pas partie de Visual et donc non 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 Visibility ou Header etc. (bien que ces propriétés soient des propriétés de dépendance valides !)

Vous vous demandez peut-être comment cela est possible ? N'est-ce pas leur Binding est censée être liée au contexte des données ? Eh bien, il s'agit tout simplement d'un hack. La liaison ne fonctionne pas vraiment. Ce sont en fait les cellules de la grille de données qui copie / clone cet objet de liaison et l'utiliser pour afficher leurs propres contenus !

Donc maintenant, pour résoudre votre problème, je suppose que HeaderItems est une propriété de l'objet qui est définie comme l'objet DataContext de votre vue parentale. Nous peut connecter le DataContext de la vue à tout DataGridColumn via ce que nous appelons un ProxyElement .

L'exemple ci-dessous illustre comment connecter un enfant logique tel que ContextMenu ou DataGridColumn de la vue parent DataContext

 <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 auriez trouvée si je n'avais pas implémenté le hack ProxyElement. Le ProxyElement est n'importe quel FrameworkElement qui vole le site DataContext de la vue principale et l'offre à l'enfant logique tel que ContextMenu ou DataGridColumn . Pour cela, il doit être hébergé en tant que Content dans un invisible ContentControl qui est sous la même vue.

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

32 votes

Je trouve décevant de devoir utiliser ce truc de proxy, mais je ne trouve pas d'autre moyen d'obtenir la même fonctionnalité autrement... Merci.

2 votes

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 Root pour définir le DataContext "ProxyElement" et cela a fonctionné.

0 votes

@jpierson, utilisez-vous Silverlight ? Parce que cela fonctionne pour moi dans WPF . Pour Silverlight, la mise en œuvre est différente.

19voto

FernAndr Points 423

Une alternative légèrement plus courte à l'utilisation d'un StaticResource comme 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 est le suivant : si vous avez déjà un élément qui est pas l'ancêtre d'un DataGrid (c.-à-d. pas le site StackPanel dans l'exemple ci-dessus), vous pouvez simplement lui donner un nom et l'utiliser en tant qu'objet x:Reference à la place, ce qui permet de ne pas avoir à définir un faux FrameworkElement du tout.

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

0 votes

Intéressant, merci ! Je me demande pourquoi nous devons nous donner tant de mal.

0 votes

Cela produit une erreur d'exécution DataContext property not found on object of type Reference.

0voto

Konstantin S. Points 143

La méthode sans proxy consiste à définir les liens dans le constructeur :

var i = 0;
var converter = new BooleanToVisibilityConverter();
foreach(var column in DataGrid.Columns)
{
    BindingOperations.SetBinding(column, DataGridColumn.VisibilityProperty, new Binding($"Columns[{i++}].IsSelected")
    { 
        Source = ViewModel,
        Converter = converter,
    });
}

0voto

IngoB Points 1046

L'élément Proxy n'a pas fonctionné pour moi, pour une info-bulle. J'ai fait cela pour une grille de données infragistics, mais vous pouvez facilement l'adapter à votre type de grille :

<igDP:ImageField Label="_Invited" Name="Invited">
    <igDP:Field.Settings>
        <igDP:FieldSettings>
            <igDP:FieldSettings.CellValuePresenterStyle>
                <Style TargetType="{x:Type igDP:CellValuePresenter}">
                    <Setter Property="ToolTip">
                        <Setter.Value>
                            <Label Content="{Binding DataItem.InvitationSent, Converter={StaticResource dateTimeConverter}}"/>
                        </Setter.Value>
                    </Setter>
                </Style>
            </igDP:FieldSettings.CellValuePresenterStyle>
        </igDP:FieldSettings>
    </igDP:Field.Settings>
 </igDP:ImageField>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X