0

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.

Kaspatoo
  • 1,223
  • 2
  • 11
  • 28

2 Answers2

3

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.

Jon
  • 2,891
  • 2
  • 17
  • 15
  • ok, this is indeed working. I just thought of something like this which is in fact creating a local variable in the main controls head, store all variables u need in temp-controls and reuse them by x:Name. Thanks for this. But before choosing this as the correct answer, I would like to await to know why all the other ways dont work. Is it a problem with datagrid? At least Ancestor-Search should work, shouldnt it? I probably am switching to a ListView, there it seems to work with one solution from above. – Kaspatoo Dec 05 '19 at 08:49
0

As mentioned in the mainpost (edit 3), it indeed is not working with datagrid. But using DataView can handle that.

Using a ListView with an inner GridView instead of a DataGrid also is possible to solve that. With that the following bindings do work:

<GridViewColumn Header="{Binding HoehenbezugsrahmenDataGridLabel, FallbackValue='HoehenbezugsrahmenDataGridLabel'}" DisplayMemberBinding="{Binding Hoehenbezugsrahmen}" Width="250" />
<GridViewColumn Header="{Binding ElementName=myControl, Path=DataContext.TransformationDataGridLabel, FallbackValue='AnzPunkteGeoMonDataGridLabel'}" DisplayMemberBinding="{Binding Transformation}" Width="250" />
<GridViewColumn Header="{Binding Path=Text, Source={x:Reference tmpHeader}}" DisplayMemberBinding="{Binding AnzPunkteGeoMon}" Width="250" />


<GridViewColumn DisplayMemberBinding="{Binding Ursprungsraumbezug}" Width="250">
    <GridViewColumn.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ElementName=myControl, Path=DataContext.TransformationDataGridLabel, FallbackValue='AnzPunkteGeoMonDataGridLabel'}"/>
        </DataTemplate>
    </GridViewColumn.HeaderTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Ursprungsraumbezug}" Width="250">
    <GridViewColumn.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=Text, Source={x:Reference tmpHeader}}"/>
        </DataTemplate>
    </GridViewColumn.HeaderTemplate>
</GridViewColumn>

All other mentioned bindings do not work, headers keep being empty. Again using Ancestor does not work.

Especially the first approach is the shortest one. Due to this I am going to change from DataGrid to ListView.

Kaspatoo
  • 1,223
  • 2
  • 11
  • 28