4
votes

I'm trying to implement Drag & Drop for multiple rows on a standard DataGridView.

I'm selecting 3 rows holding down the control key and left mouse button. I then release the control key and what to select the rows to drag. So using the left mouse button I click and hold the button on one of the rows so I can start the drag motion.

However as soon as I click one of the rows, that row is selected and the other 2 rows are de-selected so I'm no longer moving 3 rows.

How can I get round this problem? I would only expect the row to be highlighted again if I released the left mouse button (without the ctrl key). This is how windows explorer works.

I'm using C#4.0 in Visual Studio 2010.

2

2 Answers

1
votes

I faced this exact issue on a project - how to allow the user to use standard methods to select a bunch of rows in a datagridview and then drag the selection to another datagrid view and drop it. I was able to build on an idea from http://www.codeproject.com/Tips/338594/Drag-drop-multiple-selected-rows-of-datagridview-w which is just to subclass the DataGridView as a "draggable" DataGridView, trapping the mouse actions when the user presses the button on an already-selected row, while allowing standard behavior for all other cases:

public partial class DraggableDataGridView : System.Windows.Forms.DataGridView {

    private Rectangle dragBoxFromMouseDown;

    protected override void OnMouseDown(MouseEventArgs e) {

        // Trap the case where the user pressed the left mouse button on an already-selected row
        // without <shift> or <control>

        if ((e.Button & MouseButtons.Left) == MouseButtons.Left
            && Control.ModifierKeys == Keys.None
            && this.SelectedRows.Count > 0
            && HitTest(e.X, e.Y).RowIndex >= 0
            && this.SelectedRows.Contains(this.Rows[HitTest(e.X, e.Y).RowIndex])
            ) {

                // User pressed the mouse button over an already-selected row without <shift> or <control> so we should 
                // consider drag and drop. Record a rectangle around that mouse location to possibly trigger drag
                // operation

                Debug.WriteLine("MouseDown inside selection");
                Size dragSize = SystemInformation.DragSize;
                dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2), e.Y - (dragSize.Height / 2)), dragSize);

        } else {
            // In other cases use the default behavior for datagridview
            Debug.WriteLine("MouseDown outside selection");
            dragBoxFromMouseDown = Rectangle.Empty;
            base.OnMouseDown(e);
        }
    }

    protected override void OnMouseUp(MouseEventArgs e) {
        base.OnMouseUp(e);
    }

    protected override void OnMouseMove(MouseEventArgs e) {
        if ((e.Button & MouseButtons.Left) == MouseButtons.Left) {
            // If the mouse moves outside the drag limit rectangle, start the drag.
            if (dragBoxFromMouseDown != Rectangle.Empty && !dragBoxFromMouseDown.Contains(e.Location)) {
                // Proceed with the drag and drop, passing in the selected rows
                DragDropEffects dropEffect =
                        this.DoDragDrop(this.SelectedRows, DragDropEffects.Move);
            }
        }
        base.OnMouseMove(e);
    }

    public DraggableDataGridView() {
        InitializeComponent();
    }

    protected override void OnPaint(PaintEventArgs pe) {
        base.OnPaint(pe);
    }
}

Having this code, and then substituting this custom component for all my DataGridViews in forms allowed me to use these as a standard drag source, and "plug" them into the standard drop functions for other components in the usual way.

0
votes

I don't have a much simpler solution but this should work. The whole idea is change the selected rows' BackColor to the SelectionBackColor and the selected rows' ForeColor to the SelectionForeColor. They look like they are selected. I suppose DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect to get the SelectedRows easily instead of selecting rows by clicking on the row headers. I also suppose the Form.KeyPreview = true

Here is my code:

//This is to copy the SelectedRows every time user releases the Control key
DataGridViewRow[] selectedRows;
private void dataGridView_CellClick(object sender, DataGridViewCellEventArgs e){
     if(selectedRows == null) return;
     if(!selectedRows.Contains(dataGridView.Rows[e.RowIndex])){
       //Restore the BackColor and ForeColor of the selected rows
       foreach(DataGridViewRow row in selectedRows){
           row.DefaultCellStyle.BackColor = dataGridView.DefaultCellStyle.BackColor;
           row.DefaultCellStyle.ForeColor = dataGridView.DefaultCellStyle.ForeColor;
       }
     }
}
private void form_KeyUp(object sender, KeyEventArgs e){
  if(e.KeyCode == Keys.ControlKey){
     selectedRows = new DataGridViewRow[dataGridView.SelectedRows.Count];
     dataGridView.SelectedRows.CopyTo(selectedRows,0);
     foreach(DataGridViewRow row in selectedRows){
        row.DefaultCellStyle.BackColor = dataGridView.DefaultCellStyle.SelectionBackColor;
        row.DefaultCellStyle.ForeColor = dataGridView.DefaultCellStyle.SelectionForeColor;
     }
  }
}

I hope you understand the whole idea to customize it and code yourself, such as restore the BackColor and ForeColor if the dataGridView is lost focus...

I hope it helps and someone can give a better solution!