Drag and Drop within RadGridView

The purpose of this tutorial is to show you how to implement drag and drop within RadGridView, giving the user feedback where the dragged item will be dropped.

The final result should look like the one on the snapshot below:

dragdropmanager-howto-draganddrop-within-radgridview 02

  • Create a new business object named MessageViewModel. It will be used to populate the RadGridView with sample data.

public class MessageViewModel 
{ 
    public static IList Generate() 
    { 
        IList data = new ObservableCollection<MessageViewModel>(); 
        data.Add(new MessageViewModel("tom@hanna-barbera.com", "Cats are cool", 100)); 
        data.Add(new MessageViewModel("jerry@hanna-barbera.com", "Mice are cool", 100)); 
        data.Add(new MessageViewModel("spike@hanna-barbera.com", "Dogs are cool", 100)); 
        data.Add(new MessageViewModel("jerry2@hanna-barbera.com", "2Mice are cool", 200)); 
        data.Add(new MessageViewModel("spike2@hanna-barbera.com", "2Dogs are cool", 200)); 
        data.Add(new MessageViewModel("jerry3@hanna-barbera.com", "3Mice are cool", 300)); 
        data.Add(new MessageViewModel("spike3@hanna-barbera.com", "3Dogs are cool", 300)); 
        data.Add(new MessageViewModel("spike3@hanna-barbera.com", "3Dogs are cool", 300)); 
        data.Add(new MessageViewModel("spike3@hanna-barbera.com", "3Dogs are cool", 300)); 
        return data; 
    } 
    public MessageViewModel(string sender, string subject, int size) 
    { 
        this.Sender = sender; 
        this.Subject = subject; 
        this.Size = size; 
    } 
    public string Subject 
    { 
        get; 
        set; 
    } 
    public string Sender 
    { 
        get; 
        set; 
    } 
    public int Size 
    { 
        get; 
        set; 
    } 
     public override string ToString() 
    { 
        return this.Sender; 
    } 
} 
Public Class MessageViewModel 
    Public Shared Function Generate() As IList 
        Dim data As IList = New ObservableCollection(Of MessageViewModel)() 
        data.Add(New MessageViewModel("tom@hanna-barbera.com", "Cats are cool", 100)) 
        data.Add(New MessageViewModel("jerry@hanna-barbera.com", "Mice are cool", 100)) 
        data.Add(New MessageViewModel("spike@hanna-barbera.com", "Dogs are cool", 100)) 
        data.Add(New MessageViewModel("jerry2@hanna-barbera.com", "2Mice are cool", 200)) 
        data.Add(New MessageViewModel("spike2@hanna-barbera.com", "2Dogs are cool", 200)) 
        data.Add(New MessageViewModel("jerry3@hanna-barbera.com", "3Mice are cool", 300)) 
        data.Add(New MessageViewModel("spike3@hanna-barbera.com", "3Dogs are cool", 300)) 
        data.Add(New MessageViewModel("spike3@hanna-barbera.com", "3Dogs are cool", 300)) 
        data.Add(New MessageViewModel("spike3@hanna-barbera.com", "3Dogs are cool", 300)) 
        Return data 
    End Function 
    Public Sub New(ByVal sender As String, ByVal subject As String, ByVal size As Integer) 
        Me.Sender = sender 
        Me.Subject = subject 
        Me.Size = size 
    End Sub 
    Private _Subject As String 
    Public Property Subject() As String 
        Get 
            Return _Subject 
        End Get 
        Set(ByVal value As String) 
            _Subject = value 
        End Set 
    End Property 
    Private _Sender As String 
    Public Property Sender() As String 
        Get 
            Return _Sender 
        End Get 
        Set(ByVal value As String) 
            _Sender = value 
        End Set 
    End Property 
    Private _Size As Integer 
    Public Property Size() As Integer 
        Get 
            Return _Size 
        End Get 
        Set(ByVal value As Integer) 
            _Size = value 
        End Set 
    End Property 
    Public Overrides Function ToString() As String 
        Return Me.Sender 
    End Function 
End Class 
  • The XAML should look like the code snippet below.

<telerik:RadGridView   AutoGenerateColumns="True" 
                        Margin="5" 
                        Name="radGridView" 
                        AllowDrop="True" 
                        GroupRenderMode="Flat"> 
    <telerik:RadGridView.Resources> 
        <DataTemplate x:Key="DraggedItemTemplate"> 
            <StackPanel> 
                <StackPanel Orientation="Horizontal"> 
                    <TextBlock Text="Dragging:" /> 
                    <TextBlock Text="{Binding CurrentDraggedItem}" 
                               FontWeight="Bold" /> 
                </StackPanel> 
                <StackPanel Orientation="Horizontal"> 
                    <TextBlock Text="{Binding CurrentDropPosition}" 
                               FontWeight="Bold" 
                               MinWidth="45" /> 
                    <TextBlock Text=", (" 
                               Foreground="Gray" /> 
                    <TextBlock Text="{Binding CurrentDraggedOverItem}" /> 
                    <TextBlock Text=")" 
                               Foreground="Gray" /> 
                </StackPanel> 
            </StackPanel> 
        </DataTemplate> 
    </telerik:RadGridView.Resources> 
    <telerik:RadGridView.RowStyle> 
        <Style TargetType="telerik:GridViewRow"> 
            <Setter Property="telerik:DragDropManager.AllowDrag" 
                    Value="True" /> 
            <Setter Property="telerik:DragDropManager.TouchDragTrigger" 
                    Value="TapAndHold"/> 
        </Style> 
    </telerik:RadGridView.RowStyle> 
</telerik:RadGridView> 

If you are referencing NoXaml binaries, then your style should specify BasedOn={StaticResource GridViewRowStyle}.

  • Create a new class named DropIndicationDetails.

public class DropIndicationDetails : ViewModelBase 
{ 
    private object currentDraggedItem; 
    private DropPosition currentDropPosition; 
    private object currentDraggedOverItem; 
 
    public object CurrentDraggedOverItem 
    { 
        get 
        { 
            return currentDraggedOverItem; 
        } 
        set 
        { 
            if (this.currentDraggedOverItem != value) 
            { 
                currentDraggedOverItem = value; 
                OnPropertyChanged("CurrentDraggedOverItem"); 
            } 
        } 
    } 
 
    public int DropIndex { get; set; } 
 
    public DropPosition CurrentDropPosition 
    { 
        get 
        { 
            return this.currentDropPosition; 
        } 
        set 
        { 
            if (this.currentDropPosition != value) 
            { 
                this.currentDropPosition = value; 
                OnPropertyChanged("CurrentDropPosition"); 
            } 
        } 
    } 
 
    public object CurrentDraggedItem 
    { 
        get 
        { 
            return this.currentDraggedItem; 
        } 
        set 
        { 
            if (this.currentDraggedItem != value) 
            { 
                this.currentDraggedItem = value; 
                OnPropertyChanged("CurrentDraggedItem"); 
            } 
        } 
    } 
} 
Public Class DropIndicationDetails 
    Inherits ViewModelBase 
    Private m_currentDraggedItem As Object 
    Private m_currentDropPosition As DropPosition 
    Private m_currentDraggedOverItem As Object 
 
    Public Property CurrentDraggedOverItem() As Object 
        Get 
            Return m_currentDraggedOverItem 
        End Get 
        Set(value As Object) 
            If Me.m_currentDraggedOverItem <> value Then 
                m_currentDraggedOverItem = value 
                OnPropertyChanged("CurrentDraggedOverItem") 
            End If 
        End Set 
    End Property 
 
    Public Property DropIndex() As Integer 
        Get 
            Return m_DropIndex 
        End Get 
        Set(value As Integer) 
            m_DropIndex = Value 
        End Set 
    End Property 
    Private m_DropIndex As Integer 
 
    Public Property CurrentDropPosition() As DropPosition 
        Get 
            Return Me.m_currentDropPosition 
        End Get 
        Set(value As DropPosition) 
            If Me.m_currentDropPosition <> value Then 
                Me.m_currentDropPosition = value 
                OnPropertyChanged("CurrentDropPosition") 
            End If 
        End Set 
    End Property 
 
    Public Property CurrentDraggedItem() As Object 
        Get 
            Return Me.m_currentDraggedItem 
        End Get 
        Set(value As Object) 
            If Me.m_currentDraggedItem <> value Then 
                Me.m_currentDraggedItem = value 
                OnPropertyChanged("CurrentDraggedItem") 
            End If 
        End Set 
    End Property 
End Class 
  • The row reorder capabilities will be encapsulated in a Behavior. Create a new class named RowReorderBehavior.

  • Finally, the complete code for the RowReorderBehavior is shown on the code snippet below.

public class RowReorderBehavior 
{ 
    private const string DropPositionFeedbackElementName = "DragBetweenItemsFeedback"; 
    private ContentPresenter dropPositionFeedbackPresenter; 
    private Grid dropPositionFeedbackPresenterHost; 
 
    private RadGridView _associatedObject; 
 
    /// <summary> 
    /// AssociatedObject Property 
    /// </summary> 
    public RadGridView AssociatedObject 
    { 
        get 
        { 
            return _associatedObject; 
        } 
        set 
        { 
            _associatedObject = value; 
        } 
    } 
 
    private static Dictionary<RadGridView, RowReorderBehavior> instances; 
 
    static RowReorderBehavior() 
    { 
        instances = new Dictionary<RadGridView, RowReorderBehavior>(); 
    } 
 
    public static bool GetIsEnabled(DependencyObject obj) 
    { 
        return (bool)obj.GetValue(IsEnabledProperty); 
    } 
 
    public static void SetIsEnabled(DependencyObject obj, bool value) 
    { 
        RowReorderBehavior behavior = GetAttachedBehavior(obj as RadGridView); 
 
        behavior.AssociatedObject = obj as RadGridView; 
 
        if (value) 
        { 
            behavior.Initialize(); 
        } 
        else 
        { 
            behavior.CleanUp(); 
        } 
        obj.SetValue(IsEnabledProperty, value); 
    } 
 
    // Using a DependencyProperty as the backing store for IsEnabled.  This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty IsEnabledProperty = 
        DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(RowReorderBehavior), 
            new PropertyMetadata(new PropertyChangedCallback(OnIsEnabledPropertyChanged))); 
 
    public static void OnIsEnabledPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) 
    { 
        SetIsEnabled(dependencyObject, (bool)e.NewValue); 
    } 
 
    private static RowReorderBehavior GetAttachedBehavior(RadGridView gridview) 
    { 
        if (!instances.ContainsKey(gridview)) 
        { 
            instances[gridview] = new RowReorderBehavior(); 
            instances[gridview].AssociatedObject = gridview; 
        } 
 
        return instances[gridview]; 
    } 
 
    public RowReorderBehavior() 
    { 
 
    } 
 
    protected virtual void Initialize() 
    { 
        this.AssociatedObject.RowLoaded -= this.AssociatedObject_RowLoaded; 
        this.AssociatedObject.RowLoaded += this.AssociatedObject_RowLoaded; 
        this.UnsubscribeFromDragDropEvents(); 
        this.SubscribeToDragDropEvents(); 
 
        this.AssociatedObject.Dispatcher.BeginInvoke((Action)(() => 
        { 
            this.dropPositionFeedbackPresenter = new ContentPresenter(); 
            this.dropPositionFeedbackPresenter.Name = DropPositionFeedbackElementName; 
            this.dropPositionFeedbackPresenter.HorizontalAlignment = HorizontalAlignment.Left; 
            this.dropPositionFeedbackPresenter.VerticalAlignment = VerticalAlignment.Top; 
            this.dropPositionFeedbackPresenter.RenderTransformOrigin = new Point(0.5, 0.5); 
 
            this.AttachDropPositionFeedback(); 
        })); 
    } 
 
    protected virtual void CleanUp() 
    { 
        this.AssociatedObject.RowLoaded -= this.AssociatedObject_RowLoaded; 
        this.UnsubscribeFromDragDropEvents(); 
 
        this.DetachDropPositionFeedback(); 
    } 
 
    void AssociatedObject_RowLoaded(object sender, Telerik.Windows.Controls.GridView.RowLoadedEventArgs e) 
    { 
        if (e.Row is GridViewHeaderRow || e.Row is GridViewNewRow || e.Row is GridViewFooterRow) 
            return; 
 
        GridViewRow row = e.Row as GridViewRow; 
        this.InitializeRowDragAndDrop(row); 
    } 
 
    private void InitializeRowDragAndDrop(GridViewRow row) 
    { 
        if (row == null) 
            return; 
 
        DragDropManager.RemoveDragOverHandler(row, OnRowDragOver); 
        DragDropManager.AddDragOverHandler(row, OnRowDragOver); 
    } 
 
    private void SubscribeToDragDropEvents() 
    { 
        DragDropManager.AddDragInitializeHandler(this.AssociatedObject, OnDragInitialize); 
        DragDropManager.AddGiveFeedbackHandler(this.AssociatedObject, OnGiveFeedback); 
        DragDropManager.AddDropHandler(this.AssociatedObject, OnDrop); 
        DragDropManager.AddDragDropCompletedHandler(this.AssociatedObject, OnDragDropCompleted); 
    } 
 
    private void UnsubscribeFromDragDropEvents() 
    { 
        DragDropManager.RemoveDragInitializeHandler(this.AssociatedObject, OnDragInitialize); 
        DragDropManager.RemoveGiveFeedbackHandler(this.AssociatedObject, OnGiveFeedback); 
        DragDropManager.RemoveDropHandler(this.AssociatedObject, OnDrop); 
        DragDropManager.RemoveDragDropCompletedHandler(this.AssociatedObject, OnDragDropCompleted); 
    } 
 
    private void OnDragDropCompleted(object sender, DragDropCompletedEventArgs e) 
    { 
        this.HideDropPositionFeedbackPresenter(); 
    } 
 
    private void OnDragInitialize(object sender, DragInitializeEventArgs e) 
    { 
        var sourceRow = e.OriginalSource as GridViewRow ?? (e.OriginalSource as FrameworkElement).ParentOfType<GridViewRow>(); 
        if (sourceRow != null && sourceRow.Name != "PART_RowResizer") 
        { 
            DropIndicationDetails details = new DropIndicationDetails(); 
            var item = sourceRow.Item; 
            details.CurrentDraggedItem = item; 
 
            IDragPayload dragPayload = DragDropPayloadManager.GeneratePayload(null); 
 
            dragPayload.SetData("DraggedItem", item); 
            dragPayload.SetData("DropDetails", details); 
 
            e.Data = dragPayload; 
 
            e.DragVisual = new DragVisual() 
            { 
                Content = details, 
                ContentTemplate = this.AssociatedObject.Resources["DraggedItemTemplate"] as DataTemplate 
            }; 
            e.DragVisualOffset = e.RelativeStartPoint; 
            e.AllowedEffects = DragDropEffects.All; 
        } 
    } 
 
    private void OnGiveFeedback(object sender, Telerik.Windows.DragDrop.GiveFeedbackEventArgs e) 
    { 
        e.SetCursor(Cursors.Arrow); 
        e.Handled = true; 
    } 
 
    private void OnDrop(object sender, Telerik.Windows.DragDrop.DragEventArgs e) 
    { 
        var draggedItem = DragDropPayloadManager.GetDataFromObject(e.Data, "DraggedItem"); 
        var details = DragDropPayloadManager.GetDataFromObject(e.Data, "DropDetails") as DropIndicationDetails; 
 
        if (details == null || draggedItem == null) 
        { 
            return; 
        } 
 
        if (e.Effects == DragDropEffects.Move || e.Effects == DragDropEffects.All) 
        { 
            ((sender as RadGridView).ItemsSource as IList).Remove(draggedItem); 
        } 
 
        if (e.Effects != DragDropEffects.None) 
        { 
            var collection = (sender as RadGridView).ItemsSource as IList; 
            int index = details.DropIndex < 0 ? 0 : details.DropIndex; 
            index = details.DropIndex > collection.Count - 1 ? collection.Count : index; 
 
            collection.Insert(index, draggedItem); 
        } 
 
        HideDropPositionFeedbackPresenter(); 
    } 
 
    private void OnRowDragOver(object sender, Telerik.Windows.DragDrop.DragEventArgs e) 
    { 
        var row = sender as GridViewRow; 
        var details = DragDropPayloadManager.GetDataFromObject(e.Data, "DropDetails") as DropIndicationDetails; 
 
        if (details == null || row == null) 
        { 
            return; 
        } 
 
        details.CurrentDraggedOverItem = row.DataContext; 
 
        if (details.CurrentDraggedItem == details.CurrentDraggedOverItem) 
        { 
            e.Effects = DragDropEffects.None; 
            e.Handled = true; 
            return; 
        } 
 
        details.CurrentDropPosition = GetDropPositionFromPoint(e.GetPosition(row), row); 
        int dropIndex = (this.AssociatedObject.Items as IList).IndexOf(row.DataContext); 
        int draggedItemIdex = (this.AssociatedObject.Items as IList).IndexOf(DragDropPayloadManager.GetDataFromObject(e.Data, "DraggedItem")); 
 
        if (dropIndex >= row.GridViewDataControl.Items.Count - 1 && details.CurrentDropPosition == DropPosition.After) 
        { 
            details.DropIndex = dropIndex; 
            this.ShowDropPositionFeedbackPresenter(this.AssociatedObject, row, details.CurrentDropPosition); 
            return; 
        } 
 
        dropIndex = draggedItemIdex > dropIndex ? dropIndex : dropIndex - 1; 
        details.DropIndex = details.CurrentDropPosition == DropPosition.Before ? dropIndex : dropIndex + 1; 
 
        this.ShowDropPositionFeedbackPresenter(this.AssociatedObject, row, details.CurrentDropPosition); 
    } 
 
    public virtual DropPosition GetDropPositionFromPoint(Point absoluteMousePosition, GridViewRow row) 
    { 
        if (row != null) 
        { 
            return absoluteMousePosition.Y < row.ActualHeight / 2 ? DropPosition.Before : DropPosition.After; 
        } 
 
        return DropPosition.Inside; 
    } 
 
    private bool IsDropPositionFeedbackAvailable() 
    { 
        return 
              this.dropPositionFeedbackPresenterHost != null && 
              this.dropPositionFeedbackPresenter != null; 
    } 
 
    private void ShowDropPositionFeedbackPresenter(GridViewDataControl gridView, GridViewRow row, DropPosition lastRowDropPosition) 
    { 
        if (!this.IsDropPositionFeedbackAvailable()) 
            return; 
        var yOffset = this.GetDropPositionFeedbackOffset(row, lastRowDropPosition); 
        this.dropPositionFeedbackPresenter.Visibility = Visibility.Visible; 
        this.dropPositionFeedbackPresenter.Width = row.ActualWidth; 
        this.dropPositionFeedbackPresenter.RenderTransform = new TranslateTransform() 
        { 
            Y = yOffset 
        }; 
    } 
 
    private void HideDropPositionFeedbackPresenter() 
    { 
        this.dropPositionFeedbackPresenter.RenderTransform = new TranslateTransform() 
        { 
            X = 0, 
            Y = 0 
        }; 
        this.dropPositionFeedbackPresenter.Visibility = Visibility.Collapsed; 
    } 
 
    private double GetDropPositionFeedbackOffset(GridViewRow row, DropPosition dropPosition) 
    { 
        var yOffset = row.TransformToVisual(this.dropPositionFeedbackPresenterHost).Transform(new Point(0, 0)).Y; 
        if (dropPosition == DropPosition.After) 
            yOffset += row.ActualHeight; 
        yOffset -= (this.dropPositionFeedbackPresenter.ActualHeight / 2.0); 
        return yOffset; 
    } 
 
    private void DetachDropPositionFeedback() 
    { 
        if (this.IsDropPositionFeedbackAvailable()) 
        { 
            this.dropPositionFeedbackPresenterHost.Children.Remove(this.dropPositionFeedbackPresenter); 
            this.dropPositionFeedbackPresenter = null; 
        } 
    } 
 
    private void AttachDropPositionFeedback() 
    { 
        this.dropPositionFeedbackPresenterHost = this.AssociatedObject.ParentOfType<Grid>(); 
 
        if (this.dropPositionFeedbackPresenterHost != null) 
        { 
            this.dropPositionFeedbackPresenter.Content = CreateDefaultDropPositionFeedback(); 
            if (dropPositionFeedbackPresenterHost != null && dropPositionFeedbackPresenterHost.FindName(this.dropPositionFeedbackPresenter.Name) == null) 
            { 
                this.dropPositionFeedbackPresenterHost.Children.Add(this.dropPositionFeedbackPresenter); 
            } 
        } 
        this.HideDropPositionFeedbackPresenter(); 
    } 
 
    private static UIElement CreateDefaultDropPositionFeedback() 
    { 
        Grid grid = new Grid() 
        { 
            Height = 8, 
            HorizontalAlignment = HorizontalAlignment.Stretch, 
            IsHitTestVisible = false, 
            VerticalAlignment = VerticalAlignment.Stretch 
        }; 
        grid.ColumnDefinitions.Add(new ColumnDefinition() 
        { 
            Width = new GridLength(8) 
        }); 
        grid.ColumnDefinitions.Add(new ColumnDefinition()); 
        Ellipse ellipse = new Ellipse() 
        { 
            Stroke = new SolidColorBrush(Colors.Orange), 
            StrokeThickness = 2, 
            Fill = new SolidColorBrush(Colors.Orange), 
            HorizontalAlignment = HorizontalAlignment.Stretch, 
            VerticalAlignment = VerticalAlignment.Stretch, 
            Width = 8, 
            Height = 8 
        }; 
        Rectangle rectangle = new Rectangle() 
        { 
            Fill = new SolidColorBrush(Colors.Orange), 
            RadiusX = 2, 
            RadiusY = 2, 
            VerticalAlignment = VerticalAlignment.Stretch, 
            HorizontalAlignment = HorizontalAlignment.Stretch, 
            Height = 2 
        }; 
        Grid.SetColumn(ellipse, 0); 
        Grid.SetColumn(rectangle, 1); 
        grid.Children.Add(ellipse); 
        grid.Children.Add(rectangle); 
 
        Canvas.SetZIndex(grid, 10000); 
 
        return grid; 
    } 
} 

Finally, you should populate your RadGridView with sample data and attach the attached behavior:

public MainWindow() 
{ 
    InitializeComponent(); 
 
    this.radGridView.ItemsSource = MessageViewModel.Generate(); 
    RowReorderBehavior.SetIsEnabled(this.radGridView, true); 
} 
Public Sub New() 
    InitializeComponent() 
 
    Me.radGridView.ItemsSource = MessageViewModel.Generate() 
    RowReorderBehavior.SetIsEnabled(Me.radGridView, True) 
End Sub 

You can download a runnable project on the previous example from our online SDK repository here.

You can also check the SDK Samples Browser that provides a more convenient approach in exploring and executing the examples in the Telerik XAML SDK repository.

You can also check the implementation of Reorder Rows demo/Tree to Grid demo.

In this article