Wahid Bitar
Wahid Bitar

Reputation: 14100

How could I Drag and Drop DataGridView Rows under each other?

I've DataGridView that bound a List<myClass> and i sort it by "Priority" property in "myClass". So I want to drag an "DataGridViewRow" to certain position to change it's "Priority" property.

How could I "Drag & Drop" DataGridView Rows ?. And how to handle this ?.

Upvotes: 33

Views: 59582

Answers (5)

Wolfgang Schorge
Wolfgang Schorge

Reputation: 197

The Answer from sander work's fine and i have modified with drag and drop full selected row, multiselect and scrolling on move rows on bottom or top.

private static int rowIndexFrom = -1;
private static int rowIndexTo = -1;
private static int[] dataGridViewSelectedRows = null;
/// <summary>
/// this select the rows all after moving them 
/// </summary>
private static bool selectAllAfterMoveRows = false;
private int seperatorHeight = 7;



private void dataGridView1_Paint(object sender, PaintEventArgs e)
{
    lblSeperator.Visible = false;
    if (rowIndexTo == -1) return;

    var dataGridView = sender as DataGridView;
    var rect = dataGridView1.GetRowDisplayRectangle(rowIndexTo, false);
    int posY = (rowIndexTo > rowIndexFrom && !dataGridView.Rows[rowIndexTo].IsNewRow)
               ? rect.Bottom : rect.Top;
    
    using (var pen = new Pen(Color.FromArgb(150, 33, 186, 71), seperatorHeight))
    {
        e.Graphics.DrawLine(pen, new Point(rect.Left, posY - 1), new Point(rect.Right, posY - 1));
    }              
}

private void dataGridView1_MouseDown(object sender, MouseEventArgs e)
{
    if ((e.Button & MouseButtons.Left) != MouseButtons.Left) { rowIndexTo = -1; return; }
    var dataGridView = sender as DataGridView;
    if (!dataGridView1.AllowDrop || dataGridView.ReadOnly || dataGridView.SelectedRows.Count == 0) { rowIndexTo = -1; return; }

    var hitTestInfo = dataGridView.HitTest(e.X, e.Y);
    if (hitTestInfo.ColumnIndex == -1)
    {
        //DISABLED: only drag and drop on row header
    }
    foreach (DataGridViewRow row in dataGridView.SelectedRows)
        if (row.IsNewRow) row.Selected = false;

    dataGridViewSelectedRows = dataGridView.SelectedRows.Cast<DataGridViewRow>().Select(row => row.Index).ToArray();
    if (dataGridView1.SelectedRows.Count == 0) { rowIndexFrom = -1; return; }

    rowIndexFrom = hitTestInfo.RowIndex;
    if (dataGridViewSelectedRows.Contains(rowIndexFrom))
    {
        dataGridView.EndEdit();
        Task.Factory.StartNew(() =>
        {
            dataGridView.Invoke(new Action(() =>
            {
                if (dataGridViewSelectedRows?.Length > 1)
                    foreach (int row in dataGridViewSelectedRows) dataGridView.Rows[row].Selected = true;

                if (rowIndexFrom != -1 && dataGridView.Rows.Count >= dataGridViewSelectedRows?.Length)
                    dataGridView.DoDragDrop(0, DragDropEffects.Move);
            }));
        });
    }
}

private void dataGridView1_DragOver(object sender, DragEventArgs e)
{
    if (rowIndexFrom == -1) return;
    var dataGridView = sender as DataGridView;
    if (!dataGridView.AllowDrop || dataGridView.ReadOnly || dataGridView.SelectedRows.Count <= 0) { rowIndexTo = -1; return; }
    e.Effect = DragDropEffects.Move;
    try
    {  
        Point clientPointer = dataGridView.PointToClient(new Point(e.X, e.Y));
        rowIndexTo = dataGridView.HitTest(clientPointer.X, clientPointer.Y).RowIndex;
        if (dataGridView.Rows[rowIndexTo].IsNewRow) { rowIndexTo--; }
        
    }
    catch
    {
        rowIndexTo = -1;
    }
    if (dataGridViewSelectedRows?.Length > 1 && dataGridViewSelectedRows.Contains(rowIndexTo) && dataGridViewSelectedRows.Contains(rowIndexTo - 1))
    {
        rowIndexTo = -1;
    }

    #region scroll
    Point clientPoint = dataGridView.PointToClient(new Point(e.X, e.Y));
   
    int mousepos = PointToClient(Cursor.Position).Y;
    //the mouse is hovering over the bottom
    if (mousepos > (dataGridView1.Location.Y + (dataGridView1.Height * 0.95)))
    {
        mouseMoveDown = true;
        //If the first row displayed isn't the last row in the grid
        if (dataGridView1.FirstDisplayedScrollingRowIndex < dataGridView1.RowCount - 1)
        {
            //Increase the first row displayed index by 1 (scroll down 1 row)
            int firstDisplRow = dataGridView1.FirstDisplayedScrollingRowIndex;
            if (dataGridView1.Rows[firstDisplRow + 1].Displayed)
            {
                Thread.Sleep(70);
                dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.FirstDisplayedScrollingRowIndex + 1;
            }
        }
    }
    //the mouse is hovering over the top
    if (mousepos < (dataGridView1.Location.Y + (dataGridView1.Height * 0.25)))
    {
        mouseMoveDown = false;
        //first row displayed not the first row
        if (dataGridView1.FirstDisplayedScrollingRowIndex > 1)
        {
            int firstDisplRow = dataGridView1.FirstDisplayedScrollingRowIndex;
            Thread.Sleep(60);
            dataGridView1.FirstDisplayedScrollingRowIndex -= 1;
        }
    }
    #endregion

    dataGridView.Refresh();
}

private void dataGridView1_DragDrop(object sender, DragEventArgs e)
{
    if (rowIndexTo == -1) return;
    var dataGridView = sender as DataGridView;
    if (!dataGridView.AllowDrop || dataGridView.ReadOnly || dataGridView.SelectedRows.Count <= 0) { rowIndexTo = -1; dataGridView.Refresh(); return; }
    if (rowIndexTo == rowIndexFrom && dataGridView.SelectedRows.Count == 1) { rowIndexTo = -1; dataGridView.Refresh(); return; }
    if (rowIndexTo == -1) { dataGridView.Refresh(); return; }

    if (e.Effect == DragDropEffects.Move)
    {
        dataGridView.Invoke(new Action(() =>
        {
            List<DataGridViewRow> rowsToMove = dataGridView.SelectedRows.Cast<DataGridViewRow>().OrderBy(row => row.Index).ToList();

            bool indexInbetweenRows = rowIndexTo > rowsToMove.Min(row => row.Index) && rowIndexTo < rowsToMove.Max(row => row.Index);
            if (indexInbetweenRows) rowIndexTo = rowsToMove.Min(row => row.Index);

            rowIndexTo = rowIndexFrom >= rowIndexTo ? rowIndexTo : rowIndexTo - (rowsToMove.Count - 1);
            foreach (DataGridViewRow row in rowsToMove) dataGridView.Rows.Remove(row);
            int insertIndex = rowIndexTo;
            rowsToMove.ForEach(row => dataGridView.Rows.Insert(insertIndex++, row));
            dataGridView.ClearSelection();
            dataGridView.CurrentCell = dataGridView.Rows[rowIndexTo].Cells[dataGridView.FirstDisplayedCell.ColumnIndex];
            //select all rows after drag and drop
            if (selectAllAfterMoveRows)
            {
                rowsToMove.ForEach(row => row.Selected = true);
            }
            else
            {
                rowsToMove.First(row => row.Selected = true);
            }                  
            
        }));
    }

    rowIndexTo = -1;
    dataGridView.Refresh();
}

private void dataGridView1_MouseLeave(object sender, EventArgs e)
{
    rowIndexTo = -1; 
    dataGridView1.Refresh();
}

enter image description here

Upvotes: 0

Sander
Sander

Reputation: 47

I have been optimizing this based on the other answers to work in all the edge cases on a unbound datagridview.

  • Drag from one of the selected RowHeaderColumn.
  • Works with multiple selected rows.
  • Provides visual feedback where the rows will be dropped.
  • It keeps the rows selected after the drop.

To enable it to a DataGridView:

dataGridView1.AllowDrop = true;
dataGridView1.EnableDragAndDropRows();

Make a new DataGridViewExtension.cs class

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

public static class DataGridViewExtension
{
    // DataGridView Row Reorder
    public static void EnableDragAndDropRows(this DataGridView dataGridView)
    {
        dataGridView.Paint += new PaintEventHandler(GridRowReorder_Paint);
        dataGridView.MouseDown += new MouseEventHandler(GridRowReorder_MouseDown);
        dataGridView.DragOver += new DragEventHandler(GridRowReorder_DragOver);
        dataGridView.DragDrop += new DragEventHandler(GridRowReorder_DragDrop);
        dataGridView.DragLeave += (s, e) => { rowIndexTo = -1; dataGridView.Refresh(); };
    }
    private static int rowIndexFrom = -1;
    private static int rowIndexTo = -1;
    private static int[] dataGridViewSelectedRows = null;
    private static void GridRowReorder_Paint(object sender, PaintEventArgs e)
    {
        if (rowIndexTo == -1) return;

        var dataGridView = sender as DataGridView;
        var rect = dataGridView.GetRowDisplayRectangle(rowIndexTo, false);
        int posY = (rowIndexTo > rowIndexFrom && !dataGridView.Rows[rowIndexTo].IsNewRow)
                   ? rect.Bottom : rect.Top;

        using (var pen = new Pen(Color.FromArgb(150, 33, 186, 71), 7))
            e.Graphics.DrawLine(pen, new Point(rect.Left, posY - 1), new Point(rect.Right, posY - 1));
    }
    private static void GridRowReorder_MouseDown(object sender, MouseEventArgs e)
    {
        if ((e.Button & MouseButtons.Left) != MouseButtons.Left) { rowIndexTo = -1; return; }
        var dataGridView = sender as DataGridView;
        if (!dataGridView.AllowDrop || dataGridView.ReadOnly || dataGridView.SelectedRows.Count == 0) { rowIndexTo = -1; return; }

        var hitTestInfo = dataGridView.HitTest(e.X, e.Y);
        if (hitTestInfo.ColumnIndex == -1)
        {
            foreach (DataGridViewRow row in dataGridView.SelectedRows)
                if (row.IsNewRow) row.Selected = false;

            dataGridViewSelectedRows = dataGridView.SelectedRows.Cast<DataGridViewRow>().Select(row => row.Index).ToArray();
            if (dataGridView.SelectedRows.Count == 0) { rowIndexFrom = -1; return; }

            rowIndexFrom = hitTestInfo.RowIndex;
            if (dataGridViewSelectedRows.Contains(rowIndexFrom))
            {
                dataGridView.EndEdit();
                Task.Factory.StartNew(() =>
                {
                    dataGridView.Invoke(new Action(() =>
                    {
                        if (dataGridViewSelectedRows?.Length > 1)
                            foreach (int row in dataGridViewSelectedRows) dataGridView.Rows[row].Selected = true;

                        if (rowIndexFrom != -1 && dataGridView.Rows.Count >= dataGridViewSelectedRows?.Length)
                            dataGridView.DoDragDrop(0, DragDropEffects.Move);
                    }));
                });
            }
        }
    }
    private static void GridRowReorder_DragOver(object sender, DragEventArgs e)
    {
        if (rowIndexFrom == -1) return;
        var dataGridView = sender as DataGridView;
        if (!dataGridView.AllowDrop || dataGridView.ReadOnly || dataGridView.SelectedRows.Count <= 0) { rowIndexTo = -1; return; }
        e.Effect = DragDropEffects.Move;
        try
        {
            Point clientPoint = dataGridView.PointToClient(new Point(e.X, e.Y));
            rowIndexTo = dataGridView.HitTest(clientPoint.X, clientPoint.Y).RowIndex;
            if (dataGridView.Rows[rowIndexTo].IsNewRow) { rowIndexTo--; }
        }
        catch
        {
            rowIndexTo = -1;
        }
        if (dataGridViewSelectedRows?.Length > 1 && dataGridViewSelectedRows.Contains(rowIndexTo) && dataGridViewSelectedRows.Contains(rowIndexTo - 1))
        {
            rowIndexTo = -1;
        }
        dataGridView.Refresh();
    }
    private static void GridRowReorder_DragDrop(object sender, DragEventArgs e)
    {
        if (rowIndexTo == -1) return;
        var dataGridView = sender as DataGridView;
        if (!dataGridView.AllowDrop || dataGridView.ReadOnly || dataGridView.SelectedRows.Count <= 0) { rowIndexTo = -1; dataGridView.Refresh(); return; }
        if (rowIndexTo == rowIndexFrom && dataGridView.SelectedRows.Count == 1) { rowIndexTo = -1; dataGridView.Refresh(); return; }
        if (rowIndexTo == -1) { dataGridView.Refresh(); return; }

        if (e.Effect == DragDropEffects.Move)
        {
            dataGridView.Invoke(new Action(() =>
            {
                List<DataGridViewRow> rowsToMove = dataGridView.SelectedRows.Cast<DataGridViewRow>().OrderBy(row => row.Index).ToList();
                
                bool indexInbetweenRows = rowIndexTo > rowsToMove.Min(row => row.Index) && rowIndexTo < rowsToMove.Max(row => row.Index);
                if (indexInbetweenRows) rowIndexTo = rowsToMove.Min(row => row.Index);

                rowIndexTo = rowIndexFrom >= rowIndexTo ? rowIndexTo : rowIndexTo - (rowsToMove.Count - 1);
                foreach (DataGridViewRow row in rowsToMove) dataGridView.Rows.Remove(row);
                int insertIndex = rowIndexTo;
                rowsToMove.ForEach(row => dataGridView.Rows.Insert(insertIndex++, row));
                dataGridView.ClearSelection();
                dataGridView.CurrentCell = dataGridView.Rows[rowIndexTo].Cells[dataGridView.FirstDisplayedCell.ColumnIndex];
                rowsToMove.ForEach(row => row.Selected = true);
            }));
        }

        rowIndexTo = -1;
        dataGridView.Refresh();
    }
}

Upvotes: 1

BoltBait
BoltBait

Reputation: 11489

I needed to enable Drag-and-Drop to all DataGridView controls in my project. So I extended the DataGridView control using a combination of both answers on this thread. Here is the code:

internal class DataGridViewEx: DataGridView
{
    private Rectangle dragBoxFromMouseDown;
    private int rowIndexFromMouseDown;
    private int rowIndexOfItemUnderMouseToDrop;

    public DataGridViewEx()
    {
        AllowDrop = true;
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
        {
            // If the mouse moves outside the rectangle, start the drag.
            if (dragBoxFromMouseDown != Rectangle.Empty &&
                !dragBoxFromMouseDown.Contains(e.X, e.Y))
            {

                // Proceed with the drag and drop, passing in the list item.                    
                DragDropEffects dropEffect = this.DoDragDrop(
                this.Rows[rowIndexFromMouseDown],
                DragDropEffects.Move);
            }
        }
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        // Get the index of the item the mouse is below.
        rowIndexFromMouseDown = this.HitTest(e.X, e.Y).RowIndex;
        if (rowIndexFromMouseDown != -1)
        {
            // Remember the point where the mouse down occurred. 
            // The DragSize indicates the size that the mouse can move 
            // before a drag event should be started.                
            Size dragSize = SystemInformation.DragSize;

            // Create a rectangle using the DragSize, with the mouse position being
            // at the center of the rectangle.
            dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2),
                                                           e.Y - (dragSize.Height / 2)),
                                dragSize);
        }
        else
            // Reset the rectangle if the mouse is not over an item in the ListBox.
            dragBoxFromMouseDown = Rectangle.Empty;
    }

    protected override void OnDragOver(DragEventArgs drgevent)
    {
        drgevent.Effect = DragDropEffects.Move;
        base.OnDragOver(drgevent);
    }

    protected override void OnDragDrop(DragEventArgs drgevent)
    {
        // The mouse locations are relative to the screen, so they must be 
        // converted to client coordinates.
        Point clientPoint = this.PointToClient(new Point(drgevent.X, drgevent.Y));

        // Get the row index of the item the mouse is below. 
        rowIndexOfItemUnderMouseToDrop =
            this.HitTest(clientPoint.X, clientPoint.Y).RowIndex;

        // If the drag operation was a move then remove and insert the row.
        if (drgevent.Effect == DragDropEffects.Move)
        {
            if (rowIndexOfItemUnderMouseToDrop < 0)
            {
                return;
            }
            DataGridViewRow rowToMove = drgevent.Data.GetData(
                typeof(DataGridViewRow)) as DataGridViewRow;
            // you can't move a new row
            if (rowToMove.IsNewRow)
            {
                return;
            }
            this.Rows.RemoveAt(rowIndexFromMouseDown);
            this.Rows.Insert(rowIndexOfItemUnderMouseToDrop, rowToMove);

        }
        base.OnDragDrop(drgevent);
    }
}

Hope this helps!

Upvotes: 0

Wilhelm
Wilhelm

Reputation: 306

The Answer from 'Wahid Bitar' helped me a lot. I cannot comment the above, so a small addition: If deleting a row is unwanted as commented by 'neminem': Just add to DragDrop Event just before the lines that finally moves it:

        if (rowIndexOfItemUnderMouseToDrop < 0 )
        {
            return;
        }        
       dataGridView1.Rows.RemoveAt(rowIndexFromMouseDown);
       dataGridView1.Rows.Insert(rowIndexOfItemUnderMouseToDrop, rowToMove);

Upvotes: 4

Wahid Bitar
Wahid Bitar

Reputation: 14100

I found this code sample on MSDN

Note the following:

1). DataGridView property AllowDrop must be set to true (default is false).

2). The example below works out of the box when the DataGridView is NOT data-bound. Otherwise it will throw an InvalidOperationException. If it is databound, you should manipulate the order of items in the DataSource.

private Rectangle dragBoxFromMouseDown;
private int rowIndexFromMouseDown;
private int rowIndexOfItemUnderMouseToDrop;
private void dataGridView1_MouseMove(object sender, MouseEventArgs e)
{
    if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
    {
        // If the mouse moves outside the rectangle, start the drag.
        if (dragBoxFromMouseDown != Rectangle.Empty &&
            !dragBoxFromMouseDown.Contains(e.X, e.Y))
        {

            // Proceed with the drag and drop, passing in the list item.                    
            DragDropEffects dropEffect = dataGridView1.DoDragDrop(
            dataGridView1.Rows[rowIndexFromMouseDown],
            DragDropEffects.Move);
        }
    }
}

private void dataGridView1_MouseDown(object sender, MouseEventArgs e)
{
    // Get the index of the item the mouse is below.
    rowIndexFromMouseDown = dataGridView1.HitTest(e.X, e.Y).RowIndex;
if (rowIndexFromMouseDown != -1)
    {
        // Remember the point where the mouse down occurred. 
     // The DragSize indicates the size that the mouse can move 
     // before a drag event should be started.                
        Size dragSize = SystemInformation.DragSize;

        // Create a rectangle using the DragSize, with the mouse position being
        // at the center of the rectangle.
        dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2),
                                                       e.Y - (dragSize.Height / 2)),
                            dragSize);
    }
    else
        // Reset the rectangle if the mouse is not over an item in the ListBox.
        dragBoxFromMouseDown = Rectangle.Empty;
}

private void dataGridView1_DragOver(object sender, DragEventArgs e)
{
    e.Effect = DragDropEffects.Move;
}

private void dataGridView1_DragDrop(object sender, DragEventArgs e)
{
    // The mouse locations are relative to the screen, so they must be 
    // converted to client coordinates.
    Point clientPoint = dataGridView1.PointToClient(new Point(e.X, e.Y));

    // Get the row index of the item the mouse is below. 
    rowIndexOfItemUnderMouseToDrop =
        dataGridView1.HitTest(clientPoint.X, clientPoint.Y).RowIndex;

    // If the drag operation was a move then remove and insert the row.
    if (e.Effect== DragDropEffects.Move)
    {
        DataGridViewRow rowToMove = e.Data.GetData(
            typeof(DataGridViewRow)) as DataGridViewRow;
        dataGridView1.Rows.RemoveAt(rowIndexFromMouseDown);
        dataGridView1.Rows.Insert(rowIndexOfItemUnderMouseToDrop, rowToMove);

    }
}

Upvotes: 66

Related Questions