[Avalonia.Native] Use relative placement for detecting drag and drop locations. (#20280)

* Use relative position of view for drag and drop

* Add Drag And Drop Test

* Add AutomationProperties
This commit is contained in:
Tim Miller
2025-12-16 01:18:14 +09:00
committed by GitHub
parent 895edfb24a
commit 367d8b293b
5 changed files with 287 additions and 3 deletions
@@ -82,6 +82,7 @@ namespace IntegrationTestApp
new("ComboBox", () => new ComboBoxPage()),
new("ContextMenu", () => new ContextMenuPage()),
new("DesktopPage", () => new DesktopPage()),
new("DragDrop", () => new DragDropPage()),
new("Embedding", () => new EmbeddingPage()),
new("Gestures", () => new GesturesPage()),
new("ListBox", () => new ListBoxPage()),
@@ -0,0 +1,62 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="IntegrationTestApp.Pages.DragDropPage">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="4">
<Button Name="ResetDragDrop" Click="ResetDragDrop_Click" Margin="0,0,8,0">Reset</Button>
<TextBlock VerticalAlignment="Center">Drop Position: </TextBlock>
<TextBlock Name="DropPosition" VerticalAlignment="Center" Margin="4,0"/>
<TextBlock VerticalAlignment="Center" Margin="8,0,0,0">Status: </TextBlock>
<TextBlock Name="DragDropStatus" VerticalAlignment="Center" Margin="4,0"/>
</StackPanel>
<!-- Use a Grid with row definitions to create offset from window origin -->
<!-- The top spacer ensures the drag/drop controls are NOT at (0,0) -->
<Grid RowDefinitions="100,*">
<!-- Spacer row to offset controls from window origin -->
<Border Grid.Row="0" Background="LightGray">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Text="Spacer (100px) - Controls below are offset from window origin"/>
</Border>
<!-- Drag and Drop test area -->
<Grid Grid.Row="1" ColumnDefinitions="*,*" Margin="20">
<!-- Drag Source -->
<Border Name="DragSource"
Grid.Column="0"
Background="CornflowerBlue"
Margin="10"
CornerRadius="8"
AutomationProperties.AccessibilityView="Content"
PointerPressed="DragSource_PointerPressed">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Drag Source"
Foreground="White"
FontWeight="Bold"/>
</Border>
<!-- Drop Target -->
<Border Name="DropTarget"
Grid.Column="1"
Background="LightGreen"
Margin="10"
CornerRadius="8"
AutomationProperties.AccessibilityView="Content"
DragDrop.AllowDrop="True">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Drop Target"
HorizontalAlignment="Center"
FontWeight="Bold"/>
<TextBlock Name="DropTargetText"
HorizontalAlignment="Center"
Text="Drop items here"/>
</StackPanel>
</Border>
</Grid>
</Grid>
</DockPanel>
</UserControl>
@@ -0,0 +1,103 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
namespace IntegrationTestApp.Pages;
public partial class DragDropPage : UserControl
{
public DragDropPage()
{
InitializeComponent();
// Set up drag-drop event handlers
AddHandler(DragDrop.DragOverEvent, DropTarget_DragOver);
AddHandler(DragDrop.DropEvent, DropTarget_Drop);
}
private async void DragSource_PointerPressed(object? sender, PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
var dragData = new DataTransfer();
dragData.Add(DataTransferItem.CreateText("TestDragData"));
DragDropStatus.Text = "Dragging...";
var result = await DragDrop.DoDragDropAsync(e, dragData, DragDropEffects.Copy | DragDropEffects.Move);
DragDropStatus.Text = result switch
{
DragDropEffects.Copy => "Copied",
DragDropEffects.Move => "Moved",
DragDropEffects.None => "Cancelled",
_ => $"Result: {result}"
};
}
}
private void DropTarget_DragOver(object? sender, DragEventArgs e)
{
// Only handle events for the drop target
if (e.Source != DropTarget && !IsChildOf(e.Source as Visual, DropTarget))
return;
e.DragEffects = DragDropEffects.Copy;
// Get the position relative to the drop target
var position = e.GetPosition(DropTarget);
DropPosition.Text = $"DragOver: ({position.X:F0}, {position.Y:F0})";
}
private void DropTarget_Drop(object? sender, DragEventArgs e)
{
// Only handle events for the drop target
if (e.Source != DropTarget && !IsChildOf(e.Source as Visual, DropTarget))
return;
// Get the position relative to the drop target
var position = e.GetPosition(DropTarget);
DropPosition.Text = $"Drop: ({position.X:F0}, {position.Y:F0})";
// Check if the position is within reasonable bounds of the drop target
var bounds = DropTarget.Bounds;
var isWithinBounds = position.X >= 0 && position.X <= bounds.Width &&
position.Y >= 0 && position.Y <= bounds.Height;
var text = e.DataTransfer.TryGetText();
if (text != null)
{
DropTargetText.Text = isWithinBounds
? $"Dropped: {text} at ({position.X:F0}, {position.Y:F0})"
: $"ERROR: Position out of bounds! ({position.X:F0}, {position.Y:F0})";
DragDropStatus.Text = isWithinBounds ? "Drop OK" : "Drop position ERROR";
}
e.DragEffects = DragDropEffects.Copy;
}
private static bool IsChildOf(Visual? child, Visual? parent)
{
if (child == null || parent == null)
return false;
var current = child.Parent as Visual;
while (current != null)
{
if (current == parent)
return true;
current = current.Parent as Visual;
}
return false;
}
private void ResetDragDrop_Click(object? sender, RoutedEventArgs e)
{
DropPosition.Text = string.Empty;
DragDropStatus.Text = string.Empty;
DropTargetText.Text = "Drop items here";
}
}