I am trying to make a very flexible binding situation for my XamGrid.Basically I want to make the Grid always dynamic. The amount of columns can vary depedning on the case and their Keys can change so I would like to dynamically build it all the time. It's not Hierarchial data, its a flat grid. So I just have a collection of rows that contain a collections of cells.
I want to have it two way bind to a collection at runtime. So based upon my collection I will build the the corresponding columns and bind to my rows collection.
Here's some code...
Class Row that contains the List of Cells for the respective Row
public class Row{
public Row() { Cells = new List<Cell>(); } public List<Cell> Cells { get; set; }}Class Cell that contains the data for the individual cell public class Cell { public string Value { get; set; } }
Initialize a test collection, create column datatemplates then add to columns to XamGrid, and set the XamGrid item source
int numOfColumns = 3;string[] keys = new string[] { "Key1", "Key2", "Key3" }; List<Row> Rows = new List<Row>(); for (int i = 0; i < 10; i++){ Row r = new Row(); for (int j = 0; j < numOfColumns; j++) { Cell c = new Cell(); c.Value = String.Format("Row {0} Cell {1}", i, j); r.Cells.Add(c); } Rows.Add(r);} for (int i = 0; i < numOfColumns; i++){ TemplateColumn column = new TemplateColumn(); column.ItemTemplate = Create(typeof(TextBox), String.Format("Cells[{0}].Value", i)); column.Key = keys[i]; XamGrid.Columns.Add(column);}XamGrid.ItemsSource = Rows; Here's the DataTemplate Create method I use public static DataTemplate Create(Type type, string path){ return (DataTemplate)XamlReader.Load( @"<DataTemplate xmlns=""http://schemas.microsoft.com/client/2007""> <" + type.Name + @" Text=""{Binding Path= "+ path + @", Mode=TwoWay}""/> </DataTemplate>");}
int numOfColumns = 3;string[] keys = new string[] { "Key1", "Key2", "Key3" }; List<Row> Rows = new List<Row>(); for (int i = 0; i < 10; i++){ Row r = new Row(); for (int j = 0; j < numOfColumns; j++) { Cell c = new Cell(); c.Value = String.Format("Row {0} Cell {1}", i, j); r.Cells.Add(c); } Rows.Add(r);} for (int i = 0; i < numOfColumns; i++){ TemplateColumn column = new TemplateColumn(); column.ItemTemplate = Create(typeof(TextBox), String.Format("Cells[{0}].Value", i)); column.Key = keys[i]; XamGrid.Columns.Add(column);}XamGrid.ItemsSource = Rows;
When I run this I get the following Error:
Unhandled Exceprion
Message: Infragistics.Controls.Grids.InvalidColumnKeyException: The following key(s) do not correspon with the DataSource: "Key1" If you'd like to add additional columns, please use the unbound column type....
I want to ensure I have TwoWay binding, so I definitely want to make sure my columns are binding in that mode.
So instead of TemplateColumn I change it to UnboundColumn and I no longer get the error,
for (int i = 0; i < numOfColumns; i++) { UnboundColumn column = new UnboundColumn(); //TemplateColumn column = new TemplateColumn(); column.ItemTemplate = Create(typeof(TextBox), String.Format("Cells[{0}].Value", i)); column.Key = keys[i]; XamGrid.Columns.Add(column); }
but Its displaying as hierarchical and I want a flat grid....
What am I doing wrong ?
Hi,
Correct, you would need to use an UnboundColumn as opposed to a TemplateColumn, as a TemplateColumn requires you to be bound to your Underlying data.
Btw, if you are always just displaying text, you can avoid creating a DataTemplate dynamically, and use the ValueConverter and ValueConverterParameter properties off the UnboundColumn instead.
By doing this, you'll also get sorting and GroupBy capabilities for free.
The value that will get passed into your converter, will be your row object, and i you set the Parameter property to your key, you'll be able to return something like:
return ((Row)value).Cells[(string)parameter];
from the convert method of your IValueConverter.
Now, you're seeing the Hierarchy, b/c you have AutoGenerateColumns set to true. So we're creating a ColumnLayout for your Cells collection, as its a public property. So, set the property to false, and your grid will remain flat.
Hope this helps,
-SteveZ
I have tried setting AutoGenerate to false, I then get 3 columns and 10 rows but no data.
As for the DataTemplate, I need textboxes so I can edit the values and soon I'll be adding combobox templates and some other controls. So will I have to implement sorting on my own or something? Plus I want TwoWay Binding Enabled
For an UnboundColumn, the Binding should be RowData.Cells[key]
To read more about the UnboundColumn, you should read the following article:
http://help.infragistics.com/NetAdvantage/Silverlight/2010.2/CLR4.0/?page=xamGrid_Unbound_Column.html
@SteveZ
So I have followed your suggestions and everything is working exactly as you said.
I have one other curve ball, what if I wanted to show a CheckBox in an unbounded column instead of just the text? Using the ValueConvertor wouldn't work then as far as I can tell.
I know I would have to set my ItemTemplate and my EditorTemplate to a DataTemplate that has a CheckBox, but I'd like to be able to have those Sort and GroupBy, what will I have to implement to get that working properly ?
I am guessing I have to implement the IComparer to get GroupBy and Sorting??
Something like this ??
public class Sorter : IComparer<Row>{ private int columnIndex; public Sorter(int columnIndex) { this.columnIndex = columnIndex; } public int Compare(Row left, Row right) { return left.Cells[this.columnIndex].Value .CompareTo(right.Cells[this.columnIndex].Value); }}
Looks like it works for Sorting but GroupBy doesnt work.
What will I need to do to implement GroupBy? I think that this is what I needed to do?
public class Equality : IEqualityComparer<Row>{ public int cell; public Equality(int c) { cell = c; } public bool Equals(Row left, Row right) { return left.Cells[cell].Value == right.Cells[cell].Value; } public int GetHashCode(Row row) { return row.Cells[cell].Value.GetHashCode(); }} public class EqualityConvertor : IValueConverter{ public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { Row row = (Row)value; int index = Int32.Parse(parameter.ToString()); if (row != null) { return row.Cells[index].Value; } return null; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); }}
Did I do everything correct is there anything I missed? It all seems to be working now.
Sorry for the delayed response.
I'm not sure why you think a ValueConverter wouldn't work?
Basically in the converter, you just want to return whatever value you want, we'll then use the returned value for comparing and grouping.
However, to answer your other questions, for Sorting you need to set the SortComparer property on the column to an IComparer<Row> and for GroupBy set the GroupByComparer property to a type of IEqualityComparer<T>.
So the classes you've created look correct, you'll just need to create instances of them, and set them on the column's respective properties.