Open alexandrehtrb opened 1 year ago
check Avalonia.Xaml.Behaviors . The samples should give you a clue how it can be implemented.
How can I check if a DataGrid cell is being edited? To block drag-and-drop if edition is being made
I personally would make DataGridRowHeader to be a drag moving thumb if I need it to be editable
can't share the code, but may give a clue
@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>
3) 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;
}
}
4) 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:
https://github.com/AvaloniaUI/Avalonia/assets/27026741/b8c8db9b-654a-4e1c-be18-ff0372cf4081
How can I add an icon to the first column above the drag cells?
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.
Update
There is a full working solution in the Avalonia.Xaml.Behaviors repo, check this PR