BACKGROUND

Sometimes applications need to allow users to split items up into separate groupings, like when you want to select teams for a dodge ball game. One way to handle this scenario is through moving data back and forth between a couple RadGridView controls. There is a multitude of ways of implementing this. One way is that you could add buttons between two grids with buttons labeled with arrows denoting the direction in which to move selected data.

ArrowsForm

Figure 1 - moving data between grids with buttons

On the other hand, in order to achieve a better user experience, implementing drag and drop functionality between the grids is ideal.

draganddrop

Figure 2 - moving data using drag and drop

In this blog post, I will show you how to extend the RadGridView control to enable drag and drop functionality between two grids, whether it be an unbound grid, bound to a binding list of objects, or bound to a DataSet. You’ll also get some reusable code out of it.

 

IMPLEMENTATION

Much of this code is inspired from a variety of sources, it comes from reviewing code in the RadControls for WinForms demo application, from forum posts as well as from community submitted code. I made it a goal to extend the grid so that drag and drop could be used whether the grid is in bound or unbound mode. I also wanted to support the ability to drag and drop multiple rows at a time.

To get started, open Visual Studio 2012 and create a new RadControls for Windows Forms project. Once this has been created, add a new class called “DragAndDropRadGrid.cs”. Modify the class to extend the RadGridView control through inheritance:

public class DragAndDropRadGrid : RadGridView

Figure  3  - inheriting the RadGridView control

Now it’s time to get down to business. The good news is the drag and drop functionality is made easy using the built-in RadGridViewDragDropService as the plumbing code is already handled for you, you only need to handle events emanating from this service.

 

DRAG AND DROP EVENTS

Create a default constructor for the DragAndDropRadGrid class. In this constructor we will grab a reference to the RadGridView RadDragDropService and generate event handler stubs for a few of the service’s events.

public DragAndDropRadGrid()

{

   this.MultiSelect = true;

                       

   //handle drag and drop events for the grid through the DragDrop service

   RadDragDropService svc =

                this.GridViewElement.GetService<RadDragDropService>();

   svc.PreviewDragStart += svc_PreviewDragStart;

   svc.PreviewDragDrop += svc_PreviewDragDrop;

   svc.PreviewDragOver += svc_PreviewDragOver;

}

Figure  4  - grabbing a reference to the RadDragDropService of the RadGridView

The PreviewDragStart event is fired once the Drag and Drop service on the grid is started. In this case, we simply want to tell the drag and drop service if the drag operation can move forward. Implement the PreviewDragStart event handler as follows:

//required to initiate drag and drop when grid is in bound mode

private void svc_PreviewDragStart(object sender, PreviewDragStartEventArgs e)

{

    e.CanStart = true;

}

Figure  5  - implementing the PreviewDragStart event handler

The next event we will handle is the PreviewDragOver event. This event allows you to control on what targets the row being dragged can be dropped on. In this case, as long as it’s being dropped somewhere on the target grid, we are good with it. Implement the handler as follows:

private void svc_PreviewDragOver(object sender, RadDragOverEventArgs e)

{

   if (e.DragInstance is GridDataRowElement)

   {

       e.CanDrop = e.HitTarget is GridDataRowElement ||

                   e.HitTarget is GridTableElement ||

                   e.HitTarget is GridSummaryRowElement;

   }

}

Figure  6  - implementing the PreviewDragOver event handler

The last event we want to handle in our implementation is the PreviewDragDrop event. This event allows you to get a handle on all the aspects of the drag and drop operation, the source (drag) grid, the destination (target) grid, as well as the row being dragged. This is where we will initiate the actual physical move of the row(s) from one grid to the other. Implement the handler as follows:

//gather drag/source grid and target/destination information and initiate

//the move of selected rows

private void svc_PreviewDragDrop(object sender, RadDropEventArgs e)

{

    var rowElement = e.DragInstance as GridDataRowElement;

 

    if (rowElement == null)

    {

        return;

    }

    e.Handled = true;

 

    var dropTarget = e.HitTarget as RadItem;

    var targetGrid = dropTarget.ElementTree.Control as RadGridView;

    if (targetGrid == null)

    {

        return;

    }

 

    var dragGrid = rowElement.ElementTree.Control as RadGridView;

    if (targetGrid != dragGrid)

    {

        e.Handled = true;

        //append dragged rows to the end of the target grid

        int index = targetGrid.RowCount;

 

        //Grab every selected row from the source grid, including the current row

        List<GridViewRowInfo> rows =

                            dragGrid.SelectedRows.ToList<GridViewRowInfo>();

        if (dragGrid.CurrentRow != null)

        {

            GridViewRowInfo row = dragGrid.CurrentRow;

            if (!rows.Contains(row))

                rows.Add(row);

        }

        this.MoveRows(targetGrid, dragGrid, rows, index);

    }

}

Figure  7  - implementing the PreviewDragDrop event handler

 

MOVING THE DATA FROM ONE SOURCE TO THE OTHER

You’ll notice at the end of the PreviewDragDrop handler that we need to create a MoveRows function that will handle the actual moving the data from the source to the destination. As mentioned at the beginning of the post, I wanted to handle three distinct data scenarios:

·         Unbound

·         Bound to Objects (through a BindingList<T>)

·         Bound to a DataSet

It’s in the MoveRows method where the physical moving of the data happens. Basically what we need in this method is to add the data into the target data source, and remove it from the source data source in order to complete the drag and drop operation under the covers. Implement the MoveRows method as follows:

private void MoveRows(RadGridView targetGrid, RadGridView dragGrid,

                        IList<GridViewRowInfo> dragRows, int index)

{

    dragGrid.BeginUpdate();

    targetGrid.BeginUpdate();

    for (int i = dragRows.Count - 1; i >= 0; i--)

    {

        GridViewRowInfo row = dragRows[i];

        if (row is GridViewSummaryRowInfo)

        {

            continue;

        }

        if (targetGrid.DataSource == null)

        {

            //unbound scenario

            GridViewRowInfo newRow = targetGrid.Rows.NewRow();

 

            foreach (GridViewCellInfo cell in row.Cells)

            {

                if (newRow.Cells[cell.ColumnInfo.Name] != null)

                    newRow.Cells[cell.ColumnInfo.Name].Value = cell.Value;

            }

 

            targetGrid.Rows.Insert(index, newRow);

 

            row.IsSelected = false;

            dragGrid.Rows.Remove(row);

        }

        else if (typeof(DataSet).IsAssignableFrom(targetGrid.DataSource.GetType()))

        {

            //bound to a dataset scenario

            var sourceTable = ((DataSet)dragGrid.DataSource).Tables[0];

            var targetTable = ((DataSet)targetGrid.DataSource).Tables[0];

 

            var newRow = targetTable.NewRow();

            foreach (GridViewCellInfo cell in row.Cells)

            {

                newRow[cell.ColumnInfo.Name] = cell.Value;

            }

 

            sourceTable.Rows.Remove(((DataRowView)row.DataBoundItem).Row);

            targetTable.Rows.InsertAt(newRow, index);

        }

        else if (typeof(IList).IsAssignableFrom(targetGrid.DataSource.GetType()))

        {

            //bound to a list of objects scenario

            var targetCollection = (IList)targetGrid.DataSource;

            var sourceCollection = (IList)dragGrid.DataSource;

            sourceCollection.Remove(row.DataBoundItem);

            targetCollection.Add(row.DataBoundItem);

        }

        else

        {

            throw new ApplicationException("Unhandled Scenario");

        }

        index++;

    }

    dragGrid.EndUpdate(true);

    targetGrid.EndUpdate(true);

}

Figure  8  - physically moving data from one source to the other to complete the drag and drop operation

 

STARTING THE DRAG AND DROP SERVICE USING BEHAVIORS

The final thing we need to do is to start the drag and drop service. In this case, we want the service to be initiated on a row in the grid when the user clicks on it with the left mouse button. In order to implement this, we will create a custom grid behavior. To do this, create a new class that inherits the GridDataRowBehavior class. I’ve just appended this class definition in the same source file as my grid. Implement the RowSelectionGridBehavior class as follows:

//initiates drag and drop service for clicked rows

public class RowSelectionGridBehavior : GridDataRowBehavior

{

    protected override bool OnMouseDownLeft(MouseEventArgs e)

    {

        GridDataRowElement row = this.GetRowAtPoint(e.Location) as GridDataRowElement;

        if (row != null)

        {

            RadGridViewDragDropService svc =

                        this.GridViewElement.GetService<RadGridViewDragDropService>();

            svc.Start(row);

        }

        return base.OnMouseDownLeft(e);

    }

}

Figure  9  - initiating the drag and drop service through a custom grid behavior

Next we will register this behavior in our grid. Add the following code to the default constructor of our DragAndDropRadGrid:

//register the custom row selection behavior

var gridBehavior = this.GridBehavior as BaseGridBehavior;

gridBehavior.UnregisterBehavior(typeof(GridViewDataRowInfo));

gridBehavior.RegisterBehavior(typeof(GridViewDataRowInfo),

                            new RowSelectionGridBehavior());

Figure  10  - initiating the drag and drop service through a custom grid behavior

Build the solution and our custom grid is now setup and ready for us to use. You can locate it in the Visual Studio toolbox when in the design view of a form.

controlToolbox

Figure  11  - our custom grid in the toolbox

 

USING OUR NEW CONTROL

Open the designer for Form1 and layout your form by dragging two instances of our DragAndDropRadGrid control (name them leftGrid and rightGrid respectively). Then drag three RadButton instances and name them btnUnbound, btnBoundObjects, and btnBoundDataSet. Visually layout the form and label your form elements in the designer as follows:

demoDesigner

Figure  12  - grid demo application form

Initialize some settings of the grids in the default constructor of the form as follows, we’ll also add a method to reset the grids:

 

public Form1()

{

    InitializeComponent();

    leftGrid.ShowGroupPanel = false;

    rightGrid.ShowGroupPanel = false;

    leftGrid.AllowAddNewRow = false;

    rightGrid.AllowAddNewRow = false;

    leftGrid.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill;

    rightGrid.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill;

}

private void ResetGrids()

{

    leftGrid.DataSource = null;

    leftGrid.Rows.Clear();

    leftGrid.Columns.Clear();

    rightGrid.DataSource = null;

    rightGrid.Rows.Clear();

    rightGrid.Columns.Clear();

}

Figure  13  - initializing grid settings in the default constructor of the form and the reset grid functionality

First we will implement the usage of our custom grid in an unbound scenario. To do this, double-click on the Unbound button to implement its click event handler as follows:

private void btnUnbound_Click(object sender, EventArgs e)

{

    ResetGrids();

 

    PrepareUnboundGrid(leftGrid);

    leftGrid.Rows.Add("Carey", "Payette");

    leftGrid.Rows.Add("Michael", "Crump");

    leftGrid.Rows.Add("Jeff", "Fritz");

    PrepareUnboundGrid(rightGrid);

    rightGrid.Rows.Add("Phil", "Japikse");

    rightGrid.Rows.Add("Jesse", "Liberty");

    rightGrid.Rows.Add("Iris", "Classon");

}

 

private void PrepareUnboundGrid(RadGridView grid)

{

    //setup columns

    GridViewTextBoxColumn firstName = new GridViewTextBoxColumn("FirstName", "FirstName");

    firstName.HeaderText = "First Name";

    GridViewTextBoxColumn lastName = new GridViewTextBoxColumn("LastName", "LastName");

    lastName.HeaderText = "Last Name";

          

    grid.Columns.AddRange(firstName,lastName);

}

Figure  14  - implementing the unbound grid scenario

Next we will implement the usage of our grid when it’s bound to a BindingList<T>. Double-click on the Bound to Objects button, and implement it as follows:

private void btnBoundObjects_Click(object sender, EventArgs e)

{

    ResetGrids();

 

    BindingList<Player> dataList1 = new BindingList<Player>();

    dataList1.Add(new Player() { FirstName = "Carey", LastName = "Payette" });

    dataList1.Add(new Player() { FirstName = "Michael", LastName = "Crump" });

    dataList1.Add(new Player() { FirstName = "Jeff", LastName = "Fritz" });

    BindingList<Player> dataList2 = new BindingList<Player>();

    dataList2.Add(new Player() { FirstName = "Phil", LastName = "Japikse" });

    dataList2.Add(new Player() { FirstName = "Jesse", LastName = "Liberty" });

    dataList2.Add(new Player() { FirstName = "Iris", LastName = "Classon"});

          

    leftGrid.DataSource = dataList1;

    rightGrid.DataSource = dataList2;

}

Figure  15  - implementing the bound to objects scenario

Add a Player class to the Form1.cs source file to support this scenario defined as the following:

public class Player

{

    public string FirstName { get; set; }

    public string LastName { get; set; }

}

Figure  16  - the Player class, supporting the bound to objects scenario

Lastly we will implement the scenario of when the grids are bound to a DataSet. Implement the click event handler of the Bound to DataSet button as follows:

private void btnBoundDataSet_Click(object sender, EventArgs e)

{

    ResetGrids();

 

    DataSet ds1 = new DataSet();

    DataTable team1 = new DataTable();

    team1.Columns.Add("First Name", typeof(string));

    team1.Columns.Add("Last Name", typeof(string));

    team1.Rows.Add("Carey", "Payette");

    team1.Rows.Add("Michael", "Crump");

    team1.Rows.Add("Jeff", "Fritz");

    ds1.Tables.Add(team1);

 

    DataSet ds2 = new DataSet();

    DataTable team2 = new DataTable();

    team2.Columns.Add("First Name", typeof(string));

    team2.Columns.Add("Last Name", typeof(string));

    team2.Rows.Add("Phil", "Japikse");

    team2.Rows.Add("Jesse", "Liberty");

    team2.Rows.Add("Iris", "Classon");

    ds2.Tables.Add(team2);

           

    leftGrid.DataSource = ds1;

    leftGrid.DataMember = "Table1";

    rightGrid.DataSource = ds2;

    rightGrid.DataMember = "Table1";

}

Figure  17  - implementing the bound to dataset scenario

Go ahead and build and run the application. You are now able to build your Evangelists Dodge Ball dream team using drag and drop functionality in bound and unbound modes. You are also able to select multiple rows using either the shift or control key, and holding the key down while you drag the rows between the grids.

 

SUMMARY

As shown in this blog post, you can see the majority of the complexity surrounding using drag and drop between RadGridView instances is shielded from us by using the RadGridViewDragDropService. It was also easy for us to extend the RadGridView control to provide this functionality and custom behavior. Telerik RadControls for Windows Forms allows us to provide users with the rich experiences they desire without extra complexity.

The DragAndDropRadGrid class is available for you to reuse, modify and mold to your specific requirements, download the sample code from the links below.

DOWNLOAD SOURCE

Download RadControls for WinForms by Telerik


About the Author

Carey Payette

is a Developer Advocate. You can follow Carey on Twitter @careypayette or read her personal blog at www.codingbandit.com.

Comments

Comments are disabled in preview mode.