How to keep virtualmode datagridview from calling cellvalueneeded while updating data in the background in C#

元气小坏坏 提交于 2020-06-13 06:09:21

问题


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;
    }
}

回答1:


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.



来源:https://stackoverflow.com/questions/60658921/how-to-keep-virtualmode-datagridview-from-calling-cellvalueneeded-while-updating

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!