Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DataGrid Drag-and-drop #13581

Open
alexandrehtrb opened this issue Nov 12, 2023 · 5 comments
Open

DataGrid Drag-and-drop #13581

alexandrehtrb opened this issue Nov 12, 2023 · 5 comments

Comments

@alexandrehtrb
Copy link

alexandrehtrb commented Nov 12, 2023

When using a DataGrid, I would like to perform drag-and-drop to rearrange rows.

The TreeDataGrid drag-and-drop works, but there are some bugs and required effort to migrate from DataGrid to TreeDataGrid.

@aldelaro5 showed in a discussion how to make drag-and-drop for DataGrid. I tried and it works.

However, there is a bug in the proposed solution that when someone tries to select a text from a DataGridTextCell, it understands as if the person wants to perform drag-and-drop. Basically, drag-and-drop should be disabled if a DataGrid cell is being edited.

I believe this feature is very much needed, in fact, drag-and-drop should come by default in DataGrid.

@timunie
Copy link
Contributor

timunie commented Nov 13, 2023

check Avalonia.Xaml.Behaviors . The samples should give you a clue how it can be implemented.

@alexandrehtrb
Copy link
Author

How can I check if a DataGrid cell is being edited? To block drag-and-drop if edition is being made

@timunie
Copy link
Contributor

timunie commented Nov 13, 2023

I personally would make DataGridRowHeader to be a drag moving thumb if I need it to be editable

@timunie
Copy link
Contributor

timunie commented Nov 13, 2023

image

can't share the code, but may give a clue

@alexandrehtrb
Copy link
Author

@timunie , thanks a lot man!!

I will show here how I managed to do this, if anyone comes looking up for a solution.

  1. Include in your .csproj the Avalonia.Xaml.Behaviors NuGet package.

  2. Create a .xaml for your DataGrid drag-and-drop styles, then reference this file in App.xaml styles:

<Styles
  xmlns="https://github.com/avaloniaui"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
  xmlns:iac="clr-namespace:Avalonia.Xaml.Interactions.Custom;assembly=Avalonia.Xaml.Interactions.Custom"
  xmlns:idd="clr-namespace:Avalonia.Xaml.Interactions.DragAndDrop;assembly=Avalonia.Xaml.Interactions.DragAndDrop"
  xmlns:b="using:YourNamespace.Behaviors">

  <Style Selector="DataGrid.DragAndDrop">
    <Setter
      Property="RowHeaderWidth"
      Value="24" />
  </Style>

  <!-- This makes only the DataGridRowHeader available for dragging, instead of making the entire row draggable -->
  <!-- Which prevents a conflict between text selection in a cell and drag-and-drop -->
  <Style Selector="DataGrid.DragAndDrop DataGridRowHeader">
    <Setter Property="(i:Interaction.Behaviors)">
      <i:BehaviorCollectionTemplate>
        <i:BehaviorCollection>
          <idd:ContextDragBehavior HorizontalDragThreshold="3" VerticalDragThreshold="3" />
        </i:BehaviorCollection>
      </i:BehaviorCollectionTemplate>
    </Setter>
    <Setter Property="Content">
      <Template>
        <Image
          Margin="12,0,12,0"
          Width="12"
          Height="12"
          VerticalAlignment="Center"
          HorizontalAlignment="Center">
          <Image.Source>
            <!-- Use your own image here, I used this: https://www.svgrepo.com/svg/347759/grabber -->
            <DrawingImage Drawing="{StaticResource IconGrabber}" />
          </Image.Source>
        </Image>
      </Template>
    </Setter>
  </Style>

  <Style Selector="DataGrid.MyItemsDragAndDrop">
    <Style.Resources>
      <b:MyItemsDataGridDropHandler x:Key="MyItemsDataGridDropHandler" />
    </Style.Resources>
    <Setter Property="(i:Interaction.Behaviors)">
      <i:BehaviorCollectionTemplate>
        <i:BehaviorCollection>
          <idd:ContextDropBehavior Handler="{StaticResource MyItemsDataGridDropHandler}" />
        </i:BehaviorCollection>
      </i:BehaviorCollectionTemplate>
    </Setter>
  </Style>
</Styles>
  1. Create a DataGridDropHandler for your table and items view model:
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.VisualTree;
using Avalonia.Xaml.Interactions.DragAndDrop;
using YourNamespace.ViewModels;

namespace YourNamespace.Behaviors;

public class MyItemsDataGridDropHandler : DropHandlerBase
{
    private bool Validate<T>(DataGrid dg, DragEventArgs e, object? sourceContext, object? targetContext, bool bExecute)
    {
        if (sourceContext is not ItemViewModel sourceItem
            || targetContext is not MainWindowViewModel vm
            || listBox.GetVisualAt(e.GetPosition(listBox)) is not Control targetControl
            || targetControl.DataContext is not ItemViewModel targetItem)
        {
            return false;
        }

        var items = vm.Items;
        var sourceIndex = items.IndexOf(sourceItem);
        var targetIndex = items.IndexOf(targetItem);

        if (sourceIndex < 0 || targetIndex < 0)
        {
            return false;
        }

        switch (e.DragEffects)
        {
            case DragDropEffects.Copy:
            {
                if (bExecute)
                {
                    var clone = new ItemViewModel() { Title = sourceItem.Title + "_copy" };
                    InsertItem(items, clone, targetIndex + 1);
                }
                return true;
            }
            case DragDropEffects.Move:
            {
                if (bExecute)
                {
                    MoveItem(items, sourceIndex, targetIndex);
                }
                return true;
            }
            case DragDropEffects.Link:
            {
                if (bExecute)
                {
                    SwapItem(items, sourceIndex, targetIndex);
                }
                return true;
            }
            default:
                return false;
        }
    }
        
    public override bool Validate(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state)
    {
        if (e.Source is Control && sender is DataGrid dg)
        {
            return Validate<ItemViewModel>(dg, e, sourceContext, targetContext, false);
        }
        return false;
    }

    public override bool Execute(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state)
    {
        if (e.Source is Control && sender is DataGrid dg)
        {
            return Validate<ItemViewModel>(dg, e, sourceContext, targetContext, true);
        }
        return false;
    }
}
  1. In your DataGrid .xaml, include the properties HeadersVisibility="All" and Classes="DragAndDrop MyItemsDragAndDrop":
<DataGrid
        Name="dgMyItems"
        AutoGenerateColumns="False"
        ItemsSource="{Binding Items}"
        CanUserResizeColumns="True"
        HeadersVisibility="All"
        Classes="DragAndDrop MyItemsDragAndDrop">
...
</DataGrid>

Final result:

Pororoca_M2fap5KWvs.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants