Files
Lattice/Lattice.UI.DragDrop.WinUI/Integration/WinUIDragDropIntegrationService.cs
2026-01-18 16:33:35 +03:00

487 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Lattice.Core.DragDrop.Abstractions;
using Lattice.Core.DragDrop.Models;
using Lattice.Core.DragDrop.Services;
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.Abstractions;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Lattice.UI.DragDrop.WinUI.Integration;
/// <summary>
/// Сервис интеграции Drag & Drop с WinUI приложением.
/// Исправленная версия с правильной обработкой событий и очисткой ресурсов.
/// </summary>
public sealed class WinUIDragDropIntegrationService : IDisposable
{
#region Вложенные типы
private sealed class DragSourceAdapter : IDisposable
{
private readonly UIElement _element;
private readonly Func<Task<DragInfo>> _dragInfoFactory;
private readonly IDragDropService _dragDropService;
private Point _dragStartPosition;
private bool _isDragging;
private bool _disposed;
private object? _dragData;
public DragSourceAdapter(
UIElement element,
Func<Task<DragInfo>> dragInfoFactory,
IDragDropService dragDropService)
{
_element = element ?? throw new ArgumentNullException(nameof(element));
_dragInfoFactory = dragInfoFactory ?? throw new ArgumentNullException(nameof(dragInfoFactory));
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
SubscribeToEvents();
}
public DragSourceAdapter(
UIElement element,
object dragData,
IDragDropService dragDropService)
{
_element = element ?? throw new ArgumentNullException(nameof(element));
_dragData = dragData ?? throw new ArgumentNullException(nameof(dragData));
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
_dragInfoFactory = async () => new DragInfo(
_dragData,
Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move,
Point.Zero,
_element);
SubscribeToEvents();
}
private void SubscribeToEvents()
{
_element.PointerPressed += OnPointerPressed;
_element.PointerMoved += OnPointerMoved;
_element.PointerReleased += OnPointerReleased;
_element.PointerCanceled += OnPointerCanceled;
_element.PointerCaptureLost += OnPointerCaptureLost;
}
private void UnsubscribeFromEvents()
{
_element.PointerPressed -= OnPointerPressed;
_element.PointerMoved -= OnPointerMoved;
_element.PointerReleased -= OnPointerReleased;
_element.PointerCanceled -= OnPointerCanceled;
_element.PointerCaptureLost -= OnPointerCaptureLost;
}
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
if (_isDragging || _disposed) return;
var point = e.GetCurrentPoint(_element);
_dragStartPosition = new Point(point.Position.X, point.Position.Y);
}
private async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
if (_isDragging || _disposed) return;
var currentPoint = e.GetCurrentPoint(_element);
var currentPosition = new Point(currentPoint.Position.X, currentPoint.Position.Y);
var distance = CalculateDistance(_dragStartPosition, currentPosition);
if (distance > _dragDropService.DragStartThreshold)
{
await StartDragAsync(currentPosition);
}
}
private async Task StartDragAsync(Point position)
{
try
{
var dragInfo = await _dragInfoFactory();
var screenPosition = GetScreenPosition(position);
_isDragging = await _dragDropService.StartDragAsync(this, screenPosition);
}
catch (Exception ex)
{
Debug.WriteLine($"Error starting drag: {ex.Message}");
_isDragging = false;
}
}
private Point GetScreenPosition(Point relativePosition)
{
var transform = _element.TransformToVisual(null);
var point = transform.TransformPoint(new Windows.Foundation.Point(relativePosition.X, relativePosition.Y));
return new Point(point.X, point.Y);
}
private double CalculateDistance(Point p1, Point p2)
{
var dx = p2.X - p1.X;
var dy = p2.Y - p1.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
private async void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
if (!_isDragging || _disposed) return;
var point = e.GetCurrentPoint(_element);
var position = GetScreenPosition(new Point(point.Position.X, point.Position.Y));
await _dragDropService.EndDragAsync(position);
_isDragging = false;
}
private async void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
if (!_isDragging || _disposed) return;
await _dragDropService.CancelDragAsync();
_isDragging = false;
}
private async void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
if (!_isDragging || _disposed) return;
await _dragDropService.CancelDragAsync();
_isDragging = false;
}
public void Dispose()
{
if (_disposed) return;
UnsubscribeFromEvents();
if (_isDragging)
{
Task.Run(async () => await _dragDropService.CancelDragAsync()).Wait(1000);
}
_disposed = true;
GC.SuppressFinalize(this);
}
}
private sealed class DropTargetAdapter : IDisposable
{
private readonly UIElement _element;
private readonly IDropTarget _dropTarget;
private readonly IDragDropService _dragDropService;
private string? _registrationId;
private bool _disposed;
public DropTargetAdapter(
UIElement element,
IDropTarget dropTarget,
IDragDropService dragDropService)
{
_element = element ?? throw new ArgumentNullException(nameof(element));
_dropTarget = dropTarget ?? throw new ArgumentNullException(nameof(dropTarget));
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
SubscribeToEvents();
UpdateRegistration();
}
private void SubscribeToEvents()
{
_element.LayoutUpdated += OnLayoutUpdated;
_element.Unloaded += OnElementUnloaded;
}
private void UnsubscribeFromEvents()
{
_element.LayoutUpdated -= OnLayoutUpdated;
_element.Unloaded -= OnElementUnloaded;
}
private void UpdateRegistration()
{
var bounds = GetElementBounds();
if (_registrationId is null)
{
_registrationId = _dragDropService.RegisterDropTarget(_dropTarget, bounds);
}
else
{
_dragDropService.UpdateDropTargetBounds(_registrationId, bounds);
}
}
private Rect GetElementBounds()
{
var transform = _element.TransformToVisual(null);
var topLeft = transform.TransformPoint(new Windows.Foundation.Point(0, 0));
var bottomRight = transform.TransformPoint(new Windows.Foundation.Point(_element.ActualWidth, _element.ActualHeight));
return new Rect(
topLeft.X,
topLeft.Y,
bottomRight.X - topLeft.X,
bottomRight.Y - topLeft.Y);
}
private void OnLayoutUpdated(object? sender, object e)
{
if (_disposed) return;
UpdateRegistration();
}
private void OnElementUnloaded(object sender, RoutedEventArgs e)
{
Dispose();
}
public void Dispose()
{
if (_disposed) return;
UnsubscribeFromEvents();
if (_registrationId is not null)
{
_dragDropService.UnregisterDropTarget(_registrationId);
_registrationId = null;
}
if (_dropTarget is IDisposable disposable)
disposable.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}
#endregion
#region Поля
private readonly IDragDropService _dragDropService;
private readonly IDragVisualProvider _dragVisualProvider;
private readonly IDragDropHost _dragDropHost;
private readonly Dictionary<UIElement, DragSourceAdapter> _dragSources = new();
private readonly Dictionary<UIElement, DropTargetAdapter> _dropTargets = new();
private readonly Window _window;
private bool _disposed;
#endregion
#region Конструктор
/// <summary>
/// Инициализирует новый экземпляр.
/// </summary>
public WinUIDragDropIntegrationService(
Window window,
IDragDropService dragDropService,
IDragVisualProvider dragVisualProvider,
IDragDropHost dragDropHost)
{
_window = window ?? throw new ArgumentNullException(nameof(window));
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
_dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider));
_dragDropHost = dragDropHost ?? throw new ArgumentNullException(nameof(dragDropHost));
InitializeEvents();
}
#endregion
#region Публичные методы
/// <summary>
/// Регистрирует элемент как источник перетаскивания.
/// </summary>
public void RegisterDragSource(UIElement element, Func<Task<DragInfo>> dragInfoFactory)
{
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(element);
ArgumentNullException.ThrowIfNull(dragInfoFactory);
if (_dragSources.ContainsKey(element)) return;
var adapter = new DragSourceAdapter(element, dragInfoFactory, _dragDropService);
_dragSources[element] = adapter;
}
/// <summary>
/// Регистрирует элемент как источник перетаскивания с данными.
/// </summary>
public void RegisterDragSource(UIElement element, object dragData)
{
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(element);
ArgumentNullException.ThrowIfNull(dragData);
if (_dragSources.ContainsKey(element)) return;
var adapter = new DragSourceAdapter(element, dragData, _dragDropService);
_dragSources[element] = adapter;
}
/// <summary>
/// Регистрирует элемент как цель сброса.
/// </summary>
public void RegisterDropTarget(UIElement element, IDropTarget dropTarget)
{
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(element);
ArgumentNullException.ThrowIfNull(dropTarget);
if (_dropTargets.ContainsKey(element)) return;
var adapter = new DropTargetAdapter(element, dropTarget, _dragDropService);
_dropTargets[element] = adapter;
}
/// <summary>
/// Удаляет регистрацию элемента как источника.
/// </summary>
public void UnregisterDragSource(UIElement element)
{
ThrowIfDisposed();
if (_dragSources.Remove(element, out var adapter))
{
adapter.Dispose();
}
}
/// <summary>
/// Удаляет регистрацию элемента как цели.
/// </summary>
public void UnregisterDropTarget(UIElement element)
{
ThrowIfDisposed();
if (_dropTargets.Remove(element, out var adapter))
{
adapter.Dispose();
}
}
#endregion
#region Обработка событий
private void InitializeEvents()
{
_dragDropService.DragStarted += OnDragStarted;
_dragDropService.DragUpdated += OnDragUpdated;
_dragDropService.DragCompleted += OnDragCompleted;
_dragDropService.DragCancelled += OnDragCancelled;
_dragDropService.ErrorOccurred += OnErrorOccurred;
}
private void UnsubscribeFromEvents()
{
_dragDropService.DragStarted -= OnDragStarted;
_dragDropService.DragUpdated -= OnDragUpdated;
_dragDropService.DragCompleted -= OnDragCompleted;
_dragDropService.DragCancelled -= OnDragCancelled;
_dragDropService.ErrorOccurred -= OnErrorOccurred;
}
private void OnDragStarted(object? sender, DragStartedEventArgs e)
{
try
{
var visual = _dragVisualProvider.CreateDragVisual(e.DragInfo, e.StartPosition);
_dragDropHost.ShowDragVisual(visual, e.StartPosition);
}
catch (Exception ex)
{
Debug.WriteLine($"Error in OnDragStarted: {ex.Message}");
}
}
private void OnDragUpdated(object? sender, DragUpdatedEventArgs e)
{
try
{
// Обновление визуала будет через хост
}
catch (Exception ex)
{
Debug.WriteLine($"Error in OnDragUpdated: {ex.Message}");
}
}
private void OnDragCompleted(object? sender, DragCompletedEventArgs e)
{
try
{
_dragDropHost.HideDragVisual(null!); // В реальности нужно передать конкретный визуал
}
catch (Exception ex)
{
Debug.WriteLine($"Error in OnDragCompleted: {ex.Message}");
}
}
private void OnDragCancelled(object? sender, DragCancelledEventArgs e)
{
try
{
_dragDropHost.HideDragVisual(null!);
}
catch (Exception ex)
{
Debug.WriteLine($"Error in OnDragCancelled: {ex.Message}");
}
}
private void OnErrorOccurred(object? sender, DragDropErrorEventArgs e)
{
Debug.WriteLine($"DragDrop error in {e.Operation}: {e.Exception.Message}");
}
#endregion
#region Вспомогательные методы
private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
}
#endregion
#region IDisposable
/// <inheritdoc/>
public void Dispose()
{
if (_disposed) return;
UnsubscribeFromEvents();
foreach (var adapter in _dragSources.Values)
{
adapter.Dispose();
}
_dragSources.Clear();
foreach (var adapter in _dropTargets.Values)
{
adapter.Dispose();
}
_dropTargets.Clear();
_disposed = true;
GC.SuppressFinalize(this);
}
#endregion
}