Erro WPF: Não é possível encontrar FrameworkElement governante para o elemento de destino

87

Eu tenho um DataGridcom uma linha que tem uma imagem. Esta imagem é ligada por um gatilho a um determinado estado. Quando o estado mudar, quero mudar a imagem.

O próprio modelo é definido em HeaderStylede a DataGridTemplateColumn. Este modelo possui algumas ligações. O primeiro dia de ligação mostra que dia é e o estado muda a imagem com um gatilho.

Essas propriedades são definidas em um ViewModel.

Propriedades:

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

Grade de dados:

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

Agora, quando eu inicio o projeto, as imagens não aparecem e recebo este erro:

Erro System.Windows.Data: 2: Não é possível localizar FrameworkElement ou FrameworkContentElement para o elemento de destino. BindingExpression: Path = HeaderItems [0]; DataItem = null; o elemento de destino é 'DataGridTemplateColumn' (HashCode = 26950454); a propriedade de destino é 'Cabeçalho' (tipo 'Objeto')

Por que este erro está sendo mostrado?

KDP
fonte
4
Verifiquei a solução respondida acima, mas não funciona no meu caso. Quando eu mudar para outra solução como no link thomaslevesque.com/2011/03/21/… . A ideia é a mesma que a solução, ao invés de usar FrameworkElement, eles criaram outra classe. Então funciona para mim.
05 de
Para outros que acabam aqui pesquisando a mensagem de erro: A resposta a essa pergunta semelhante me ajudou a resolver o problema com bastante facilidade stackoverflow.com/a/18657986/4961688
Tim Pohlmann

Respostas:

165

Infelizmente, qualquer um DataGridColumnhospedado em DataGrid.Columnsnão faz parte da Visualárvore e, portanto, não está conectado ao contexto de dados do datagrid. Portanto, as ligações não funcionam com suas propriedades como Visibilityou Headeretc (embora essas propriedades sejam propriedades de dependência válidas!).

Agora você pode se perguntar como isso é possível? Sua Bindingpropriedade não deveria estar vinculada ao contexto de dados? Bem, é simplesmente um hack. A ligação realmente não funciona. Na verdade, são as células de datagrid que copiam / clonam esse objeto de ligação e o usam para exibir seus próprios conteúdos!

Portanto, agora, de volta à solução de seu problema, presumo que HeaderItemsseja uma propriedade do objeto definido como o DataContextde sua Visualização pai. Nós podemos conectar o DataContextdo fim de qualquer DataGridColumnvia algo que chamamos de um ProxyElement.

O exemplo abaixo ilustra como conectar um filho lógico, como ContextMenuou DataGridColumnao View paiDataContext

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

A exibição acima encontrou o mesmo erro de ligação que você encontrou se eu não tivesse implementado o hack ProxyElement. O ProxyElement é qualquer FrameworkElement que rouba o DataContextda View principal e o oferece ao filho lógico, como ContextMenuou DataGridColumn. Para isso, ele deve ser hospedado como um Contentinvisível ContentControlque está sob a mesma visualização.

Espero que isso o oriente na direção correta.

WPF-it
fonte
25
Acho que ter que usar esse negócio de proxy hacky realmente decepcionante, mas não consigo encontrar outra maneira de obter a mesma funcionalidade de outra forma ... Obrigado.
Alex Hope O'Connor
2
Isso não funcionou para mim, mas depois de ler o artigo de Josh Smith sobre ramificações virtuais, tentei adicionar a ligação OneWayToSource em meu controle raiz para definir o DataContext "ProxyElement" e funcionou.
jpierson
1
Não. A solução acima se encaixa muito bem no .NET 3.5.
WPF-it
1
Essa resposta é antiga, mas ainda é útil no .NET 4.0. Muitas das respostas envolvendo a cópia do DataContext para a coluna não parecem funcionar. Eu precisava mostrar / ocultar uma coluna dependendo de uma propriedade de modelo de exibição e esta solução funcionou bem. E sem código por trás não causará um incidente diplomático na revisão do código.
James_UK_DEV
3
O menu de contexto FYI não é o mesmo e tem uma solução alternativa não proxy. O menu de contexto tem uma propriedade exposta, Parentenquanto o DataGridTextColumnnão expõe sua DataGridOwnerpropriedade. Veja como uma vinculação de itens de contexto é realizada por meio de vinculação RelativeSource em minha resposta Vinculação do menu de contexto ao Datacontext da janela pai
ΩmegaMan
8

Uma alternativa um pouco mais curta para usar um StaticResourcecomo na resposta aceita é 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>

A principal vantagem disso é: se você já tiver um elemento que não seja um ancestral do DataGrid (ou seja, não o StackPanelno exemplo acima), você pode simplesmente dar um nome a ele e usá-lo como o x:Reference, portanto, não precisa definir nenhum modelo FrameworkElementem absoluto.

Se você tentar fazer referência a um ancestral, obterá um XamlParseExceptionem tempo de execução devido a uma dependência cíclica.

FernAndr
fonte