-2

I want to bind a DataGrid to a Collection of nested Properties. I tried with the solution from
WPF: Bound datagrid does not update items properties but with no luck so far.

What i get is an empty DataGrid and Console output like that:

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

Also Output from tcc_CollectionChanged (but not PropertyChangedHandler):

30.12.2016 11:11:22, Collection changed

I use the Modern UI (firstfloor) framework. Here is my code for the Window:

public partial class Home : UserControl
{

    private ObservableTable<TableClass> tcc;

    public Home()
    {
        InitializeComponent();
    }

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        TableClass tc;
        List<TableClass> tcl = new List<TableClass>();

        tcc = new ObservableTable<TableClass>();

        tcc.CollectionChanged += tcc_CollectionChanged;
        tcc.ItemPropertyChanged += PropertyChangedHandler;

        for (int i = 0; i < 10; i++)
        {
            tc = new TableClass();

            tc.AGNR.Name = "AGNr";
            tc.AGNR.Value = i.ToString();

            tc.MNR.Name = "MNr";
            tc.MNR.Value = i.ToString() + " M";

            tc.MST.Name = "MSt";
            tc.MST.Value = i % 2 == 0 ? "production" : "stopped";

            tcc.Add(tc);
        }

    }

    static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
    {
        Console.WriteLine(DateTime.Now.ToString() + ", Property changed");
        return;
    }

    static void tcc_CollectionChanged(object sender, EventArgs e)
    {
        Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
        return;
    }
}

Corresponding XAML:

<UserControl x:Class="MuiWpfTestApp.Pages.Home"
         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" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         Loaded="UserControl_Loaded">
<Grid Style="{StaticResource ContentRoot}">
    <ScrollViewer>
        <DataGrid x:Name="tcgrid" ItemsSource="{Binding tcc, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="250" Width="250">
            <DataGridTextColumn Binding="{Binding AGNR.Value, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
                                Header="{Binding AGNR.Key, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
            <DataGridTextColumn Binding="{Binding MNR.Value, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
                                Header="{Binding MNR.Key, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
            <DataGridTextColumn Binding="{Binding MST.Value, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
                                Header="{Binding MST.Key, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
        </DataGrid>
    </ScrollViewer>
</Grid>

INotifyPropertyChanged is implemented in TableClass:

 class TableClass : INotifyPropertyChanged
{
    private KeyValueClass _mnr;
    private KeyValueClass _agnr;
    private KeyValueClass _mst;

    public KeyValueClass MNR
    {
        get
        {
            return _mnr;
        }
        set
        {
            _mnr = value;
            NotifyPropertyChanged("MNR");
        }
    }
    public KeyValueClass AGNR
    {
        get
        {
            return _agnr;
        }
        set
        {
            _agnr = value;
            NotifyPropertyChanged("AGNR");
        }
    }
    public KeyValueClass MST
    {
        get
        {
            return _mst;
        }
        set
        {
            _mst = value;
            NotifyPropertyChanged("MST");
        }
    }

    public TableClass()
    {
        MNR = new KeyValueClass();
        AGNR = new KeyValueClass();
        MST = new KeyValueClass();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

And KeyValueClass is very simple (what do I need here?):

class KeyValueClass
{
    private string _name;
    private string _val;

    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
        }
    }
    public string Value
    {
        get
        {
            return _val;
        }
        set
        {
            _val = value;
        }
    }
}

I need this nested Properties in the Collection, because i can get those data in different languages. So I cannot code the headers of the grid.

Community
  • 1
  • 1
moser
  • 1
  • 2

1 Answers1

0

Since you can only bind to public properties you must define "tcc" as such. Your TableClass and KeyValueClass types must also be public:

public partial class Home : UserControl
{
    public ObservableCollection<TableClass> tcc { get; set; }

    public Home()
    {
        InitializeComponent();
        tcc = new ObservableCollection<TableClass>();
        DataContext = this;
    }

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        TableClass tc;
        List<TableClass> tcl = new List<TableClass>();

        tcc.CollectionChanged += tcc_CollectionChanged;

        for (int i = 0; i < 10; i++)
        {
            tc = new TableClass();

            tc.AGNR.Name = "AGNr";
            tc.AGNR.Value = i.ToString();

            tc.MNR.Name = "MNr";
            tc.MNR.Value = i.ToString() + " M";

            tc.MST.Name = "MSt";
            tc.MST.Value = i % 2 == 0 ? "production" : "stopped";

            tcc.Add(tc);
        }

    }

    static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
    {
        Console.WriteLine(DateTime.Now.ToString() + ", Property changed");
        return;
    }

    static void tcc_CollectionChanged(object sender, EventArgs e)
    {
        Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
        return;
    }
}

public class TableClass : INotifyPropertyChanged
{
    private KeyValueClass _mnr;
    private KeyValueClass _agnr;
    private KeyValueClass _mst;

    public KeyValueClass MNR
    {
        get
        {
            return _mnr;
        }
        set
        {
            _mnr = value;
            NotifyPropertyChanged("MNR");
        }
    }
    public KeyValueClass AGNR
    {
        get
        {
            return _agnr;
        }
        set
        {
            _agnr = value;
            NotifyPropertyChanged("AGNR");
        }
    }
    public KeyValueClass MST
    {
        get
        {
            return _mst;
        }
        set
        {
            _mst = value;
            NotifyPropertyChanged("MST");
        }
    }

    public TableClass()
    {
        MNR = new KeyValueClass();
        AGNR = new KeyValueClass();
        MST = new KeyValueClass();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

public class KeyValueClass
{
    private string _name;
    private string _val;

    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
        }
    }
    public string Value
    {
        get
        {
            return _val;
        }
        set
        {
            _val = value;
        }
    }
}

There is no built-in class ObservableTable so I changed this one to ObservableCollection in the sample code above.

You should also add the columns to the Columns collection of the DataGrid:

<DataGrid x:Name="tcgrid" ItemsSource="{Binding tcc, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                  AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding AGNR.Value, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
                                Header="{Binding AGNR.Key, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
        <DataGridTextColumn Binding="{Binding MNR.Value, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
                                Header="{Binding MNR.Key, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
        <DataGridTextColumn Binding="{Binding MST.Value, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
                                Header="{Binding MST.Key, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
    </DataGrid.Columns>
</DataGrid>

Then it seems to work:

mm8
  • 163,881
  • 10
  • 57
  • 88
  • Hi mm8 thank you for your answer and your tip about making the property "tcc" public. I made it private before because it gave an error without making my types public. I still get the same error _Cannot find governing FrameworkElement or FrameworkContentElement for target element_ though, as you can see in your picture, the headers for the grid are empty. The result for me is still a completely empty table. Observable Table is "taken" from the question I referenced in my question it looks like so – moser Dec 30 '16 at 11:42
  • ...cannot paste code here. What i want from the class is a notification if the properties in KeyValueClass change. – moser Dec 30 '16 at 11:45
  • The binding for the column headers should be AGNR.Name etc. If I try this, still empty headers – moser Dec 30 '16 at 11:49
  • There is only ONE column header for each column and a column header has no DataContext so what property of what class are you expecting to get displayed in the header? One column header maps to several TableClass objects so it makes no sense to bind the header to a property of a TableClass object... – mm8 Dec 30 '16 at 13:26