Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DataGridView binding problem: "Index -1 does not have a value."

I have a datagridview bound to a binding source and a couple buttons on a form. One button adds an item to the binding source, the other removes the currently selected item. There's also an event handler that listens to the CurrentChanged event and updates the Enabled status of the Remove button.

Everything is hunky dory until I go to remove the last item from the datagridview. Then I see a very ugly exception:

   at System.Windows.Forms.CurrencyManager.get_Item(Int32 index)
   at System.Windows.Forms.CurrencyManager.get_Current()
   at System.Windows.Forms.DataGridView.DataGridViewDataConnection.OnRowEnter(DataGridViewCellEventArgs e)
   at System.Windows.Forms.DataGridView.OnRowEnter(DataGridViewCell& dataGridViewCell, Int32 columnIndex, Int32 rowIndex, Boolean canCreateNewRow, Boolean validationFailureOccurred)
   at System.Windows.Forms.DataGridView.SetCurrentCellAddressCore(Int32 columnIndex, Int32 rowIndex, Boolean setAnchorCellAddress, Boolean validateCurrentCell, Boolean throughMouseClick)
   at System.Windows.Forms.DataGridView.SetAndSelectCurrentCellAddress(Int32 columnIndex, Int32 rowIndex, Boolean setAnchorCellAddress, Boolean validateCurrentCell, Boolean throughMouseClick, Boolean clearSelection, Boolean forceCurrentCellSelection)\r\n   at System.Windows.Forms.DataGridView.MakeFirstDisplayedCellCurrentCell(Boolean includeNewRow)
   at System.Windows.Forms.DataGridView.OnEnter(EventArgs e)
   at System.Windows.Forms.Control.NotifyEnter()
   at System.Windows.Forms.ContainerControl.UpdateFocusedControl()
   at System.Windows.Forms.ContainerControl.AssignActiveControlInternal(Control value)
   at System.Windows.Forms.ContainerControl.ActivateControlInternal(Control control, Boolean originator)
   at System.Windows.Forms.ContainerControl.SetActiveControlInternal(Control value)
   at System.Windows.Forms.ContainerControl.SetActiveControl(Control ctl)
   at System.Windows.Forms.ContainerControl.set_ActiveControl(Control value)
   at System.Windows.Forms.Control.Select(Boolean directed, Boolean forward)
   at System.Windows.Forms.Control.SelectNextControl(Control ctl, Boolean forward, Boolean tabStopOnly, Boolean nested, Boolean wrap)
   at System.Windows.Forms.Control.SelectNextControlInternal(Control ctl, Boolean forward, Boolean tabStopOnly, Boolean nested, Boolean wrap)
   at System.Windows.Forms.Control.SelectNextIfFocused()
   at System.Windows.Forms.Control.set_Enabled(Boolean value)
   at Bug3324.Form1.HandleBindingSourceCurrentChanged(Object _sender, EventArgs _e) in D:\\Dev\\TempApps\\Bug3324\\Bug3324\\Form1.cs:line 41
   at System.Windows.Forms.BindingSource.OnCurrentChanged(EventArgs e)
   at System.Windows.Forms.BindingSource.CurrencyManager_CurrentChanged(Object sender, EventArgs e)
   at System.Windows.Forms.CurrencyManager.OnCurrentChanged(EventArgs e)

I've isolated the problem in a small scenario:

    private BindingSource m_bindingSource = new BindingSource();

    public Form1()
    {
        InitializeComponent();

        m_bindingSource.CurrentChanged += HandleBindingSourceCurrentChanged;
        m_bindingSource.DataSource = new BindingList<StringValue>();

        dataGridView1.DataSource = m_bindingSource;

        btnAdd.Click += HandleAddClick;
        btnRemove.Click += HandleRemoveClick;
    }

    private void HandleRemoveClick(object _sender, EventArgs _e)
    {
        m_bindingSource.RemoveCurrent();
    }

    private void HandleAddClick(object _sender, EventArgs _e)
    {
        m_bindingSource.Add(new StringValue("Some string"));
    }

    private void HandleBindingSourceCurrentChanged(object _sender, EventArgs _e)
    {
        // this line throws an exception when the last item is removed from
        // the datagridview
        btnRemove.Enabled = (m_bindingSource.Current != null);

    }
}

public class StringValue
{
    public string Value { get; set; }

    public StringValue(string value)
    {
        Value = value;
    }
}

Through pure experimentation, I've found that if I don't alter the button state in the CurrentChanged event handler, then everything works fine. So I suspect some sort of order of operations issue. But what? Why does attempting to make a change completely unrelated to the datagridview cause issues?

To make things even more interesting, the exception is usually harmless (or not showing up at all) if the program is started within VS with a debugger attached. But if it's executed on its own, there's a message box popping up with exception details.

I've tried handling the RowEnter event on the datagridview and found that in this scenario, it still thinks it has a row and attempts to retrieve the Current item from the binding source, but m_bindingSource.Current is already null. Why is this only an issue when the CurrentChanged event is handled?

Any and all help would be greatly appreciated. Thanks.

like image 985
Adam Lear Avatar asked Sep 10 '10 19:09

Adam Lear


3 Answers

Maybe not a real answer but I remember BindingSource and Datagrid being picky and brittle in this department. My general advice would be not to use RemoveCurrent but to delete the record from the underlying datastore.

like image 83
Henk Holterman Avatar answered Nov 06 '22 12:11

Henk Holterman


After some figgling, I've discovered some good news and some bad news for you:

The good news is that the (m_bindingSource.Current != null); part isn't the problem. That runs just fine.

The bad news is that the error is being caused by btnRemove.Enabled = false;

Do see what I mean, change: btnRemove.Enabled = (m_bindingSource.Current != null); To:

btnRemove.Enabled = false; 
if(m_bindingSource.Current != null)
   btnRemove.Enabled = true;

The code will die on the first line.

I'm not 100% sure why, but if you move btnRemove.Enabled = false up to the first line of the HandleRemoveClick method everything works as planned.

Hope that's helpful to you.

like image 2
Tyanna Avatar answered Nov 06 '22 13:11

Tyanna


I ran into the same problem today and found the workaround in this thread. Unfortunately I didn't like to split up the enable/disable code of the buttons. So I did some more research and found another solution, which worked for me.

All I did to resolve the IndexOutOfRangeException was to reset the bindings before I set the enable/disable of the buttons. (To optimize the performance you may check if the datasource count is zero or the position of the currency manager is -1. In other cases the reset isn't necessary.)

private void HandleBindingSourceCurrentChanged(object _sender, EventArgs _e)
{
    if(m_bindingSource.Count == 0) // You also can check position == -1
    {
      m_bindingSource.ResetBindings(false);
    }

    btnRemove.Enabled = (m_bindingSource.Current != null);
}

Hope that's helpful.

like image 2
Andy Avatar answered Nov 06 '22 12:11

Andy