Search code examples
wpfbindingdatagridtwo-way

WPF: How to make DataGrid binding with dynamic columns editable?


I need to bind some data to a DataGrid with variable number of columns. I made it work using following code:

int n = 0;
foreach (string title in TitleList)
{
    DataGridTextColumn col = new DataGridTextColumn();
    col.Header = title;
    Binding binding = new Binding(string.Format("DataList[{0}]", n++));
    binding.Mode = BindingMode.TwoWay;
    col.Binding = binding;
    grid.Columns.Add(col);
}

where DataList is declared as:

public ObservableCollection<double> DataList { get; set; }

and TitleList is declared as:

public ObservableCollection<string> TitleList { get; set; }

The problem is that, even though I specified TwoWay binding, it is really one-way. When I click a cell to try to edit, I got an exception "'EditItem' is not allowed for this view". Did I just miss something in the binding expression?

P.S. I found an article from Deborah "Populating a DataGrid with Dynamic Columns in a Silverlight Application using MVVM". However, I had hard time to make it work for my case (specifically, I can't make the header binding work). Even if it worked, I'm still facing issues like inconsistent cell styles. That's why I'm wondering if I could make my above code work - with a little tweak?

EDIT: I found another post which might be related to my problem: Implicit Two Way binding. It looks if you bind to a list of string to a TextBox using

<TextBox Text="{Binding}"/>

You will get an error like "Two-way binding requires Path or XPath". But the problem can easily be fixed by using

<TextBox Text="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"/>

or

<TextBox Text="{Binding .}"/>

Can anybody give me a hint if my problem can be solved in a similar way?


Solution

  • Do you bind to an indexer?. can you show us how your DataList Property looks like?

    i did the same a while ago with an indexed property.

     public SomeObjectWithIndexer DataList
     {get; set;}
    
    
     public class SomeObjectWithIndexer 
     {
          public string this
          {
              get { ... }
              set { ... }//<-- you need this one for TwoWay
          }
     }
    

    EDIT: the reason that you cant edit your Property, is that you try to edit a "double field". one workaround would be to wrap your double into a class with INotifyPropertyChanged.

    public class DataListItem
    {
        public double MyValue { get; set;}//with OnPropertyChanged() and stuff
    }
    

    then you can use a

    ObservableCollection<DataListItem>
    

    and you can edit your value. the question wether the index are always the same stay still around :)

    Binding binding = new Binding(string.Format("DataList[{0}].MyValue", n++));
    

    EDIT2: working example: just to show twoway is working

    public class DataItem
    {
        public string Name { get; set; }
        public ObservableCollection<DataListItem> DataList { get; set; }
    
        public DataItem()
        {
            this.DataList = new ObservableCollection<DataListItem>();
        }
    }
    

    Wrapper for double:

    public class DataListItem
    {
        private double myValue;
        public double MyValue
        {
            get { return myValue; }
            set { myValue = value; }//<-- set breakpoint here to see that edit is working
        }
    }
    

    usercontrol with a datagrid

    <UserControl x:Class="WpfStackoverflow.IndexCollectionDataGrid"
             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">
    <DataGrid ItemsSource="{Binding MyList}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" />
            <DataGridTextColumn Header="Index1" Binding="{Binding Path=DataList[0].MyValue, Mode=TwoWay}" />
            <DataGridTextColumn Header="Index2" Binding="{Binding Path=DataList[1].MyValue, Mode=TwoWay}" />
        </DataGrid.Columns>
    </DataGrid>
    </UserControl>
    

    .cs

    public partial class IndexCollectionDataGrid : UserControl
    {
        public IndexCollectionDataGrid()
        {
            InitializeComponent();
            this.MyList = new ObservableCollection<DataItem>();
    
            var m1 = new DataItem() {Name = "test1"};
            m1.DataList.Add(new DataListItem() { MyValue = 10 });
            m1.DataList.Add(new DataListItem() { MyValue = 20 });
    
            var m2 = new DataItem() { Name = "test2" };
            m2.DataList.Add(new DataListItem() { MyValue = 100 });
            m2.DataList.Add(new DataListItem() { MyValue = 200 });
    
            this.MyList.Add(m1);
            this.MyList.Add(m2);
    
            this.DataContext = this;
        }
    
        public ObservableCollection<DataItem> MyList { get; set; }
    }
    

    i hope you get in the right direction with this example.