I Have DataGrid that contains DataBase table Information (access if it's matter).
Until now I had perfect datagrid work's with database but with buttons( Show, Add, Delete and Edit), but now, I want to delete the buttons and do all these functions with only the dataGrid.
When I do the show button without the clicking ( automatically when the wpf loading ) It show the data table but when I try to set header column it show me that the DataGrid is null (when These dataGrid show's the table from the data base with the right information). I would like to get help with this issue. Thank's.
The code :
try
{
DataBaseIkuns.Instance.OpenConnectionWithDB();
DataBaseIkuns.Instance.LoadDataFromDB(DictionaryUtilsDB.dictioneary[DictionaryUtilsDB.CommendTypes.ShowIkuns]);
dataGridIkuns.ItemsSource = DataBaseIkuns.Instance.dt.DefaultView;
// ---------------------- Until here it work's perfect - loading the table from the data base with the origin header column
DataBaseIkuns.Instance.SetNameOnHeaderColumn(dataGridIkuns); // here it fall.
DataBaseIkuns.Instance.CloseConnectionWithDB();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.ToString());
}
internal void SetNameOnHeaderColumn(Microsoft.Windows.Controls.DataGrid dataGridIkuns)
{
dataGridIkuns.Columns[0].Header = "x"; // Fall because DataGridIkuns colomn count is 0.
dataGridIkuns.Columns[1].Header = "y";
}
The error : Index was out of range. Must be non-negative and less than the size of the collection parameter name:index
When I click "Show" button it do this exactly function and it work's perfect. But when I do it when the wpf appliaction load it wrong. Why?
By the way another question, if anyone know guide for work's with the dataGrid (add,remove and edit) without buttons only with keyboard editing) I would very happy to get it.
If you are letting the grid auto-generate columns for you, then chances are that the columns have not been populated yet. Column auto-population does not necessarily happen instantaneously upon setting the ItemsSource; it may be deferred either because the grid has never been measured, or because it has no data items yet.
You can try deferring the call to SetNameOnHeaderColumn until later by scheduling it on the Dispatcher using BeginInvoke with DispatcherPriority.Loaded:
try
{
DataBaseIkuns.Instance.OpenConnectionWithDB();
DataBaseIkuns.Instance.LoadDataFromDB(/* ... */);
var view = DataBaseIkuns.Instance.dt.DefaultView;
dataGridIkuns.ItemsSource = view;
dataGridIkuns.Dispatcher.BeginInvoke(
DispatcherPriority.Loaded,
new Action(
() =>
{
if (dataGridIkuns.ItemsSource == view)
SetNameOnHeaderColumn(dataGridIkuns);
}));
DataBaseIkuns.Instance.CloseConnectionWithDB();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.ToString());
}
Since you asked, here are some standard shortcuts that should work with the DataGrid:
Tab/Shift+Tab - Go to next/prev cell on current row, or wrap around to next/prev row
Left/Right - Go to prev/next cell on current row only
Up/Down - Go to prev/next row
F2 - Edit selected cell
F4 - Open cell editor drop-down (once editing, if applicable)
Esc - Cancel edit
Del - Delete current row (when not editing a cell)
Home/End - Jump to first/last cell in row
Ctrl+Home/Ctrl+End - Jump to first/last row in grid
PgUp/PgDn - Jump up/down one "page" in the view
Enter/Shift+Enter - Commit current cell if editing, then jump to same column in next/prev row
I am not aware of any shortcut to insert a new row or to jump to the new row placeholder.
Related
I have an input enabled DataGridView bound to a BindingSource which has a SortableBindingList as DataSource.
There is no direct connection to the database. The list is read once from the database beforehand. After the user is done with editing he can choose either to save the changed data to the database or not.
The list has 2 fields:
"Type" (enum)
"Path" (string)
As I want to use a ComboBox for the user to select the "Type" I add an additional column "TypeCbx" which is bound to the enum values. Initially, all "Type" values from the list are copied to the column "TypeCbx" and for changes, the value of "TypeCbx" is copied back to "Type" in the event ...CellEndEdit().
Furthermore, I have 2 button columns included:
"Browse" button: opens a FolderDialog for the user in order to adjust the actual row and set the column "Path" and (if it is a new row/entry) a default value to "Type"
"Remove" button: removes the row/entry from the DGV
This worked as long as I used an unbound DataTable in which I had the following code for the button "Browse":
private void dgvPaths_OpenFolderClick(DataGridView sender, DataGridViewCellEventArgs e) {
string newSelectedPath = Helper.FileBrowserDialog("Select folder", LastSelectedPath);
if (newSelectedPath != null) {
LastSelectedPath = Helper.CleanPath(newSelectedPath);
if (dgvPaths.Rows[e.RowIndex].IsNewRow) {
// --- variante old: unbound datatable ----------------------------------------
DataGridViewRow row = (DataGridViewRow)dgvPaths.Rows[0].Clone();
row.Cells[dgvPaths.Columns["Path"].Index].Value = LastSelectedPath;
row.Cells[dgvPaths.Columns["Type"].Index].Value = LibraryPathType.Movies;
row.Cells[dgvPaths.Columns["TypeCbx"].Index].Value = LibraryPathType.Movies;
row.Cells[dgvPaths.Columns["TypeCbx"].Index].ReadOnly = false;
dgvPaths.Rows.Add(row);
dgvPaths_CellValueChanged(sender, e);
} else if (dgvPaths.Rows[e.RowIndex].Cells["Path"].Value == null || LastSelectedPath != dgvPaths.Rows[e.RowIndex].Cells["Path"].Value.ToString()) {
dgvPaths.Rows[e.RowIndex].Cells["Path"].Value = LastSelectedPath;
dgvPaths.Rows[e.RowIndex].Cells["TypeCbx"].ReadOnly = false;
dgvPaths_CellValueChanged(sender, e);
}
}
}
Now, with the bound DataSource, the line dgvPaths.Rows.Add(row); no longer works. So I adjusted the code as follows:
// --- variante new 1: bound list, working on dgv -----------------------------
dgvPaths.Rows[e.RowIndex].Cells["Path"].Value = LastSelectedPath;
dgvPaths.Rows[e.RowIndex].Cells["Type"].Value = LibraryPathType.Movies;
dgvPaths.Rows[e.RowIndex].Cells["TypeCbx"].Value = LibraryPathType.Movies;
dgvPaths.Rows[e.RowIndex].Cells["TypeCbx"].ReadOnly = false;
dgvPaths_CellValueChanged(sender, e);
Issue 1:
Now, the data is written into the row of the DataGridView but the DataGridView does not interpret it as an input and therefore it is not really added - it still is a "new Row" waiting for input. I need to manually go into the Path Column of the row and press a key in order that the DataGridView accepts it as a valid entry and shows a new "new Row" line.
=> How can I inform the DataGridView that programmatically entered data should be handled like a user input?
Issue 2:
Furthermore, when I manually enter an entry in the DataGridView and click in the "Path" column between the added line and the new "new Row" line, a first chance exception is thrown.
=> What is the reason for the first chance exception?
Then I've read that you should not manipulate the DataGridView but instead the BindingSouce or BindingSource.DataSource, which I tried with by changing the code to this:
// --- variante new 2: bound list, working on datasource ----------------------
Library.Current.AddDirtyPath(LibraryPathType.Movies, LastSelectedPath);
Issue 3:
Hereby, I also get a first chance exception when this entry is added to the source list.
=> What is the reason for the first chance exception?
What is the correct approach here?
=> Do I need to manipulate the rows of the DataGridView or the entries of the BindingSource or the entries of the BindingSource.DataSource?
Issue 4:
The bound DataGridView threw another exception when loading the DataSource and there is no enum value for "0" (I guess for the "new Row" line). Therefore, I needed to add a dummy enum value which is set to "0" to my enum value list which I need to skip again for the actual ComboBox selection values. It works but it messes up the code.
=> Is it possible to avoid this dummy value, at all?
--- U P D A T E ---
After reading Caius recommendation, I have decided to update this question as I was able to follow the SBL approach and reduce the issues.
The correct approach here is to use a main "storage" SBL for all working data (which is initially filled by reading the DB) and create a filtered SBL out of it which is used as DS for the DGV where the user can work on and use sort and filter methods. By adding/updating/removing data, you have to ensure that all adjustments stay synchronous in the "storage" SBL. Then, when you want to update the DB, you use the 'storage' SBL.
Thereby only 1 issue is left: if you want to add a new line with a button column function inside the DGV itself. It fails as the state of the actual new line is changed while you are calling the add function from inside the DGV. There are 2 ways to "fix" this:
a) You need to completely(!) Clear() the SBL that is used as DS for the DGV and add the new line afterwards (according with all other existing ones from the "storage" list) back to this SBL. Thereby the state of the new line is also changed but as it is completely removed the state is also cleared. After this you need to Refresh() the DGV. Hereby, you will lose focus of the actual cell.
b) You use a hidden button outside of the DGV by btnAdd.PerformClick() which calls the add function in which you do not need to Clear() the SBL that is bound to the DGV. This seems strange but it works (whyever) and you keep the focus on the "new line" row (not on the added one).
All other functions like updating and removing an existing DGV line can be called by additional button columns within the DGV itself without issues.
The enum issue is not an issue as it is common to use a dummy zero value for empty entries. If I find out, how to get rid of the dummy value, I will update this question accordingly.
In order to fill the combobox column without a split between display and value columns you only need to ensure that the DataPropertyName of the combobox column is set like the DB column name.
--- U P D A T E: 2 ---
I have created a detailed video (tutorial) about my approach:
https://www.youtube.com/watch?v=W_afaNf7nz8
From 1:31:20 I show the difference between adding a new line by an external and an internal button and also the strange behaviour of using the method directly (=> error) or triggering an external button which uses the same method (=> no error).
What is the correct approach here?
You have a datagridview
You bind its DataSource to a datatable
You have a datagridviewcombobox
You bind it's DataSource to a completely different datatable
You tell the combo which columns, from its own datatable, are to be used for display, value and which column in the table that the grid is bound to, shall be updated/used for deciding which value to Show
var gdt = new DataTable();
gdt.Columns.Add("FileType", typeof(FileType));//enum
gdt.Columns.Add("Path");
gdt.Rows.Add(FileType.Text, "c:\my.txt");
var fdt = new DataTable();
fdt.Columns.Add("Val", typeof(FileType));//enum
fdt.Columns.Add("Disp");
foreach(FileType t in Enum.GetValues<FileType>())
{
fdt.Rows.Add(t, t.ToString());
}
//now wire it up
datagridviewWhatever.DataSource = gdt; //makes columns
var c = new DataGridViewComboBoxColumn();
c.DisplayMember = "Disp"; //name of column in fdt to use for show
c.ValueMember = "Val"; //name of column in fdt to use for value during lookup/set operations
c.DataPropertyName = "FileType"; //name of column in gdt that this combo shall show/set
c.DataSource = fdt; //set DataSource last (performance reasons)
Combo will now, for each row, get the value in gdt.FileType (eg FileType.Text), lookup the value in its own table fdt.Val, use the related value in fdt.Disp (eg "Text") to show in the list. If the user chooses a new list item (eg "Excel") it goes the other way, get the relevant Val (eg FileType.Excel) chosen and push it into gdt.FileType
I've never actually done it with enum typed values; can't see why it wouldn't work but if it gives trouble it might be simpler to switch to using ints instead of FileTypes - make all your typeof() calls typeof(int) and cast the FileType t to int in the foreach when adding it to the row collection.
Finally, when you're down with this as a concept I recommend throwing it all away and doing it using the visual designer - it will make a much better, nicer, easier to use job of it:
add a DataSet type file to your project
open it
if there is a database somewhere backing all this, right click on the design surface and choose Add TableAdapter, enter the connection string, choose a query, that returns rows, put SELECT * FROM MovieFiles WHERE ID = #id or whatever, call it FillById/GetDataById, finish
if there isn't a database backing all this just right click the surface, add a datatable called MovieFiles, right click it and add columns like FileType (set type to int in property grid), Path etc
right click a blank space and add another datatable called FileTypes, put a string column called Disp and an int column called Val (sounding familiar?)
save and switch to Forms designer. Open Data Sources tool panel (View menu.. other windows submenu)
Drag the MovieFiles node out of data sources and into the form. A datagridview appears as well as a bunch of stuff in the bottom tray. The grid is already correctly binded to the DataSet that contains the MovieFiles table. You can see all the code VS is writing for you in the Forms.Designer. It looks a lot like the code I put manually above
Edit the columns of the datagridview, change the file type column to be a combobox type, set the DataSource to the FileTypes table, DisplayMember to Disp, ValueMemberto Val. Check the DataPropertyName is still FileType in movies - this should also be familiar as it's the visual version of the code above)
the only thing left to do is put some code in the constructor that fills the FileTypes table from the enum (or to be honest, just make a table in the DB called FileTypes, have an int and a string column, add a tableadapter, select them.. that way you don't have to recompile the whole program when you add a file type)
Why do I advocate using these visually designed datatables rather than code ones? Because they're so much nicer in every single way. They have logical methods and named properties for columns, they just work with LINQ and they behave like .net classes that are collections of POCOs not 2D arrays of object that are indexed by string and need casting to make them useful. DataTable are awful to work with in comparison. They wrap around basic DataTable and do expose them but follow a rule that if you've exposed it, you've probably gone wrong
//no; using .Tables puts you back in "world of pain"
var p = (string)datasetX.Tables["MovieTypes"].Rows.Cast<DataRow>().First()["Path"];
//yes - using named property for MovieTypes is good, LINQ works, Path is a string property etc
var p = datasetX.MovieFiles.First().Path;
Hello I have not much experience in programming and haven't found any useful information regarding my question, that's why I need your help.
My goal is to create a datagridview that is databound by a local database (datatable), which is fully customazible by user. At first, when the user logins to main form if there was no previuos instance of editing, the datagridview will display nothing, however user can add columns by button (which specifies headertext, datatype and so on) and when added the datagridview will display the first column and then the user can edit the rows simply by clicking on the datagridview square (like MS excel). After that, the user can save the data by clicking on a save button, now the datatable will show the saved data, and the next time user will login, it will show the saved contents.
The situaton of now: I have 3 columns with data in the datatable, it is databounded to the datagridview and on debug it will show how it should be, I create the unbound column but it of course will not save after pressing the save button, thats because I have a problem with this code:
private void button3_Click(object sender, EventArgs e) // save button
{
for (int j = 0; j < dataGridView1.ColumnCount; j++)
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
cmd = new SqlCommand(#"INSERT INTO Inventorius VALUES ('" +
dataGridView1.Rows[i].Cells[j].Value + "')");
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
I'm trying to find a specific algorithm that can insert all rows and columns to the datatable that are created/deleted in the datagridview.
I think I should also look into my function that adds the column, or is it irrelavent? Ah please do tell me if the goal is suitable in doing with datagridview, or is there any more better alternatives, thank you.
I think you're going about this the wrong way
I understand you want to provide the user the ability to create columns, but there are only a certain number of columns and what columns they are is dictated by the database design. You can't let the user arbitrarily add their own column (well.. you can but this seems to be not your goal)
Because there are a set of known columns you might as well just support them all, bind them all, have them all in your datagridview and then just make them all invisible (theColumn.Visible = false) and when the user chooses to "add" them, just make them Visible=true. They "look" like theyve been added but in reality they were always there, wired up and working properly, just hidden
I also think you're going about your data grid/table/access the wrong way (or, the hard way)
Add a new dataset to your project
Open it, right click the surface, add a tableadapter
Go through the wizard, selecting a connection string, saving it, adding a select that returns rows, select * from sometable where id = #id, call the methods FillBId/GetDataById, finish
Save the dataset
Switch to your form, open the Data Sources window (View menu, Other Windows), drag the node representing your datatable, onto the form - a datagridview appears along with some other componentry that will retrieve and save data, manage current rows etc
Put some code into the constructor after InitializeComponent():
foreach(DataGridViewColumn c in yourDataGridViewName.Columns)
c.Visible = false;
Wire up some way for the user to choose columns, perhaps a listbox with checkboxes that gets its items list from the datagridview columns collection
Pay attention to how the back end code is structured - when you dropped the grid on the form it writes some code too; that's how to load (fill) and save (tableadapter.update) the db too
Do not iterate over a datagridview pulling values out of it; it's a UI control for showing and editing the datatable to which it is bound. If you want to get access to values, get it via the datatable. If you want to save edited alues, it's just a single line of code: someTableAdapter.Update(someDataTableNameHere)
I have a datagridview with VirtualMode = true that I have also implemented drag/drop to enable the user to reorder rows within the datagridview. My issue is even though I am using SuspendLayout/ResumeLayout, the datagridview is still calling CellValueNeeded in the middle of processing causing my program to crash.
Within the DragDrop event, I have the following code where 'dragRow' is the source row and 'row' is the destination of the drag/drop event.
gridview.SuspendLayout();
try
{
// copy dragged row
DataGridViewRow rowCopy = gridview.Rows[dragRow];
DataValue dataCopy = dataList[dragRow];
// remove dragged row
dataList.RemoveAt(dragRow);
gridview.Rows.RemoveAt(dragRow);
// insert row
dataList.Insert(row, dataCopy);
gridview.Rows.Insert(row, rowCopy);
// move selection to moved row
gridview.CurrentCell = gridview[gridview.CurrentCell.ColumnIndex, row];
}
finally { gridview.ResumeLayout(true); }
Before the drag/drop is initiated, my program detects that the user selected the last row. I have designed the last row to always be empty for reasons I am not going to get into here. Usually if the user selects the last row, then it initiates the drag/drop with only the DragDropEffects.Copy option enabled. If I detect the second to last row is also empty, then I switch the row being dragged to the second to last row to enable the user to move the blank row (as the last row is not movable). The issue is during the DragDrop event between where the row is removed from my data list to where it is inserted in the new location the datagridview calls its CellValueNeeded event causing my program to crash on an out of range exception as it tries to read something from my data list that is not there.
I have also seen this issue in relation to tool tips being displayed. If the user hovers the mouse within the row/cell they just dragged, then the tool tip displayed is for the wrong row as if the CellToolTipTextNeeded event was raised for the wrong cell and not updated after the ResumeLayout.
Is there something I'm missing that I should be doing to let the datagridview know I'm updating its data source while in virtualmode?
For reference, the following CellValueNeeded handler is example of where IndexOutOfRangeException is being thrown due to gridview trying to read from row that no longer exists in dataList after line dataList.RemoveAt(dragRow); in above code.
private void gridview_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
switch (e.ColumnIndex)
{
case 2: // Name
e.Value = dataList[e.RowIndex].Name;
break;
case 3: // Value
e.Value = dataList[e.RowIndex].Value;
break;
}
}
You ask two questions:
First:
Q: "How to keep virtualmode datagridview from calling cellvalueneeded while updating data in the background in C#?"
A: My modeling shows that this line is what explicitly causes CellValueNeeded to be called:
gridview.CurrentCell = gridview[gridview.CurrentCell.ColumnIndex, row];
Wrapping it in SuspendLayout does not change the fact. If you want to avoid CellValueNeeded being called in this method, then remove this line and call it elsewhere.
Second
Q: "Is there something I'm missing that I should be doing to let the datagridview know I'm updating its data source while in virtualmode?"
A: (Short Answer) No.
According to my modeling, your code will work without throwing exceptions if:
It takes into account that CellValueNeeded will be called if the control redraws for any reason at any time whether caused by your application or some other window activity or mouse state change (which included any mouse motion whatsoever over the control).
It maintains these three values in sync at all times, updating immediately if a row is removed or inserted:
The RowCount of the DGV
The Count of the data source
The offset of 1 needed for RowCount if-and-when the AllowUserToAddRows property is true.
You address a bug in your code: That you are trying to manipulate the removal and insertions of rows using int index values. This approach is fraught with danger anyway, but particularly in VirtualMode because there is no binding between those index values and the source list containing the DataValue objects. In a drag drop operation coded in the manner you show, these index values become unreliable (i.e might-or-might-not-work) the moment you insert or remove a list item.
Please try substituting this for the first code block in your post and let me know if it fixes your problem.
try
{
// We need to bank the actual objects here.
DataValue
draggedItem = dataList[dragRowIndex],
dropTarget = dataList[dropRowIndex];
// From here on out, anything index-based is doomed to
// spradically fail because we're changing the list by
// removing one or more items from it. There is no
// binding between the two (you gave that up when you
// set VirtualMode = true)
dataList.RemoveAt(dragRowIndex); // Remove the dragged item(s)
SynchronizeCounts();
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// CRITICAL:
// So at what index is the drop target now?
int correctDropRowIndex = dataList.IndexOf(dropTarget);
// In many cases it's not the same as dropRowIndex!!
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
dataList.Insert(correctDropRowIndex, draggedItem);
SynchronizeCounts();
// move selection to moved row
int safeColumnIndex = gridview.CurrentCell == null ? 0 : gridview.CurrentCell.ColumnIndex;
int newIndexOfDroppedItem = dataList.IndexOf(draggedItem);
#if false
// Setting CurrentCell is guaranteed to make calls
// to CellValueChanged. You will have to do it elsewhere
// if you don't want that to happen in this here code block.
gridview.CurrentCell =
gridview
[
columnIndex: safeColumnIndex,
rowIndex: newIndexOfDroppedItem
];
#endif
}
catch (Exception e)
{
Debug.Assert(false, e.Message);
}
... where ...
private void SynchronizeCounts()
{
gridview.RowCount = dataList.Count;
if (gridview.AllowUserToAddRows)
{
gridview.RowCount++;
}
}
Our GitHub has a DataGridView VirtualMode example with a lot of diagnostic capability. If you'd like to do more code analysis of your DGV you're welcome to Clone or Download our Visual Studio solution.
I don't know a good way to explain this but what I have so far is I created a dataset and called it Database and created a table in it. Then I used a bindingsource and defined Database as its datasource.
Then I put the bindingsource as my datagrids data source and I put my textboxs text to the respective column in the bindingsource (e.g. textbox1 text=bindingsource - Name) so I can update my xml so I can click on the row and it fills the textboxes with the rows info and as i edit the text box it changes the data in the rows on the grid view so far an edit and update.
But I want to be able to have the option to update or not. So instead of I type and it changes I want to be able to fill in the boxes then choose to press update or cancel like a normal edit form.
This is my form
It's as if I could do something like
string = textboxname
then when I press update it then does selected row column "name" = string
ok so i figured it out thanks to devTimmy. i was looking in the wrong place and with the wrong ideas.
iv now done it so it takes the selected row index and uses that to fill in the correct row when its told which cell to fill. i didn't think to tell it which cell before so it failed and i thought no more of it.
here is my code.
int i;
i = dataGridView1.CurrentRow.Index;
dataGridView1.Rows[i].Cells[0].Value = Nametb.Text;
dataGridView1.Rows[i].Cells[1].Value = Locationtb.Text;
dataGridView1.Rows[i].Cells[2].Value = Infotb.Text;
dataGridView1.Rows[i].Cells[3].Value = dayvisittb.Text;
database1.WriteXml("Database.xml");
i have a DataGridView in my program that has a DataGridViewButtonColumn.
When a user clicks on that button, it shows more detail on another panel.
I am having trouble trying to do the following:
Click the "send" button on an ordering screen, that will send the order to the database, and the DataGridView will automatically update, but the user still will need to click on the corresponding column button on the row to view more detail.
What i want to do is that when the user clicks send, it will automatically find the row that corresponds to the orderID, and automatically click that button.
this is what i have:
foreach(DataGridViewRow row in OutOrderListGridView.Rows){
string compareID = row.Cells[0].Value.ToString();
if (compareID == OrderID)
{
row.Cells[10].Selected = true; <-- here i want to performClick() on the columnbutton(cell[10]) on the specific row.
}
}
Thank you.
I am not 100% sure i quite understand what you are after, but if i am correct you want to do the following provided the CellClick event is handling whatever you are trying to ultimately achieve.
Instead of 'performing' the click ( PerformClick() ) as such you can raise the event manually with known values... This should work for you but is slightly untested in your particular application given unknown variables. Code below also had to be slightly reworked to suit.
for (int i = 0; i < OutOrderListGridView.RowCount; i++)
{
if (OutOrderListGridView[0, i].Value.ToString() == OrderID)
{
OutOrderListGridView_CellClick(OutOrderListGridView, new DataGridViewCellEventArgs(10, i));
break;
}
}
The foreach loop had to be changed to a for loop in order to make use of the current index (position of the loop) that a foreach loop does not provide.
The string comparison did not need to be two stepped so was condensed accordingly.
I have also added a break to the loop as i would think that once you found your match, it would be pointless to iterate through the remaining rows.