Making a Bind in a WPF DataGrid is not the the most difficult task. In fact, there are dozens of posts explaining how to do it (http://wpftutorial.net/DataGrid.html, http://www.codeproject.com/Articles/30905/WPF-DataGrid-Practical-Examples). However, they all have one detail in common: they assume that the columns of the DataGrid are known at compile time, ie., each column is a reference to a property of class that represents a row in the DataGrid.
An example: the class Customer has the properties Id, Name and Age. The DataGrid displays a list of customers. Therefore, it is necessary to create a column for each property. To do so, simply create in xaml, three instances of the class DataGridTextColumn and make a bind to each of the 3 properties, like following code:
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Path=Id}"/>
<DataGridTextColumn Header="Nome" Binding="{Binding Path=Name}"/>
<DataGridTextColumn Header="Idade" Binding="{Binding Path=Age}"/>
</DataGrid.Columns>
Although
this approach solves many of the problems, it is not for scenarios
whose columns are known only at runtime. Consider, for
example, the case where the user selects a date range and the DataGrid should, thereafter,
show a column for each day in this range. In this case, how to generate
the bindings of the columns? And worse: how to fill column headers?
The
purpose of this post is, therefore, to show an approach to deal with scenarios in
which the columns of the DataGrid
are not known at compile time. At the end of the post, a small project
for download will exemplify the use of the technique presented here. To understand it, it is essential that the reader has a
minimal knowledge about bindings and how to use DataGrid in WPF. If not, I strongly recommend you
read the posts mentioned in the first paragraph. We will follow here the solution proposed in http://stackoverflow.com/questions/320089/how-do-i-bind-a-wpf-datagrid-to-a-variable-number-of-columns/8890435
In order to
solve the problem posed in question it should be noted, firstly, that
you cannot define the columns in xaml, simply because we do not know the columns in compile time. Therefore, we will do the bindings programatically through a C# method. This method
should receive a list of column names and, from them, create instances of the
DataGridTextColumn class and
set its Header and Binding
programmatically, as shown in the code below:
public void SetGridValues(IList<string> columnHeaders, IList<Row> lines)
{
_datagrid.Columns.Clear();
for (var index
= 0; index < columnHeaders.Count; index++)
{
var newColumn = new DataGridTextColumn
{
Header
= columnHeaders[index],
Binding = new Binding(string.Format("Cells[{0}].Value", index)),
};
_datagrid.Columns.Add(newColumn);
}
Lines = lines;
}
What this method does is very simple: for each column, the method creates an instance of DataGridTextColumn sets the Header property using the corresponding name and fill the Binding property using an instance of the Binding class. The Binding class, in turn, receives in the constructor a single parameter: the Path of the property to be displayed in the Grid. It is worth remembering here that the DataGrid stores its data in the format "list of lines", that is, each element from the list ItemsSource is a line of the DataGrid. In the method SetGridValues
(above), the parameter lines is exactly the list of rows in the DataGrid. Therefore, the Path parameter of the binding is the path relative to the instance of the Row class for that line.
To better understand this, we must examine in detail the class Row:
public class Row
{
public IList<Cell> Cells { get;
set; }
}
The class definition Row is actually quite simple: a list of cells represents each column. The cell represented by the Cell class is even simpler:
public class Cell : INotifyPropertyChanged
{
private string _value;
public Cell(string
value)
{
_value
= value;
}
public
string Value
{
get
{ return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
#region
Implementation of INotifyPropertyChanged
public
event PropertyChangedEventHandler
PropertyChanged;
private
void OnPropertyChanged(string name)
{
if
(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion
}
Examining the Row and Cell classes, it becomes clear the reason for the chosen value for the Path property in the Binding constructor. A clearer explanation I leave to the reader's curiosity to execute the code in debug mode in Visual Studio:
string.Format("Cells[{0}].Value", index)
To finish, is worth one last comment: the method SetGridValues is only necessary to assemble the skeleton of the DataGrid, ie, to define the DataGrid columns. Once assembled the skeleton, updating a cell is as easy as changing its Value property, as follows:
Model.Lines[row].Cells[column].Value = Model.NewValue;
In this case, the setter of the Value property already notifies the change and, as there is a bind to the corresponding cell, the DataGrid control makes the refresh automatically, freeing us from this burden. Take a look in our download project and see this code working.
Dear developers, I hope you enjoyed. Any questions and comments, please post. The code to see the DataGrid in action is in the link below:
Please, like us on facebook and have a look at our fan page!!
No comments:
Post a Comment