I am having a bit of an issue with the datagridview's CellEndEdit event. While I understand the concept of what the issue actually is, any attempt at circumventing it seems to fail.
Basically, I have a datagridview, in the CellEndEdit event, I make a check against the database to make sure the entry is not a duplicate. If it is, I prompt the user with a messagebox to tell them they can't enter duplicates, I then change the value back to its original state/value programmatically, and return the cell to an "Edit" state.
My understanding is that the fact that i'm changing the value programatically is why the event fires twice. To circumvent, I set a flag upon first entering the event, then prompt + set + re-edit, then set the flag to false. This does not work... can anyone tell me why or how I can make this happen?
Here's the event code:
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if(e.ColumnIndex == this.dataGridView1.Columns["Name"].ColumnIndex)
{
if(!this.CellBeingEdited)
{
string NewName = this.dataGridView1.Rows[e.RowIndex].Cells["Name"].Value.ToString();
//-== DATABASE CODE REMOVED ==-
bool IsDuplicate = ...;
if(IsDuplicate)
{
MessageBox.Show("Cannot have duplicate item names at this level!");
this.dataGridView1.CurrentCell = this.dataGridView1.Rows[e.RowIndex].Cells["Name"];
this.CellBeingEdited = true;
this.dataGridView1.CurrentCell.Value = this.LastEditedRowName;
this.CellBeingEdited = false;
this.dataGridView1.BeginEdit(false);
return;
}
}
}
}
This bit of code does not fire twice when I edit a value in a row :
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
string test = "test";
if (this.dataGridView1.Rows[e.RowIndex].Cells[0].Value.ToString() == test)
{
this.dataGridView1.CurrentCell = this.dataGridView1.Rows[e.RowIndex].Cells[0];
this.dataGridView1.CurrentCell.Value = "not test";
this.dataGridView1.BeginEdit(false);
return;
}
}
Perhaps are you calling the event elsewhere?
Related
I am using a datagradview and I am wanting to prompt the user to save the current row before moving off I have been trying to use the following event but I seem to be in a circular loop when I hit my save event.
private void dgStock_SelectionChanged(object sender, EventArgs e)
{
if (isDirty == true)
{
isSavedFromRow = true;
btnSaveDetails_Click(sender, e);
isDirty = false;
}
}
The problem with the selection changed event is this happens once the row has changed so the user could think their saving the new row and not the current row.
I also seem to be caught in a circular loop some how has the messagebox box is getting fired numerious times I am only setting the isDirty to true if the user enters key down on my textboxes.
if (isDirty == true)
{
DialogResult _result = MessageBox.Show("Are you sure you wish to upate Live Product Information", "Save Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
if (_result == DialogResult.Yes)
{
updateStock();
_trackChanges.Clear();
isDirty = false;
}
}
This is me setting my dirty flag on key down felt this was best way to avoid the problem I seem to be having.
private void txtDescription_KeyDown(object sender, KeyEventArgs e)
{
isDirty = true;
btnSaveDetails.Enabled = true;
}
Your code sample suggests that your save stuff is defined in your save button click. If you change your structure to something like this
private void SaveClick(object sender, EventArgs e)
{
DoSaveStuff();
}
private void DoSaveStuff()
{
// Do your save stuff
}
i.e. pull your save stuff out into a method. You can call DoSaveStuff() whenever you need to save, e.g.
private void SelectionChanged(object sender, EventArgs e)
{
// Do stuff
if (condition)
{
DoSaveStuff();
}
}
The advantage of this approach is you're capturing the aspect of the behaviour you're interested in - the save stuff - rather than the whole button click. Furthermore, you click a button to save, your application doesn't, it simply saves in certain circumstances, e.g. when you click a button or when something changes.
As MSDN says the SelectionChangedEvent occurs whenever the selection has changed. So, whenever this happens you could check your original selection to see if it has changed and then save if it has. Maybe doing something like this
private void SelectionChanged(object sender, EventArgs e)
{
bool hasContentsChanged = // determine if your content has changed
if (hasContentsChanged)
{
DoSaveStuff();
}
}
An advantage of this approach is that you'd only have to save if it really had changed, i.e. if the original text != new text, rather than in all cases.
In my WinForms application, I have the following logic triggered by a button press:
private void ExecuteSelectedConsoleCommand()
{
var commandsRow = GetCommandsRow();
var consoleCommand = GetConsoleCommand(commandsRow);
Task.Factory.StartNew(() =>
{
var runningCommandRow =
runtimeDataSet.RunningCommands.AddRunningCommandsRow(Guid.NewGuid(),
consoleCommand.OneLineDescription);
consoleCommand.Run(null);
runningCommandRow.Delete();
});
}
A BindingSource is used to let a DataGridView automatically update itself.
As of now, without the following hack, I get an error saying the the "index 0 is invalid".
// prevents error when removing last row from data bound datagridview
var placeholder = runtimeDataSet
.RunningCommands
.AddRunningCommandsRow(Guid.NewGuid(), "PLACEHOLDER");
With the above code, which causes there to always be at least one row in the DataGridView, it works fine.
How do I fix this?
Note: This seems like something many others would have run into, but my web searches have failed..
I have had the same problem. After some trial and error I have discovered that you have to handle several events of the datagridview to make this go away, and even that requires some error ignoring. I don't remeber precisely what I have done to circumvent this error but I think the following to snippets may offer some insight (explanations in comments):
Remove row:
//When I remove a row that has been added, but not commited return from the function
//run the update function only if the row is commited (databound)
private void dgvTest_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e) {
if (_lastDataRow == null || _lastDataRow.RowState == DataRowState.Added)
return;
UpdateRowToDatabase();
}
Row validating:
//I got the kind of Index not valid or other index errors
//RowValidating is fired when entering the row and when leaving it
//In my case there was no point in validating on row enter
private void dgvTest_RowValidating(object sender, DataGridViewCellCancelEventArgs e) {
if (Disposing)
return;
if (!dgvTest.IsCurrentRowDirty) {
return;
}
try {
var v = dgvTest.Rows[e.RowIndex].DataBoundItem;
} catch {
return;
}
}
Data error:
//I had to trap some errors and change things accordingly
private void dgvTest_DataError(object sender, DataGridViewDataErrorEventArgs e) {
if (e.Exception.Message.Contains("Index") && e.Exception.Message.Contains("does not have a value")) {
//when editing a new row, after first one it throws an error
//cancel everything and reset to allow the user to make the desired changes
bindingSource.CancelEdit();
bindingSource.EndEdit();
bindingSource.AllowNew = false;
bindingSource.AllowNew = true;
e.Cancel = true;
dgvTest.Visible = false;
dgvTest.Visible = true;
}
}
Row enter:
//Some problemes occured when entering a new row
//This resets the bindingsource's AllowNew property so the user may input new data
private void dgvTest_RowEnter(object sender, DataGridViewCellEventArgs e) {
if (!_loaded)
return;
if (e.RowIndex == dgvTest.NewRowIndex)
if (String.IsNullOrWhiteSpace(dgvTest.Rows[e.RowIndex == 0 ? e.RowIndex : e.RowIndex - 1].Cells[_idColumn].Value.ToStringSafe())) {
bindingSource.CancelEdit();
bindingSource.AllowNew = false;
bindingSource.AllowNew = true;
}
}
Hope this helps!
Yesterday I try to implement a new listview that support sub-item edit, my solution is to show a textbox when double click the sub-item. The key code as following:
protected override void OnDoubleClick(EventArgs e)
{
Point pt = this.PointToClient(Cursor.Position);
ListViewItem curItem;
int subItemIndex = GetSubItemAt(pt.X, pt.Y, out curItem);
DoubleClickEventArgs args = new DoubleClickEventArgs(subItemIndex);
base.OnDoubleClick(args);
if (subItemIndex>=0 && !args.Cancel)
{
//StartEdit(...);
}
}
public void EndEdit(bool acceptChanges)
{
//validation
.................
.................
AfterSubItemEventArgs e = new AfterSubItemEventArgs(this.SelectedItems[0], m_editSubItemIndex, this.SelectedItems[0].SubItems[m_editSubItemIndex].Text, m_textbox.Text, false);
OnAfterSubItemEdit(e);
if (e.Cancel)
{
//....
}
else
{
//set new value
}
m_textbox.Visible = false;
m_editSubItemIndex = -1;
}
OnAfterSubItemEdit is a event that user can do some validations or other operations. I add a check in this method, if the new value exist, I will show a messagebox to user firstly, then hide the textbox. But now, the problem comes, when i move the mouse, the listview items can be selected, I don't how to solve this issue, I tried my best to find out the way, but failed. So, please help me!
Listview has a LabelEdit property; when you set it "true", then in an event handler you can call Listview.Items[x].BeginEdit(), and edit an item. As an example, you can handle ListView.DoubleClick event and call BeginEdit right there:
private void Form1_Load(object sender, System.EventArgs e)
{
listView1.LabelEdit = true;
}
private void listView1_DoubleClick(object sender, System.EventArgs e)
{
if(this.listView1.SelectedItems.Count==1)
{
this.listView1.SelectedItems[0].BeginEdit();
}
}
The problem is that your form still calls the DoubleClick event whether the value exists or not. Add appropriate condition before calling base DoubleClick in your code, i.e.:
if(!new value exists)
base.OnDoubleClick(args);
Is it possible to cancel the SelectedIndexChange event for a listbox on a winforms application? This seems like such a logical thing to have that I must be overlooking some easy feature. Basically, I have been popping up a message box asking if the user really wants to move to another item, as this will change the UI and I don't want their changes to be lost. I'd like to be able to cancel the event in case the user has not saved what they are working on. Is there a better way of doing this?
You cannot cancel it.
What I did just a couple of days ago was to have a variable with the latest selected index. Then when the event fires, you ask the user if he wants to save, this is done in the eventhandler. If the user selected "Cancel" you change the id again.
The problem is that this will make the event fire once again. So what i've used is a bool just saying "Inhibit". And at the top of the eventhandler I have:
if(Inhibit)
return;
Then below this where you ask the question you do something like this:
DialogResult result = MessageBox.Show("yadadadad", yadada cancel etc);
if(result == DialogResult.Cancel){
Inhibit = true; //Make sure that the event does not fire again
list.SelectedIndex = LastSelectedIndex; //your variable
Inhibit = false; //Enable the event again
}
LastSelectedIndex = list.SelectedIndex; // Save latest index.
This is exactly #Oskar Kjellin 's method, but with a twist. That is, one variable less and with a selected index changed event that really behaves like a selected index changed event. I often wonder why is selected index changed event getting fired even if I click on the exact same selected item. Here it doesn't. Yes it's a deviation, so be doubly sure if you want this to be there.
int _selIndex = -1;
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listBox1.SelectedIndex == _selIndex)
return;
if (MessageBox.Show("") == DialogResult.Cancel)
{
listBox1.SelectedIndex = _selIndex;
return;
}
_selIndex = listBox1.SelectedIndex;
// and the remaining part of the code, what needs to happen when selected index changed happens
}
I just ran into this exact problem. What I did is when the user makes changes, I set ListBox.Enabled = false; This disallows them to select a different index. Once they either save or discard their changes, I set ListBox.Enabled = true; Probably not as slick as a prompt, but it gets the job done.
More elegant, use the Tag property:
if ((int)comboBox.Tag == comboBox.SelectedIndex)
{
// Do Nothing
}
else
{
if (MessageBox.Show("") == DialogResult.Cancel)
{
comboBox.SelectedIndex = (int)comboBox.Tag;
}
else
{
// Reset the Tag
comboBox.Tag = comboBox.SelectedIndex;
// Do what you have to
}
}
The SelectedIndexChanged cannot be cancelled. So you only have one real option:
private int? currentIndex;
public void ListBox_SelectedIndexChanged(sender, EventArgs args) {
if (currentIndex.HasValue && currentIndex.Value != listBox1.SelectedIndex) {
var res = MessageBox.Show("Do you want to cancel edits?", "Cancel Edits", MessageBoxButtons.YesNo);
if (res == DialogResult.Yes) {
currentIndex = (listBox1.SelectedIndex == -1 ? null : (int?) listBox1.SelectedIndex);
} else {
listBox1.SelectedIndex = currentIndex.Value;
}
}
}
This is my way to cancel SelectionChange for ComboBox. I think it could also fit to ListBox.
private bool comboBox_CancelSelection = false;
private int comboBox_LastSelectedIndex = -1;
private void comboBox_SelectedIndexChanged(object sender, EventArgs e) {
if (comboBox_CancelSelection) {
comboBox_CancelSelection = false;
return ;
}
// Handle Event
if (!comoBox_CancelSelection) {
comboBox_LastSelectedIndex = comboBox.SelectedIndex;
} else {
comboBox.SelectedIndex = comboBox_LastSelectedIndex;
}
}
I have a Datagridview, on which I need to validate the user input on certain rows, and change it according to it's value. As a example, if the user input a product code that doesn't exist on the DB, a search for a product dialog raises and search for the product and returns the correct code.
So after reading around a lot, I decided to handle the CellFormating event: but I ran into two problems:
The e.value is set with the right value, but it doesn't persist
And the event is raised like a millions of time, and sometimes ran into a SO exception.
And that's why I put a mbox in the event handler and now the programs does nothing but shows that mbox.
The point here is, even when the forms is newly created, the event raises a lot, and, every time I move the mouse pointer over any cell, the event is raised again.
What's the best option here? below is the code inside the event handler:
void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
try
{
if (e.ColumnIndex == 0 && !dataGridView1.Rows[e.RowIndex].IsNewRow)
{
if (!Rep.Cajero_ProductoExiste(double.Parse(e.Value.ToString())))
{
BuscarEIngresarProducto(ref e);
}
}
MessageBox.Show("Event handler raised");
}
catch
{
e.FormattingApplied = false;
}
}
Where Rep.Cajero_ProductoExiste is a method that returns a bool=true if the product exists, and the method BuscarEIngresarProducto() is as below:
private void BuscarEIngresarProducto(ref DataGridViewCellFormattingEventArgs e)
{
Busqueda b = new Busqueda(Rep, 2);
if (b.ShowDialog() == DialogResult.OK)
{
e.Value = b.ProductoCodigo;
dataGridView1.CurrentRow.Cells["pk"].Value = b.Producto;
e.FormattingApplied = true;
}
}
You can try to handle CurrentCellDirtyStateChanged and check for IsCurrentCellDirty property. If it's true - validate input.