How to bind a DataGrid to an IList<string> property?

Oct 8, 2008 at 8:38 PM
I'm trying to bind an IList<string> property of a source object to a WPF Control.  The WPF DataGrid seemed like the obvious choice of control.

I'm trying this:
DataGrid datagrid = new DataGrid();
Binding binding = new Binding("Nameoflistofstringproperty")
                                 {
                                    Source = someObject,
                                    Mode =
BindingMode.TwoWay,
                                    UpdateSourceTrigger =
UpdateSourceTrigger.PropertyChanged
                                };
datagrid.SetBinding(ItemsControl.ItemsSourceProperty, binding);

The list property in the test object has two strings.  What I see in the Control are two integers which are, I guess, the string lengths of the two strings.  string.Length seems to be the only property on string, so I guess there is some magic in the DataGrid that picks the first property of the object in the list.

Does that mean that the List<string> needs to be replaced with eg.  List<Someclass> with Someclass defined something like:
class Someclass {
   public string AStringField { get, set }
}
?

Or is there a simpler way to make DataGrid create a single column with the string as the value?

Thanx!


- Steinar
Coordinator
Oct 9, 2008 at 2:17 AM
If you just want to display the ToString() version of your list you can use a TemplateColumn like so:

<

 

dg:DataGridTemplateColumn>
    <dg:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}" />
        </DataTemplate>
    </dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>

 

Oct 9, 2008 at 8:25 AM

vinsibal, thank you!

However, I need to be able to do this in C# code.  The DataGrid Control will be part of a dialog panel that's constructed when using reflection to examine the source object's properties, and attributes put on the properties.

Unfortunately I haven't grasped yet, how XAML templates play together with C# code.  I need to do some more research here.

Thanx again!

Oct 21, 2008 at 4:50 PM
Two things:
 1. I've tried translating this into C# code but so far been unsuccessful.  I've succeeded in creating a DataGridTemplateColumn, but after that I don't know what to do with it:
      DataGrid datagrid = new DataGrid();
       datagrid.VerticalAlignment =
VerticalAlignment.Stretch;
       DataGridTemplateColumn templateColumn = (DataGridTemplateColumn) XamlReader.Load(XmlReader.Create(new StringReader(@"<Controls:DataGridTemplateColumn xmlns:Controls=""clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"" xmlns:Windows=""clr-namespace:System.Windows;assembly=PresentationFramework"" xmlns:Data=""clr-namespace:System.Windows.Data;assembly=PresentationFramework"" xmlns:Controls1=""clr-namespace:System.Windows.Controls;assembly=PresentationFramework""><Controls:DataGridTemplateColumn.CellTemplate><Windows:DataTemplate><Controls1:TextBlock Text=""{Data:Binding}"" /></Windows:DataTemplate></Controls:DataGridTemplateColumn.CellTemplate></Controls:DataGridTemplateColumn>")));

 2. I'm not sure if "display the ToString version of my  list" is want to do.  I want an IList<string> property on the source object of a data binding to display in a DataGrid column as the string value instead of the  string length, and to be editable, so that changing the string in a cell will change the bound list member.  It _does_ look like the TemplateColumn XAML fragment does the right thing though, if just {Binding} will display the string value of an element, and work when setting the value back as well

But if 2. is ok, I _still_ need to figure out how to apply the template in C# code.

Hm... I see that the XML fragment in the string constant looks quite different from your original example...?  I think that happened when I pasted your fragment into a XAML editor.  Well... at least the dg: namespace in your fragment needs to be defined in some way...?  (but the "" defined by the XAML editor is possibly the wrong thing?)
Oct 21, 2008 at 5:41 PM
I wrote earlier:

> Hm... I see that the XML fragment in the string constant looks quite
> different from your original example...? I think that happened when
> I pasted your fragment into a XAML editor. Well... at least the
> dg: namespace in your fragment needs to be defined in some way...?
> (but the "" defined by the XAML editor is possibly the wrong thing?)

My mistake: the namespace wasn't empty, I was just fooled by the
quoting of " chars inside strings.

I'm guessing that the cell template thing is supposed to work like this:
- Create a DataGrid
- Create a DataGridTemplateColumn with the correct bindings
- Create a data binding from the IList<string> property
- Set the data binding on the DataGrid

Unfortunately when I do this, the DataGrid only contains a single column called Length.

The current code for creating the DataGrid looks like this (I've moved the XAML template to a text constant):

const string dataGridTemplateConfiguration =
@"<Controls:DataGridTemplateColumn xmlns:Controls=""clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"" xmlns:Windows=""clr-namespace:System.Windows;assembly=PresentationFramework"" xmlns:Data=""clr-namespace:System.Windows.Data;assembly=PresentationFramework"" xmlns:Controls1=""clr-namespace:System.Windows.Controls;assembly=PresentationFramework"">
<Controls:DataGridTemplateColumn.CellTemplate>
<Windows:DataTemplate>
<Controls1:TextBlock Text=""{Data:Binding}"" />
</Windows:DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
</Controls:DataGridTemplateColumn>";
DataGrid datagrid = new DataGrid();
datagrid.VerticalAlignment = VerticalAlignment.Stretch;
DataGridTemplateColumn templateColumn = (DataGridTemplateColumn)XamlReader.Load(XmlReader.Create(new StringReader(dataGridTemplateConfiguration)));
datagrid.Columns.Add(templateColumn);
Binding binding = DataBindingUtilities.CreateOperatorPropertyBinding(op, operatorProperty.PropertyInfo);
datagrid.SetBinding(ItemsControl.ItemsSourceProperty, binding);
Coordinator
Oct 22, 2008 at 2:41 AM
Does something along these lines help:

DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
DataTemplate cellTemplate = new DataTemplate();
FrameworkElementFactory cellTemplateFactory = new FrameworkElementFactory(typeof(TextBlock));
Binding binding = new Binding();
cellTemplateFactory.SetBinding(
TextBlock.TextProperty, binding);
cellTemplate.VisualTree = cellTemplateFactory;
cellTemplate.Seal();
templateColumn.CellTemplate = cellTemplate;
DataGrid_Standard.Columns.Add(templateColumn);
DataGrid_Standard.AutoGenerateColumns =
false;
DataGrid_Standard.ItemsSource = <your data source>
;

 

Oct 22, 2008 at 1:48 PM
From: vinsibal [email removed]:

> Does something along these lines help:

Yes, thanx. That lets me display the IList<string> property in a DataGrid column.

But it doesn't let me edit it. I tried reusing the cellTemplate as an edit template, ie. do
templateColumn.CellEditingTemplate = cellTemplate
but that (not surprisingly didn't make any difference).

Next I kept that setting and tried making the Binding TwoWay. This made the dialog using the panel throw an exception when being opened, saying that a TwoWay binding needs a path setting.

I removed the TwoWay setting, and created a new template called cellEditTemplate:
DataTemplate cellEditTemplate = new DataTemplate();
FrameworkElementFactory cellEditTemplateFactory = new FrameworkElementFactory(typeof(TextBox));
Binding cellEditBinding = new Binding();
cellEditTemplateFactory.SetBinding(TextBox.TextProperty, cellEditBinding);
cellEditTemplate.VisualTree = cellEditTemplateFactory;
cellEditTemplate.Seal();
templateColumn.CellEditingTemplate = cellEditTemplate;

With this the dialog opens, but when I try modifying a cell, I get the "TwoWay binding needs a path setting" exception. And I don't know what would be a meaningful path setting for a string.
Oct 22, 2008 at 2:02 PM
"Two-way binding requires Path or XPath." is the exception I see.

I've tried to setting the Path of the Binding used in the cellEditTemplate to new PropertyPath("") but I still got the same exception when attempting to edit a cell.
Coordinator
Oct 22, 2008 at 2:27 PM
Set the Path = "."

But you'll still have a problem b/c the way the binding system works is that the properties on the data source need to be notified of the changes.  Since you are using "string" as the property it will not know how to update when the binding updates in the TextBox.  You can create a proxy class to implement the INotifyPropertyChanged interface and it should work for that.
Oct 22, 2008 at 5:39 PM
From: vinsibal

> Set the Path = "."

Almost there. :-)

I did this:
FrameworkElementFactory cellEditTemplateFactory = new FrameworkElementFactory(typeof(TextBox));
Binding cellEditBinding = new Binding { Path = new PropertyPath("."), Mode = BindingMode.TwoWay };
cellEditTemplateFactory.SetBinding(TextBox.TextProperty, cellEditBinding);
cellEditTemplate.VisualTree = cellEditTemplateFactory;
cellEditTemplate.Seal();
templateColumn.CellEditingTemplate = cellEditTemplate;

Now I'm allowed to edit the cells, but the changed string values don't make it all the way to the IList<string> elements matching the cells in the source object IList<string> property, and when I change cells the old value pop back.

I tried setting the Path on the cellTemplate Binding, as well as on the cellEditTemplate Binding, but that didn't help.

> But you'll still have a problem b/c the way the binding system
> works is that the properties on the data source need to be
> notified of the changes. Since you are using "string" as the
> property it will not know how to update when the binding
> updates in the TextBox. You can create a proxy class to
> implement the INotifyPropertyChanged interface and it should
> work for that.

I've thought of different approaches for that for later. We already have a proxy implementing INotifyPropertyChanged, wrapping the the source object (because we couldn't introduce that dependency into the assemblies defining the types of the source objects).

It's possible that we could extend this proxy in various ways for the IList<string> properties. I've mainly thought in the lines of replacing the IList<T> with SomeCollection<T>, where SomeCollection implements INotifyCollectionChanged, and in particular of changing IList<string> to SomeCollection<OfAClassThatHasASingleStringProperty>. What have kept me back is the serious increase in complexity this would cause.
Coordinator
Oct 22, 2008 at 6:35 PM
"Now I'm allowed to edit the cells, but the changed string values don't make it all the way to the IList<string> elements matching the cells in the source object IList<string> property, and when I change cells the old value pop back.

I tried setting the Path on the cellTemplate Binding, as well as on the cellEditTemplate Binding, but that didn't help."

Until you address the notificaiton callback issue, editing will not work as expected.
Oct 22, 2008 at 7:07 PM
From: vinsibal

> Until you address the notificaiton callback issue, editing will not work as expected.

Hm... ok...? It does work for me when I bind a property that is IList<T> where T is a class with properties, but doesn't implement INotifyPropertyChanged.

Are you saying I _have_ to wrap the string of an IList<string> property and make the property into an IList<Wrapper> property where Wrapper is some wrapper class holding a string?

I believe this is doable in the proxy that already wraps the source object, but very complicating to the code, and not very efficient.

Or is there a different way of adressing the callback issue? Note that for now it's ok for me that I'm able to edit the string values, I don't need to be notified when something else changes the property.
Oct 22, 2008 at 8:16 PM
From: SteinarB [email removed]:

> Or is there a different way of adressing the callback issue?
> Note that for now it's ok for me that I'm able to edit the
> string values, I don't need to be notified when something else
> changes the property.

To put it in a different way: I thought implementing IPropertyNotifyChanged had to do with notifying other listeners, or notifying the DataGrid when something else changes the source property?

Right now I see in the debugger that the changes in the cell TextBox does not make into the bound source property, so implementing IPropertyNotifyChanged should/would not help anything... unless I'm misunderstanding...?
Coordinator
Oct 24, 2008 at 12:18 AM
Sorry about that.  The issue isn't about not implementing INotifyPropertyChanged.  The issue is binding to the object string and trying to update that object by editing the TextBox.Text.  If it were any List<T> where T is the object that is bound in the column, if you update the cell by editing it, it will not know how to convert that back to a new object of T.  So the recommendation when used with the DataGrid is to bind to properties of the object and not the object itself.
Oct 29, 2010 at 1:20 AM

I'm running into the same issue two years later, was there ever a solution to this? Should I use the listbox instead?

 

Thanks!

Nov 2, 2010 at 1:42 PM
Edited Nov 2, 2010 at 1:43 PM

What we did was something like this:

 class ValueContainer {
   string Value { set; get; }
 };

and then bind an IList<ValueContainer> the DataGrid.ItemsSource. 

Then you will get a single column with the name Value, and be able to edit collection items in text boxes.

Nov 2, 2010 at 4:55 PM

Hey Steinarb

I ended up implementing something like that to fix the behavior, wrote a class that contained the string as a property

Thanks for your response!!!