I have a databound datagridview and some buttons that edit, add, remove data from an SQL datatable. My main goal is to make a button that deletes the SelectedRows from the datatable that the user has selected.
Since I only need data from the first column my Column.Index will always be 0
Now from the rows that the user has selected I save the Row.Index in an array so I can pinpoint and delete which rows where the ones that were selected afterwards. The thing is I can't use my SelectedRowIndexes array outside the foreach loop because it's a local variable. For example when I run the Console.WriteLine command I get the error that my array does not exist in the current context. Am I thinking about it the wrong way ? Is there some workaround ?
private void BtnRemoveClick(object sender, System.EventArgs e)
{
foreach (DataGridViewRow row in datagridview1.SelectedRows)
{
int[] SelectedRowIndexes = { row.Index };
}
for (int i=0; i<SelectedRowIndexes.Length; i )
{
Console.WriteLine(SelectedRowIndexes[i]); // error CS0103: The name 'SelectedRowIndexes' does not exist in the current context
}
}
CodePudding user response:
If your grid is bound to a DataTable
then the DataBoundItem
of each row is a DataRowView
. You can use a loop or a LINQ query to get each of those from the SelectedRows
of the grid into an array or collection, call Delete
on each one and then call Update
on a data adapter to save all the changes. E.g.
var rows = myDataGridView.SelectedRows
.Cast<DataGridViewRow>()
.Select(dgvr => dgvr.DataBoundItem)
.Cast<DataRowView>()
.ToArray();
foreach (var row in rows)
{
row.Delete();
}
myDataAdapter.Update(myDataTable);
This assumes that myDataTable
is bound to myDataGridView
and that myDataAdapter
has been configured with an appropriate DeleteCommand
.
If you have bound the DataTable
via a BindingSource
, which you should, then you can do it another way too:
var rowIndexes = myDataGridView.SelectedRows
.Cast<DataGridViewRow>()
.Select(dgvr => dgvr.Index)
.ToArray();
for (var i = rowIndexes.GetUpperBound(0); i >= 0; i--)
{
var rowIndex = rowIndexes[i];
myBindingSource.RemoveAt(rowIndex);
}
You would then use the data adapter to save the changes in the same way.
CodePudding user response:
You didn't mention what kind of items you are showing in your DataGridView. To ease the discussion I'll assume that you show a sequence of Products
class Product
{
...
}
Separate your Form from the database access
You need to access your database. Your form does not have to know how and where the data is stored. All it has to know is that somehow there is something where you can put data in, and later get it back.
This has the advantage that you decouple your form from your database. If you later decide to change how you access the database, for instance you decide to use Entity Framework instead of SQL, then your form does not have to change. Or if you need to access the database in a different form, you can reuse the database.
The class that hides how the database is accessed is quite often called a Repository. You can store items in it. Later you can query for stored items.
In your Repository you need at least the following methods
Interface IProductRepository
{
IEnumerable<Product> FetchProducts(...); // TODO: invent a suitable name
void DeleteProducts(IEnumerable<Product> productsToDelete);
... // other useful methods
}
FetchProducts
will fetch the Products that you need to display in your DataGridView. As your repository doesn't know that the Products are shown in a DataGridView I can't use FetchProductsToDisplay
or something like that. The parameters will have to filter the Products.
class ProductRepository : IProductRepository
{
// TODO: implement
}
The implementation is not part of this question. You can use entity framework, Dapper, or plain old SQL. Users of your class won't know, and won't have to know how you access the data.
Access the Products in the DataGridView
You say that you have databound the data to the DataGridView. Therefore I assume you know how to instantiate the DataGridView, its columns, etc. The details are outside of this question.
Properties will allow you to access the displayed Products:
// access all Products in the DataGridView
public BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>) this.DataGridView1.DataSource;
set => this.DataGridView1.DataSource = value;
}
// returns the current Product or null if there is no current Product
Product CurrentProduct => this.DataGridView1.CurrentRow?.DataBoundItem as Product;
// returns the (possible empty) sequence of selected Products
IEnumerable<Product> SelectedProducts => this.DataGridView1.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();
In words: from the sequence of SelectedRows, cast every row to a DataGridViewRow. From every row in this sequence of DataGridViewRows take the value of the DataBoundItem. Cast every DataBoundItem to a Product.
To fill your DataGridView with the initial list of Products:
IProductRepository ProductRepository {get;} // initialized in constructor
void InitializeProductsDataGridView()
{
IEnumerable<Product> productsToDisplay = this.ProductRepository.FetchProductsToDisplay(...);
this.DisplayedProducts = new BindingList<Product>(productsToDisplay.ToList());
}
Back to your question
My main goal is to make a button that deletes the SelectedRows from the datatable that the user has selected.
Now that you have properly defined access to the Products in the DataGridView and the Access to the database, this is a two line method:
void DeleteSelectedProducts()
{
IEnumerable<Product> selectedProducts = this.SelectedProducts;
this.ProductRepository.DeleteProducts(selectedProducts);
}
Summary
Don't make one big form that can do everything. Separate your concerns (if you don't know this term, google for it). In this case: separate your form from the access to the database, and separate the access to the Products in your DataGridView.
This has the advantage that it is easier to understand what your classes do. It is easier to change them, without having to change the users of your class. It is easier to test these smaller classes, and it is easier to reuse items.
Most of my forms that have a DataGridView have the access properties I defined above. In fact, I created generic extension methods, so I can use them for every DataGridView. I only needed to write unit tests for them once.