New to Telerik UI for WinForms? Download free 30-day trial

Rows Reordering in Master-Detail Hierarchy

Consider the case you have a bound RadGridView with master-detail hierarchical data. This article demonstrates how to utilize the RadGridViewDragDropService and implement rows reordering.

Figure 1: Rows reordering in Master-Detail hierarchy

WinForms RadGridView Rows Reordering Master-Detail Hierarchy

There are several main steps that need to be followed:

  1. Register a custom GridDataRowBehavior which starts the RadGridViewDragDropService when you click with the left mouse button over a child row.

  2. Override the OnMouseDownLeft method of the GridDataRowBehavior and start the service.

  3. Handle the RadDragDropService.PreviewDragStart event in order to indicate that RadGridView can start the drag operation. In the RadDragDropService.PreviewDragOver event you can control on what targets the row being dragged can be dropped on. In the PreviewDragDrop event you can perform the actual reordering of the data bound records. Note that it is important to update the ParentId field of the affected child record. Thus, you will indicate the new parent record to which the dragged record belongs.

BindingList<Level0> parentRecords = new BindingList<Level0>();
BindingList<Level1> childRecords = new BindingList<Level1>(); 
GridViewRowInfo draggedRow = null;

public RadForm1()
{
    InitializeComponent();
    FillData();

    radGridView1.DataSource = parentRecords;
    radGridView1.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill;

    GridViewTemplate template = new GridViewTemplate();
    template.DataSource = childRecords;
    template.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill;
    radGridView1.MasterTemplate.Templates.Add(template);

    GridViewRelation relation = new GridViewRelation(radGridView1.MasterTemplate);
    relation.ChildTemplate = template;
    relation.RelationName = "ParentChild";
    relation.ParentColumnNames.Add("Id");
    relation.ChildColumnNames.Add("ParentId");
    radGridView1.Relations.Add(relation);
    radGridView1.UseScrollbarsInHierarchy = true;
    this.radGridView1.AllowRowReorder = true;

    //handle drag and drop events for the grid through the DragDrop service
    RadDragDropService svc = this.radGridView1.GridViewElement.GetService<RadDragDropService>();
    svc.PreviewDragStart += svc_PreviewDragStart;
    svc.PreviewDragDrop += svc_PreviewDragDrop;
    svc.PreviewDragOver += svc_PreviewDragOver;

    //register the custom row selection behavior
    var gridBehavior = this.radGridView1.GridBehavior as BaseGridBehavior;
    gridBehavior.UnregisterBehavior(typeof(GridViewDataRowInfo));
    gridBehavior.RegisterBehavior(typeof(GridViewDataRowInfo), new RowSelectionGridBehavior());
}

private void svc_PreviewDragStart(object sender, PreviewDragStartEventArgs e)
{
    SnapshotDragItem snapshot = e.DragInstance as SnapshotDragItem;
    if (snapshot == null)
    {
        e.CanStart = false;
    }
    GridDataRowElement draggedRowElement = snapshot.Item as GridDataRowElement;
    if (draggedRowElement != null && draggedRowElement.RowInfo != null)
    {
        e.CanStart = true;
        draggedRow = draggedRowElement.RowInfo;
    }
    else
    {
        e.CanStart = false;
    }
}

private void svc_PreviewDragOver(object sender, RadDragOverEventArgs e)
{
    SnapshotDragItem snapshot = e.DragInstance as SnapshotDragItem;
    if (snapshot == null)
    {
        e.CanDrop = false;
    }
    if (snapshot.Item is GridDataRowElement)
    {
        e.CanDrop = e.HitTarget is GridDataRowElement || e.HitTarget is GridNewRowElement ||
                    e.HitTarget is GridTableElement ||
                    e.HitTarget is GridSummaryRowElement;
    }
}

private void svc_PreviewDragDrop(object sender, RadDropEventArgs e)
{
    SnapshotDragItem snapshot = e.DragInstance as SnapshotDragItem;
    if (snapshot == null)
    {
        return;
    }
    var rowElement = snapshot.Item as GridDataRowElement;
    if (rowElement == null)
    {
        return;
    }
    e.Handled = true;

    var dropTarget = e.HitTarget as GridDataRowElement;
    var targetGrid = dropTarget.ElementTree.Control as RadGridView;
    if (targetGrid == null)
    {
        return;
    }
    var dragGrid = draggedRow.ViewTemplate.MasterTemplate.Owner;
    if (targetGrid == dragGrid && dropTarget.RowInfo.HierarchyLevel == 1 && draggedRow.HierarchyLevel == 1)
    {
        e.Handled = true;
        Level1 draggedItem = draggedRow.DataBoundItem as Level1;  
        int targetIndex = childRecords.IndexOf(dropTarget.RowInfo.DataBoundItem as Level1);
        Level1 newItem = new Level1(draggedItem.Id, (int)((GridViewHierarchyRowInfo)dropTarget.RowInfo.Parent).Cells["Id"].Value, draggedItem.Name);
        GridViewRowInfo targetDataRow = dropTarget.RowInfo;

        targetGrid.BeginUpdate();
        childRecords.Remove(draggedItem);
        childRecords.Insert(targetIndex, newItem);
        targetGrid.EndUpdate();
        targetGrid.CurrentRow = targetDataRow;
    }
}

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.AllowAutoScrollColumnsWhileDragging = true;
            svc.AllowAutoScrollRowsWhileDragging = true;
            svc.Start(new SnapshotDragItem(row));
        }
        return base.OnMouseDownLeft(e);
    }
}

private void FillData()
{
    int cnt = 0;
    for (int i = 0; i < 10; i++)
    {
        parentRecords.Add(new Level0(i, "Parent" + i));

        for (int j = 0; j < 5; j++)
        {
            childRecords.Add(new Level1(cnt, i, "Child " + cnt));
            cnt++;
        }
    }
}

public class Level0 : System.ComponentModel.INotifyPropertyChanged
{
    public Level0(int id, string title)
    {
        this.Id = id;
        this.Title = title;
    }

    public int Id
    {
        get
        {
            return this._id;
        }
        set
        {
            this._id = value;
            OnPropertyChanged("Id");
        }
    }

    public string Title
    {
        get
        {
            return this._title;
        }
        set
        {
            this._title = value;
            OnPropertyChanged("Title");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private int _id;
    private string _title;
}

public class Level1 : System.ComponentModel.INotifyPropertyChanged
{
    public Level1(int id, int parentId, string name)
    {
        this.Id = id;
        this.ParentId = parentId;
        this.Name = name;
    }

    public int Id
    {
        get
        {
            return this._id;
        }
        set
        {
            this._id = value;
            OnPropertyChanged("Id");
        }
    }

    public int ParentId
    {
        get
        {
            return this._parentId;
        }
        set
        {
            this._parentId = value;
            OnPropertyChanged("ParentId");
        }
    }

    public string Name
    {
        get
        {
            return this._name;
        }
        set
        {
            this._name = value;
            OnPropertyChanged("Name");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private int _id;
    private int _parentId;
    private string _name;
}