Getting a DataGrid
to take on a suitable data object for manipulation:
Since a DataGrid
operates on the principal that it is bound to a data object, you'll want to keep track of your data in something like a DataTable
.
For example, initialize a field for your MainWindow class of type DataTable and name it something relevant:
public partial class MainWindow : Window
{
private DataTable _cars = new DataTable("Cars");
Then in your Constructor, after you Initialize the Window component, tie the DataGrid.ItemSource
to the DataTable's collection as a data view:
public MainWindow()
{
InitializeComponent();
dgCars.ItemsSource = _cars.AsDataView();
}
Now, any time you programmatically add new rows to the _cars
table, they will be reflected inside the DataGrid
, yay! However, you want to be able to operate on the data from the user interface, so let's dive in!
Operating on the data inside the DataTable
, using inputs from the user interface:
When you want to operate on the data, you can get what is selected from the items inside the DataGrid
and use the indexes provided by them to remove the items from the DataTable
and then re-apply the DataView. That's the summary, but I'll go into more detail and finish the example:
We need to iterate over each DataGrid
item and check if it is selected before performing our logic:
for (int i = 0; i < dgCars.Items.Count; i++)
{
if (dgCars.SelectedItems.Contains(dgCars.Items[i]))
{
// This is where we do the magic
}
}
HOWEVER, we can't remove items from the DataTable
that is currently being used to supply the DataGrid
or we'll run into IndexOutOfBounds (and possibly Enumeration) errors, so to be safe, we'll use a copy of the table to operate on:
DataTable result = _cars.Copy(); //New in this step
for (int i = 0; i < dgCars.Items.Count; i++)
{
if (dgCars.SelectedItems.Contains(dgCars.Items[i]))
{
result.Rows.RemoveAt(i); //New in this step
}
}
Again, we'll run into IndexOutOfBounds errors, because we are iteration over the data as if there was X
amount of data, but each time we RemoveAt(i), we're now iterating over X--
amount of data. So, let's add a count and keep track:
int removed = 0; //New in this step
DataTable result = _cars.Copy();
for (int i = 0; i < dgCars.Items.Count; i++)
{
if (dgCars.SelectedItems.Contains(dgCars.Items[i]))
{
//Subtracting `removed` new in this step
result.Rows.RemoveAt(i - removed);
removed++; //New in this step
}
}
Last, but not least, we'll point our _cars
variable to our result
DataTable object on the heap and then reassign the dgCars.ItemSource = _cars.AsDataView()
to update our DataGrid (a more complicated explanation on this at the very bottom of my answer, if interested):
int removed = 0;
DataTable result = _cars.Copy();
for (int i = 0; i < dgCars.Items.Count; i++)
{
if (dgCars.SelectedItems.Contains(dgCars.Items[i]))
{
result.Rows.RemoveAt(i - removed);
removed++;
}
}
_cars = result; //New in this step
dgCars.ItemSource = _cars.AsDataView(); //New in this step
Finished product:
This example we've built here allows you to remove data from the DataGrid
by selecting rows on it with your mouse and then clicking a button who's Click
value equals btnRemove_Click
. Simple modifications and logic change will allow you to do the same to add, edit, etc data, but with the principle we originally started with, which is to operate on the data object (in this case, a DataTable
) and have that item be the ItemsSource
for the DataGrid
.
public partial class MainWindow : Window
{
private DataTable _cars = new DataTable("Cars");
public MainWindow()
{
InitializeComponent();
// THIS WASN'T IN THE BUILD EXAMPLE, BUT AS A BONUS:
// We could ALSO use this opportunity to setup static
// column headers if we know what they are in advance!
_cars.Columns.Add("Year");
_cars.Columns.Add("Make");
_cars.Columns.Add("Model");
dgCars.ItemsSource = _cars.AsDataView();
}
private btnRemove_Click(object sender, RoutedEventArgs e)
{
int removed = 0;
DataTable result = _cars.Copy();
for (int i = 0; i < dgCars.Items.Count; i++)
{
if (dgCars.SelectedItems.Contains(dgCars.Items[i]))
{
result.Rows.RemoveAt(i - removed);
removed++;
}
}
_cars = result;
dgCars.ItemSource = _cars.AsDataView();
}
}
-Extra reading-
Earlier in step 4, I mentioned:
Last, but not least, we'll point our _cars
variable to our result
DataTable object on the heap and then reassign the dgCars.ItemSource = _cars.AsDataView()
to update our DataGrid
The reason for this is because _cars
and result
are both objects instantiated from a class, so they reside on the heap. Items on the heap are garbage collected (removed from memory) when there is no longer a reference to them on the stack. Since _cars
is a field of our MainWindow
, and continues outside the scope of btnRemove_Click
, when we point it to the DataTable result
we keep a reference to that table, and drop our reference to the original table. Thus, when btnRemove_Click
completes, the variable result
is garbage collected, the old DataTable
that _cars
USED to point to is garbage collected, and _cars
now references our new DataTable object that we created.
This answer goes into significantly more detail, and the comments on it are worth reading as well: https://stackoverflow.com/a/80113/13924556