295 lines
9.0 KiB
C#
295 lines
9.0 KiB
C#
using Lattice.Core.DragDrop.Abstractions;
|
||
using Lattice.Core.DragDrop.Models;
|
||
using Lattice.Core.Geometry;
|
||
using Lattice.UI.DragDrop.WinUI.Services;
|
||
using Microsoft.UI.Xaml;
|
||
using System;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
|
||
|
||
/// <summary>
|
||
/// Реализация поведения цели сброса для элементов WinUI.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>
|
||
/// Этот класс обрабатывает события перетаскивания WinUI и преобразует их в вызовы
|
||
/// методов интерфейса <see cref="IDropTarget"/>.
|
||
/// </para>
|
||
/// <para>
|
||
/// Поведение автоматически регистрирует элемент в системе перетаскивания,
|
||
/// обновляет его границы при изменении размера и обрабатывает все этапы операции сброса.
|
||
/// </para>
|
||
/// </remarks>
|
||
public sealed class WinUIDropTargetBehavior : IDropTarget
|
||
{
|
||
#region Поля
|
||
|
||
private readonly Lattice.Core.DragDrop.Services.IDragDropService _dragDropService;
|
||
private readonly WinUIDragDropHost _host;
|
||
|
||
private FrameworkElement? _element;
|
||
private string? _registrationId;
|
||
private Rect _currentBounds;
|
||
|
||
#endregion
|
||
|
||
#region Конструктор
|
||
|
||
/// <summary>
|
||
/// Инициализирует новый экземпляр класса <see cref="WinUIDropTargetBehavior"/>.
|
||
/// </summary>
|
||
/// <param name="dragDropService">Сервис для управления операциями перетаскивания.</param>
|
||
/// <param name="host">Хост для отображения визуальных элементов.</param>
|
||
/// <exception cref="ArgumentNullException">
|
||
/// Выбрасывается, если любой из параметров равен null.
|
||
/// </exception>
|
||
public WinUIDropTargetBehavior(Lattice.Core.DragDrop.Services.IDragDropService dragDropService, WinUIDragDropHost host)
|
||
{
|
||
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
|
||
_host = host ?? throw new ArgumentNullException(nameof(host));
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Публичные методы
|
||
|
||
/// <summary>
|
||
/// Прикрепляет поведение к указанному элементу.
|
||
/// </summary>
|
||
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
|
||
/// <exception cref="ArgumentNullException">
|
||
/// Выбрасывается, если <paramref name="element"/> равен null.
|
||
/// </exception>
|
||
/// <remarks>
|
||
/// После вызова этого метода:
|
||
/// 1. Элементу устанавливается <see cref="UIElement.AllowDrop"/> = true
|
||
/// 2. Поведение подписывается на события перетаскивания
|
||
/// 3. Элемент регистрируется в системе перетаскивания
|
||
/// </remarks>
|
||
public void Attach(FrameworkElement element)
|
||
{
|
||
Detach();
|
||
|
||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||
element.AllowDrop = true;
|
||
|
||
SubscribeToEvents();
|
||
UpdateBounds();
|
||
RegisterToService();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Открепляет поведение от элемента.
|
||
/// </summary>
|
||
public void Detach()
|
||
{
|
||
if (_element == null) return;
|
||
|
||
UnsubscribeFromEvents();
|
||
UnregisterFromService();
|
||
|
||
_element.AllowDrop = false;
|
||
_element = null;
|
||
_currentBounds = Rect.Empty;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Обработчики событий
|
||
|
||
private void SubscribeToEvents()
|
||
{
|
||
if (_element == null) return;
|
||
|
||
_element.DragEnter += OnDragEnter;
|
||
_element.DragOver += OnDragOver;
|
||
_element.DragLeave += OnDragLeave;
|
||
_element.Drop += OnDrop;
|
||
_element.SizeChanged += OnSizeChanged;
|
||
}
|
||
|
||
private void UnsubscribeFromEvents()
|
||
{
|
||
if (_element == null) return;
|
||
|
||
_element.DragEnter -= OnDragEnter;
|
||
_element.DragOver -= OnDragOver;
|
||
_element.DragLeave -= OnDragLeave;
|
||
_element.Drop -= OnDrop;
|
||
_element.SizeChanged -= OnSizeChanged;
|
||
}
|
||
|
||
private async void OnDragEnter(object sender, DragEventArgs e)
|
||
{
|
||
if (_element == null) return;
|
||
|
||
try
|
||
{
|
||
var position = e.GetPosition(_element);
|
||
var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y));
|
||
|
||
if (await CanAcceptDropAsync(dropInfo))
|
||
{
|
||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
private async void OnDragOver(object sender, DragEventArgs e)
|
||
{
|
||
if (_element == null) return;
|
||
|
||
try
|
||
{
|
||
var position = e.GetPosition(_element);
|
||
var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y));
|
||
|
||
if (await CanAcceptDropAsync(dropInfo))
|
||
{
|
||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
|
||
await OnDragOverAsync(dropInfo);
|
||
}
|
||
|
||
e.Handled = true;
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
private async void OnDragLeave(object sender, DragEventArgs e)
|
||
{
|
||
await OnDragLeaveAsync();
|
||
}
|
||
|
||
private async void OnDrop(object sender, DragEventArgs e)
|
||
{
|
||
if (_element == null) return;
|
||
|
||
try
|
||
{
|
||
var position = e.GetPosition(_element);
|
||
var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y));
|
||
|
||
if (await CanAcceptDropAsync(dropInfo))
|
||
{
|
||
await OnDropAsync(dropInfo);
|
||
dropInfo.MarkAsHandled();
|
||
e.Handled = true;
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||
{
|
||
UpdateBounds();
|
||
}
|
||
|
||
private DropInfo CreateDropInfo(DragEventArgs e, Point position)
|
||
{
|
||
object? data = null;
|
||
|
||
if (e.DataView.Properties.TryGetValue("DragData", out var dragData))
|
||
{
|
||
data = dragData;
|
||
}
|
||
|
||
return new DropInfo(
|
||
data: data,
|
||
position: position,
|
||
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy |
|
||
Core.DragDrop.Enums.DragDropEffects.Move,
|
||
target: _element
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает границы элемента в экранных координатах.
|
||
/// </summary>
|
||
/// <returns>Прямоугольник с границами элемента или <see cref="Rect.Empty"/>, если
|
||
/// элемент не загружен или не может быть преобразован.</returns>
|
||
private Rect GetScreenBounds()
|
||
{
|
||
if (_element == null || !_element.IsLoaded)
|
||
return Rect.Empty;
|
||
|
||
try
|
||
{
|
||
var transform = _element.TransformToVisual(Window.Current.Content);
|
||
var position = transform.TransformPoint(new Windows.Foundation.Point(0, 0));
|
||
|
||
return new Rect(
|
||
position.X,
|
||
position.Y,
|
||
_element.ActualWidth,
|
||
_element.ActualHeight
|
||
);
|
||
}
|
||
catch
|
||
{
|
||
return Rect.Empty;
|
||
}
|
||
}
|
||
|
||
private void UpdateBounds()
|
||
{
|
||
if (_element == null) return;
|
||
|
||
_currentBounds = GetScreenBounds();
|
||
|
||
if (_registrationId != null && _currentBounds != Rect.Empty)
|
||
{
|
||
_dragDropService.UpdateDropTargetBounds(_registrationId, _currentBounds);
|
||
}
|
||
}
|
||
|
||
private void RegisterToService()
|
||
{
|
||
if (_element == null) return;
|
||
|
||
_currentBounds = GetScreenBounds();
|
||
|
||
if (_currentBounds != Rect.Empty && _registrationId == null)
|
||
{
|
||
_registrationId = _dragDropService.RegisterDropTarget(this, _currentBounds);
|
||
}
|
||
}
|
||
|
||
private void UnregisterFromService()
|
||
{
|
||
if (_registrationId != null)
|
||
{
|
||
_dragDropService.UnregisterDropTarget(_registrationId);
|
||
_registrationId = null;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region IDropTarget Implementation
|
||
|
||
public Task<bool> CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default)
|
||
{
|
||
return Task.FromResult(true); // Принимаем все по умолчанию
|
||
}
|
||
|
||
public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct = default)
|
||
{
|
||
dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Move;
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
public Task OnDropAsync(DropInfo dropInfo, CancellationToken ct = default)
|
||
{
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
public Task OnDragLeaveAsync(CancellationToken ct = default)
|
||
{
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
#endregion
|
||
} |