Search code examples
c#wpfmvvmdata-bindingdatagrid

C# WPF - Binding to parent-ViewModel within DataGrid not working


I am trying to access a binding to my ViewModels' DependencyProperty from within a DataGrid after it is binded to an itemssource. While from outside the DataGrid Bindings work well and Bindings within the DataGrid work for elements of the Itemssource well too, I am not able to get a binding to my viewModel within the DataGrid. I tried several aproaches.

I want to use that non-working binding to achieve: - load the column-header via a dependency Property from the viewModel - load a bool Value from the ViewModel which determines whether a column is read-only

This is one of my DependencyProperties I have specified in the ViewModel:

public static readonly DependencyProperty FirstColDataGridLabelProperty = DependencyProperty.Register(
            "FirstColDataGridLabel", typeof(string), typeof(MyVm), new PropertyMetadata(default(string)));

In the constructor of the viewlModel some values are assigned to the dependencyProperties.

Edit: Added the bounding grid and UserControl-Tag to the Data-Grid: MyList is a List, containing the data (FirstCol, SecondCol, etc.)

<UserControl x:Class="Aidb.GeoMonPlus.Wpf.Control.EditSpatialReferenceListControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:viewModel="clr-namespace:Aidb.GeoMonPlus.ViewModel.Control;assembly=Aidb.GeoMonPlus.ViewModel"
             Width="Auto"
             Height="Auto"
             MinWidth="400"
             MinHeight="200"
             mc:Ignorable="d" 
             d:DesignHeight="400" d:DesignWidth="1000"
             x:Name="myControl">
    <Grid>
<DataGrid Grid.Column="0" Grid.Row="0"
    ItemsSource="{Binding MyList}"
    CanUserResizeColumns="True"
    CanUserResizeRows="True"
    CanUserSortColumns="True"
    CanUserAddRows="False"
    AlternatingRowBackground="Gainsboro"
    AlternationCount="2"
    AutoGenerateColumns="False"
    BorderBrush="Black"
    GridLinesVisibility="Vertical">
    <DataGrid.Columns>
        <DataGridTextColumn Header="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type viewModel:MyVm}}, Path=FirstColDataGridLabel}" Binding="{Binding FirstCol}" Width="Auto" IsReadOnly="True"/>
        <DataGridTextColumn Header="{Binding SecondColDataGridLabel, FallbackValue='AnzeigeraumbezugDataGridLabel'}" Binding="{Binding (viewModel:EditSpatialReferenceListVm.GCSDataGridLabel) }" Width="Auto" IsReadOnly="True"/>
        <DataGridTextColumn Header="{Binding (viewModel:MyVm.ThirdColDataGridLabel)}" Binding="{Binding ThirdCol}" Width="Auto" IsReadOnly="True"/>
        <DataGridTextColumn Header="{Binding ElementName=myControl, Path=DataContext.FourthColDataGridLabel}" Binding="{Binding FourthCol}" Width="Auto" IsReadOnly="True">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="Margin" Value="0,0,-1,0"></Setter>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>
<Button Grid.Column="0" Grid.Row="1"
    Margin="0,20,0,20"
    Width="75"
    Height="Auto"
    HorizontalAlignment="Left"
    VerticalAlignment="Center"
    Command="{Binding BtnShowSubmit}"
    Content="{Binding BtnShowLabel, FallbackValue='BtnShowLabel'}" />

None of the four tries for a dataBinding for the header-names are working. All headers of the table are empty or the fallback-value is shown.

The binding of the itemssource works, data is shown as expected in the datagrid. The binding in the button to show the buttons text works as expected.

Is this just not possible for DataGrid or how do I access ViewModels' DependecyProperties from inside the dataGrid after it was binded to an itemssource?

Edit2: As it was asked, this is my ViewModels Base Class:

public class ViewModelBase : DependencyObject, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected static void PropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        var vmBase = sender as ViewModelBase;
        vmBase?.OnPropertyChanged(args.Property.Name);
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Edit3:

I found some errormessages:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='MyProject.ViewModel.Control.EditSpatialReferenceListVm', AncestorLevel='1''. BindingExpression:Path=GCSDataGridLabel; DataItem=null; target element is 'DataGridTextColumn' (HashCode=8060118); target property is 'Header' (type 'Object')

or

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=(viewModel:EditSpatialReferenceListVm.GCSDataGridLabel); DataItem=null; target element is 'DataGridTextColumn' (HashCode=16639474); target property is 'Header' (type 'Object')

Searching for this leads to that post: WPF Error: Cannot find governing FrameworkElement for target element Where it is stated out from user 'WPF-it' that:

Sadly any DataGridColumn hosted under DataGrid.Columns is not part of Visual tree and therefore not connected to the data context of the datagrid.

He suggest to use a proxy-control.


Solution

  • I just went through the same thing. There are several methods posted for getting around this and I can see you have tried some, but none of them worked for me either except one. Create a control just above your datagrid and give it a name:

    <TextBlock x:Name="tempcontrol" Text="{Binding HeaderText}" Visibility="Collapsed"/>
    

    and then bind this temp control to the data you want and set it's visibility to collapsed.

    Then bind your DataGridColumn to the temp controls property:

    <DataGridTextColumn Header="{Binding Path=Text, Source={x:Reference tempControl}}"/>
    

    My case was little different because I was binding to the Visibilty property so I used a FrameworkElement instead of a TextBlock for the dummy control, but I think this should work as well.