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.
Related
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 have datagridview table. Whenever a user selects row and click 'Update Counter' button, counter value changes to 1 at top corner of window.
Now if a user selects the same row, the counter is updated incrementally which I don't want. I don't want counter to update on same row selection.
My code -
Int32 selectedRowCount = dataGridView1.Rows.GetRowCount(DataGridViewElementStates.Selected);
for (int i = 0; i < selectedRowCount; i++)
{
// need to insert condition so that counter is not updated when same row is selected again.
Counter.Text = Convert.ToString(i + 1);
}
}
I new to C# development, please provide changes.
Without seeing the rest of the code, or really understanding what you are doing (is this list going to change, what is the counter, can you identify the selected row easily?), all I can really suggest is maintaining a list of selected rows and checking that before updating the counter.
For example, in the for each:
if (selectedRows.Contains(rowIdentifier)) continue;//if you want to keep looping, otherwise break;
You may use the Tag property of the DataGridViewRow to identify rows already selected.
foreach (DataGridViewRow r in dataGridView1.SelectedRows)
if (r.Tag!=null) { Counter.Text=int.Parse(Counter.Text)+1 ; r.Tag=True ; }
I would add a condition statement that checks to see if the variable is dirty or has already been updated. This sounds similar to a concurrency issue or checking concurrency, I'm sure if you searched for ways to resolve concurrency issues the logic used would be similar. I agree with #johnc
I would utilize the onclick event then disable the ability to click the same row again.
OR use some other unique property of the row and check against it.
if(row.uniqueProperty == -1)
{
Counter.Text = Convert.ToString(i + 1);
}
Basically, I created a SQL table storing all values from selected row. If selected row value is something already there in the table, the counter doesn't get updated while the counter gets updated when unique row is selected.
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.
I'm trying to update a value in the database after the user finishes editing a cell. The DataGridView is populated manually (and dynamically) due to some complexities involved in DataBinding this particular data set.
In the event handler for CellEndEdit, I have called my method to update the value back to the database, and it works as planned. The trouble occurs when I attempt to repopulate the DataGridView. For reference, this method is called PopulateAccounts().
Once a value is entered in the cell, everything works perfectly if the user presses Enter on their keyboard or clicks another control on the form. However, clicking on another cell in the same DataGridView results in the following error:
Operation is not valid because it results in a reentrant call to the SetCurrentCellAddressCore function.
The PopulateAccounts() method includes DataGridView.Rows.Clear() which throws the error. I've researched this error in a few related SO questions, and it seems to be related to threading, which I'm clueless about. One suggested fix is to invoke my PopulateAccounts() method:
BeginInvoke(new MethodInvoker(PopulateAccounts));
This works, but it causes the DataGridView to select all cells preceding the one that was edited (see screenshot below)
Again, this only happens when clicking out of the cell and into another. Otherwise, i.e. pressing Enter or clicking on another control, it simply selects the first cell.
Here is the code for PopulateAccounts() for reference:
// Populates the DataGridView's account records.
private void PopulateAccounts()
{
dgvShed.Rows.Clear();
using (PassShedEntities context = new PassShedEntities(conString))
{
foreach (Account acct in context.Account.Where(a => a.Category_id == (int)lstCategories.SelectedValue))
{
dgvShed.Rows.Add();
foreach (DataGridViewColumn col in dgvShed.Columns)
{
Credential cred = (from c in context.Credential
join f in context.Field on c.Field_id equals f.Id
join a in context.Account on c.Account_id equals a.Id
where c.Account_id == acct.Id
where f.Label == col.Name
select c).SingleOrDefault();
dgvShed.Rows[dgvShed.Rows.Count - 1].Cells[col.Name].Tag = cred.Id;
dgvShed.Rows[dgvShed.Rows.Count - 1].Cells[col.Name].Value = cred.Value;
}
}
}
}
Once a value is entered in the cell, everything works perfectly if the user presses Enter on their keyboard or clicks another control on the form
The above actions doesn't change the CurrentCell and the grid repopulation works fine.
However, clicking on another cell in the same DataGridView results in the following error:
When you click on another cell the CurrentCell of the datagridview is changing and grid internally calls the SetCurrentCellAddressCore to do the cell change. This call internally raises CellEndEdit event which triggers the SetCurrentCellAddressCore again and this cycle goes on resulting in infinite loop. The DataGridView detects this loop and throws exception resulting in the error you have mentioned.
In the above case the my guess is that DataGridView somehow goofed up the selection of cells. You could simply clear the selection at the end of PopulateAccounts with DataGridView.ClearSelection() method.
I've been wrestling with just about the exact same problem and just came up with a solution that works with my specifics. I moved all the code from the CellEndEdit event to the CellValueChanged event (which fires right before the former) and now both options for ending the cell-edit: the enter-stroke and clicking off to another cell, work great.
I'm not sure what the perfectly proper way to use the events is, but this seems to be working!
I am kind of new to C# and Asp.Net, so this question might sound repetitive but I am not able to find a solution for this particular problem.
I have two buttons on my HTML page and a single class in .cs file. On one of the button clicks, I create a table programmatically (dynamically).
The table contains some checkboxes which are also created dynamically.
Table creation is one of the last tasks that I perform. Before that, I read several files and extract data from them to create the table. After the table is drawn, the user can select one or more checkboxes.
Now, how on second button click, can I know that which of the checkboxes were checked before the page reload? Currently I have made all these checkboxes member variables of the the only class that I have in the .cs file.
I tried adding checkbox event handler through C# code. But the handler is not getting called when the checkbox is checked. I don't want to set the 'autopostback' property of the checkbox to true since if thats set true, the page reloads after checking one of the checkboxes. User should be able to select multiple checkboxes.
Add your checkboxes dynamically and set a unique name for each checkbox. Checkboxes are only posted back to the server if the checkbox is checked, so you can test to see if it is checked by checking Request.Form to see if the name exists. For example, lets say you named your check boxes chk_[0-9] (i.e. chk_0, chk_1 etc till 9), you could check if they ticked by doing:
for(int i=0; i < 10; i++)
{
string chk_name = "chk_" + i.ToString();
if (Request.Form[chk_name] != null)
{
//checkbox is checked
}
else
{
//checkbox is not checked
}
}