Skip to main content

How to: Implement Drag and Drop

This guide covers common drag-and-drop scenarios: initiating drags, handling drops, providing visual feedback, and accepting file drops.

Accepting dropped files​

The most common drag-and-drop scenario is accepting files that your users drag from the OS file manager.

XAML setup​

Enable dropping by setting DragDrop.AllowDrop to True on the target element:

<Border Background="#F3F4F6" Padding="40"
DragDrop.AllowDrop="True">
<TextBlock Text="Drop files here"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>

Code-behind handler​

Register handlers for DragOver (to indicate which effects you accept) and Drop (to process the dropped data):

public MainWindow()
{
InitializeComponent();

DragDrop.AddDropHandler(this, OnDrop);
DragDrop.AddDragOverHandler(this, OnDragOver);
}

private void OnDragOver(object? sender, DragEventArgs e)
{
// Accept file drops only; reject everything else
e.DragEffects = e.DataTransfer.Contains(DataFormat.File)
? DragDropEffects.Copy
: DragDropEffects.None;
}

private void OnDrop(object? sender, DragEventArgs e)
{
if (e.DataTransfer.TryGetFiles() is { } files)
{
foreach (var file in files)
{
var path = file.Path.LocalPath;
// Process the file
}
}
}

The value you set in e.DragEffects also controls the cursor, which tells your users what the drop will do:

DragDropEffectsCursorMeaning
NoneNo-drop cursorDrop is not allowed.
CopyCopy cursor (+)The item will be copied.
MoveMove cursorThe item will be moved.
LinkLink cursorA link or shortcut will be created.
tip

Always set e.DragEffects in your DragOver handler. If you do not, the platform may show a "not allowed" cursor even when your control can accept the drop.

Accepting dropped text​

You can also accept plain text drops. Use TryGetText() to retrieve the string value:

private void OnDrop(object? sender, DragEventArgs e)
{
if (e.DataTransfer.TryGetText() is { } text)
{
// Use the dropped text
ViewModel.Content = text;
}
}

Initiating a drag operation​

To start a drag from your control (for example, from a list item), call DragDrop.DoDragDropAsync inside a pointer-pressed handler:

private async void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (sender is not Control control) return;

var dragData = new DataTransfer();
dragData.Add(DataTransferItem.CreateText("Dragged item text"));

var result = await DragDrop.DoDragDropAsync(e, dragData, DragDropEffects.Copy | DragDropEffects.Move);

if (result == DragDropEffects.Move)
{
// Item was moved, remove from source
}
}
warning

Avoid starting a drag on every PointerPressed event. Instead, add a minimum distance threshold or wait for PointerMoved to confirm the user intends to drag rather than click.

note

Do not dispose the DataTransfer you pass to DoDragDropAsync, and do not create it in a using statement. Avalonia disposes it automatically when the drag operation completes.

Drag between lists​

A common pattern is dragging items between two list controls. You set up one handler to initiate the drag from the source and another to accept the drop on the target.

Both handlers share a custom data format. Because the item is a view model that never leaves your application, you can make it an in-process format:

private static readonly DataFormat<ItemViewModel> ItemFormat =
DataFormat.CreateInProcessFormat<ItemViewModel>("my-app-item");

Source list​

private async void SourceList_PointerPressed(object? sender, PointerPressedEventArgs e)
{
if (sender is ListBox listBox && listBox.SelectedItem is ItemViewModel item)
{
var data = new DataTransfer();
data.Add(DataTransferItem.Create(ItemFormat, item));

var result = await DragDrop.DoDragDropAsync(e, data, DragDropEffects.Move);

if (result == DragDropEffects.Move)
ViewModel.SourceItems.Remove(item);
}
}

Target list​

In the drop handler, retrieve your custom object and add it to the target collection:

private void TargetList_Drop(object? sender, DragEventArgs e)
{
if (e.DataTransfer.TryGetValue(ItemFormat) is { } item)
{
ViewModel.TargetItems.Add(item);
e.DragEffects = DragDropEffects.Move;
}
}

Visual feedback during drag​

Providing visual feedback helps your users understand where they can drop. This example changes the drop target's appearance when the user drags something over it, assuming the target is a Border declared in XAML with x:Name="DropZone" and DragDrop.AllowDrop="True":

public MainWindow()
{
InitializeComponent();

DragDrop.AddDragEnterHandler(DropZone, (s, e) =>
{
DropZone.BorderBrush = Brushes.Blue;
DropZone.BorderThickness = new Thickness(2);
});

DragDrop.AddDragLeaveHandler(DropZone, (s, e) =>
{
DropZone.BorderBrush = Brushes.Transparent;
DropZone.BorderThickness = new Thickness(0);
});

DragDrop.AddDropHandler(DropZone, (s, e) =>
{
DropZone.BorderBrush = Brushes.Transparent;
DropZone.BorderThickness = new Thickness(0);
// Handle drop...
});
}

Attach the handlers to the drop zone itself rather than to the window. Otherwise, the highlight appears whenever the user drags anywhere over the window.

tip

Reset the visual state in both the DragLeave and Drop handlers. If you only reset on DragLeave, the highlight will remain when the user completes a drop.

Custom data formats​

To transfer your own data, create a typed DataFormat<T> once and reuse it on both the drag source and the drop target:

// Set
var data = new DataTransfer();
data.Add(DataTransferItem.Create(MyTypeFormat, myObject));

// Get
if (e.DataTransfer.TryGetValue(MyTypeFormat) is { } obj)
{
// Use obj
}

MyTypeFormat is a static field created with a DataFormat method, for example DataFormat.CreateInProcessFormat<MyType>("my-app-type"). See the DataFormat<T> API reference for a full list of available methods and supported data types.

caution

Identifiers passed to CreateStringApplicationFormat and CreateBytesApplicationFormat can contain only ASCII letters, digits, dots (.) and hyphens (-). MIME-style identifiers such as application/x-my-type are not accepted.

Edge cases and troubleshooting​

  • Drop handler not firing: Verify that DragDrop.AllowDrop is set to True on the target element and that your DragOver handler sets e.DragEffects to a value other than None.
  • Drag starts on single click: Add a distance threshold before calling DoDragDropAsync. Without one, a simple click triggers a drag, which can confuse your users.
  • Custom data lost across processes: Data in a format created with CreateInProcessFormat never leaves the current process. To drag custom data to another process, serialize it to a string or byte[] and use an application or platform format instead.
  • Multiple data formats: Call Set several times on the same DataTransferItem, once per format, then add the item to the DataTransfer. This lets drop targets choose the richest format they support.
  • Disposed data during a drag: Do not dispose the DataTransfer you pass to DoDragDropAsync. Avalonia disposes it when the drag completes.

Platform notes​

PlatformSupport levelNotes
WindowsFullFile drops from Explorer, inter-app text and bitmap drops, and custom formats within your application all work.
macOSFullFile drops from Finder are supported. The system drag cursor respects DragDropEffects.
Linux (X11/Wayland)FullBehavior matches Windows. Wayland compositors may differ slightly in cursor rendering.
Browser (WebAssembly)LimitedFile drops from the OS file manager are supported in most browsers. Dragging between elements within your app requires a custom implementation because the browser handles pointer capture.
iOS / AndroidNot supportedDrag-and-drop is not available. Consider using long-press gestures or list reorder patterns for similar functionality.

See also​