Błąd WPF: nie można znaleźć regulującego elementu FrameworkElement dla elementu docelowego

88

Mam DataGridz wierszem, który ma obraz. Ten obraz jest powiązany z wyzwalaczem do pewnego stanu. Kiedy stan się zmieni, chcę zmienić obraz.

Sam szablon jest ustawiony na HeaderStylea DataGridTemplateColumn. Ten szablon ma pewne powiązania. Pierwszy wiążący dzień pokazuje, jaki jest dzień, a stan zmienia obraz za pomocą wyzwalacza.

Te właściwości są ustawiane w ViewModel.

Nieruchomości:

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

Siatka danych:

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

Teraz, kiedy uruchamiam projekt, obrazy się nie wyświetlają i pojawia się ten błąd:

Błąd System.Windows.Data: 2: nie można znaleźć regulującego elementu FrameworkElement lub FrameworkContentElement dla elementu docelowego. BindingExpression: Path = HeaderItems [0]; DataItem = null; element docelowy to „DataGridTemplateColumn” (HashCode = 26950454); Właściwość docelowa to „Nagłówek” (typ „Obiekt”)

Dlaczego ten błąd się wyświetla?

KDP
źródło
4
Sprawdziłem powyższe rozwiązanie, ale w moim przypadku nie działa. Kiedy przechodzę na inne rozwiązanie jak w linku thomaslevesque.com/2011/03/21/… . Pomysł jest taki sam jak solution, zamiast używać FrameworkElement, utworzyli inną klasę. Wtedy to działa dla mnie.
leo5
Dla innych, którzy kończą tutaj, wyszukując komunikat o błędzie: Odpowiedź na to podobne pytanie pomogła mi dość łatwo rozwiązać problem stackoverflow.com/a/18657986/4961688
Tim Pohlmann

Odpowiedzi:

166

Niestety żaden DataGridColumnhostowany pod DataGrid.Columnsnie jest częścią Visualdrzewa i dlatego nie jest połączony z kontekstem danych datagrid. Dlatego powiązania nie działają z ich właściwościami, takimi jak Visibilitylub Headeritp. (Chociaż te właściwości są prawidłowymi właściwościami zależności!).

Teraz możesz się zastanawiać, jak to możliwe? Czy ich Bindingwłasność nie powinna być powiązana z kontekstem danych? Cóż, to po prostu hack. Wiązanie tak naprawdę nie działa. W rzeczywistości to komórki datagrid kopiują / klonują ten obiekt wiążący i używają go do wyświetlania własnej zawartości!

A teraz wracając do rozwiązania twojego problemu, zakładam, że HeaderItemsjest to właściwość obiektu, który jest ustawiony jako DataContextwidok nadrzędny. My może połączyć DataContextz widokiem na dowolny DataGridColumnpoprzez coś nazywamyProxyElement .

Poniższy przykład ilustruje sposób łączenia logicznego elementu podrzędnego, takiego jak ContextMenulub DataGridColumnz widokiem nadrzędnymDataContext

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

Powyższy widok napotkał ten sam błąd wiązania, który znalazłeś, jeśli nie zaimplementowałem hackowania ProxyElement. ProxyElement to dowolny element FrameworkElement, który kradnie element DataContextz widoku głównego i oferuje go logicznemu elementowi podrzędnemu, na przykład ContextMenulub DataGridColumn. W tym celu musi być hostowany jako a Contentw niewidzialnym, ContentControlktóry jest pod tym samym Widokiem.

Mam nadzieję, że poprowadzi cię to we właściwym kierunku.

WPF-it
źródło
26
Uważam, że korzystanie z tych hacków proxy jest naprawdę rozczarowujące, ale nie mogę znaleźć innego sposobu na osiągnięcie tej samej funkcjonalności w innym przypadku ... Dziękuję.
Alex Hope O'Connor
2
To nie zadziałało, ale po przeczytaniu artykułu Josha Smitha o wirtualnych gałęziach spróbowałem dodać powiązanie OneWayToSource na mojej kontrolce głównej, aby ustawić „ProxyElement” DataContext i to zadziałało.
jpierson
1
Nie. Powyższe rozwiązanie bardzo dobrze pasuje do .NET 3.5.
WPF-it,
1
Ta odpowiedź jest stara, ale nadal przydatna w stosunku do .NET 4.0. Wiele odpowiedzi dotyczących kopiowania DataContext do kolumny wydaje się nie działać. Musiałem pokazać / ukryć kolumnę w zależności od właściwości modelu widoku i to rozwiązanie działało dobrze. A brak kodu nie spowoduje incydentu dyplomatycznego w przeglądzie kodu.
James_UK_DEV
3
Menu kontekstowe FYI nie jest tym samym i ma obejście inne niż proxy. Menu kontekstowe ma ujawnioną właściwość, Parentpodczas gdy DataGridTextColumnnie ujawnia swojej DataGridOwnerwłaściwości. Zobacz, jak odbywa się wiązanie elementów kontekstu za pośrednictwem powiązania RelativeSource w mojej odpowiedzi Menu kontekstowe Powiązanie z kontekstem danych okna nadrzędnego
ΩmegaMan
8

Nieco krótszą alternatywą dla użycia StaticResourcejak w akceptowanej odpowiedzi jest 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>

Główną zaletą tego jest to, że jeśli masz już element, który nie jest przodkiem DataGrid (tj. Nie jest to StackPanelw powyższym przykładzie), możesz po prostu nadać mu nazwę i użyć go jako x:Referencezamiast niej, stąd nie musisz definiować żadnego atrapy FrameworkElementw ogóle.

Jeśli spróbujesz odwołać się do przodka, otrzymasz XamlParseExceptionw czasie wykonywania z powodu cyklicznej zależności.

FernAndr
źródło