DragAndDrop core

This commit is contained in:
FrigaT
2026-01-18 16:33:35 +03:00
parent 9ea82af329
commit 79bdd8bc62
229 changed files with 21214 additions and 2494 deletions

View File

@@ -0,0 +1,829 @@
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Реализация сервиса управления операциями перетаскивания.
/// Полностью потокобезопасная реализация с поддержкой async/await.
/// </summary>
public sealed class DragDropService : IDragDropService
{
#region Nested Types
private sealed class DropTargetInfo : IDisposable
{
public required Abstractions.IDropTarget Target { get; init; }
public required Geometry.Rect Bounds { get; set; }
public required int Priority { get; init; }
public required string? Group { get; init; }
public required string Id { get; init; }
public DateTime LastAccessTime { get; set; } = DateTime.UtcNow;
public int UsageCount { get; set; }
public void Dispose()
{
if (Target is IDisposable disposable)
disposable.Dispose();
}
}
private sealed class DragOperationContext : IDisposable
{
public Abstractions.IDragSource? Source { get; set; }
public Models.DragInfo? DragInfo { get; set; }
public Abstractions.IDropTarget? CurrentDropTarget { get; set; }
public CancellationTokenSource? CancellationTokenSource { get; set; }
public DateTime StartTime { get; set; } = DateTime.UtcNow;
public bool ThresholdExceeded { get; set; }
public void Dispose()
{
DragInfo?.Dispose();
CancellationTokenSource?.Dispose();
}
}
#endregion
#region Fields
private readonly Dictionary<string, DropTargetInfo> _dropTargets = new();
private readonly ReaderWriterLockSlim _dropTargetsLock = new(LockRecursionPolicy.NoRecursion);
private readonly object _dragOperationLock = new();
private DragOperationContext? _currentDragOperation;
private Timer? _cleanupTimer;
private bool _disposed;
private int _totalDragOperations;
private int _successfulDrops;
private int _cancelledOperations;
private int _errorCount;
private long _totalOperationTicks;
#endregion
#region Events
private event EventHandler<DragStartedEventArgs>? _dragStarted;
private event EventHandler<DragUpdatedEventArgs>? _dragUpdated;
private event EventHandler<DropTargetChangedEventArgs>? _dropTargetChanged;
private event EventHandler<DragCompletedEventArgs>? _dragCompleted;
private event EventHandler<DragCancelledEventArgs>? _dragCancelled;
private event EventHandler<DragDropErrorEventArgs>? _errorOccurred;
#endregion
#region Properties
public bool IsDragActive => Volatile.Read(ref _currentDragOperation) != null;
public Models.DragInfo? CurrentDragInfo => _currentDragOperation?.DragInfo;
public Abstractions.IDropTarget? CurrentDropTarget => _currentDragOperation?.CurrentDropTarget;
public double DragStartThreshold { get; set; } = 3.0;
public bool EnableAsyncOperations { get; set; } = true;
public int AsyncOperationTimeout { get; set; } = 5000;
public event EventHandler<DragStartedEventArgs> DragStarted
{
add => _dragStarted += value;
remove => _dragStarted -= value;
}
public event EventHandler<DragUpdatedEventArgs> DragUpdated
{
add => _dragUpdated += value;
remove => _dragUpdated -= value;
}
public event EventHandler<DropTargetChangedEventArgs> DropTargetChanged
{
add => _dropTargetChanged += value;
remove => _dropTargetChanged -= value;
}
public event EventHandler<DragCompletedEventArgs> DragCompleted
{
add => _dragCompleted += value;
remove => _dragCompleted -= value;
}
public event EventHandler<DragCancelledEventArgs> DragCancelled
{
add => _dragCancelled += value;
remove => _dragCancelled -= value;
}
public event EventHandler<DragDropErrorEventArgs> ErrorOccurred
{
add => _errorOccurred += value;
remove => _errorOccurred -= value;
}
#endregion
#region Constructor
public DragDropService()
{
// Инициализация таймера очистки (каждые 5 минут)
_cleanupTimer = new Timer(CleanupExpiredTargets, null,
TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
}
#endregion
#region Registration Methods
public string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null)
{
ThrowIfDisposed();
if (target == null) throw new ArgumentNullException(nameof(target));
var id = Guid.NewGuid().ToString("N");
var info = new DropTargetInfo
{
Target = target,
Bounds = bounds,
Priority = priority,
Group = group,
Id = id
};
_dropTargetsLock.EnterWriteLock();
try
{
_dropTargets[id] = info;
return id;
}
finally
{
_dropTargetsLock.ExitWriteLock();
}
}
public bool UpdateDropTargetBounds(string id, Geometry.Rect bounds)
{
ThrowIfDisposed();
_dropTargetsLock.EnterUpgradeableReadLock();
try
{
if (!_dropTargets.TryGetValue(id, out var info))
return false;
_dropTargetsLock.EnterWriteLock();
try
{
info.Bounds = bounds;
info.LastAccessTime = DateTime.UtcNow;
return true;
}
finally
{
_dropTargetsLock.ExitWriteLock();
}
}
finally
{
_dropTargetsLock.ExitUpgradeableReadLock();
}
}
public bool UnregisterDropTarget(string id)
{
ThrowIfDisposed();
_dropTargetsLock.EnterWriteLock();
try
{
if (_dropTargets.Remove(id, out var info))
{
info.Dispose();
return true;
}
return false;
}
finally
{
_dropTargetsLock.ExitWriteLock();
}
}
public void UnregisterDropTargetsInGroup(string group)
{
ThrowIfDisposed();
if (string.IsNullOrEmpty(group)) return;
_dropTargetsLock.EnterWriteLock();
try
{
var idsToRemove = new List<string>();
foreach (var kvp in _dropTargets)
{
if (kvp.Value.Group == group)
{
idsToRemove.Add(kvp.Key);
}
}
foreach (var id in idsToRemove)
{
if (_dropTargets.Remove(id, out var info))
{
info.Dispose();
}
}
}
finally
{
_dropTargetsLock.ExitWriteLock();
}
}
#endregion
#region Async Operations
public async Task<bool> StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition)
{
ThrowIfDisposed();
if (source == null) throw new ArgumentNullException(nameof(source));
lock (_dragOperationLock)
{
if (_currentDragOperation != null)
return false;
}
try
{
Interlocked.Increment(ref _totalDragOperations);
Models.DragInfo? dragInfo = null;
// Проверка возможности начала перетаскивания
if (EnableAsyncOperations && source is Abstractions.IAsyncDragSource asyncSource)
{
var result = await ExecuteWithTimeoutAsync(
asyncSource.CanStartDragAsync(),
"CanStartDragAsync",
source);
if (!result.CanStart || result.DragInfo == null)
return false;
dragInfo = result.DragInfo;
}
else
{
if (!source.CanStartDrag(out dragInfo) || dragInfo == null)
return false;
}
var updatedDragInfo = dragInfo.CloneWithPosition(startPosition);
// Начало перетаскивания
bool started;
if (EnableAsyncOperations && source is Abstractions.IAsyncDragSource asyncSource2)
{
started = await ExecuteWithTimeoutAsync(
asyncSource2.StartDragAsync(updatedDragInfo),
"StartDragAsync",
source);
}
else
{
started = source.StartDrag(updatedDragInfo);
}
if (!started)
{
updatedDragInfo.Dispose();
return false;
}
lock (_dragOperationLock)
{
_currentDragOperation = new DragOperationContext
{
Source = source,
DragInfo = updatedDragInfo,
CancellationTokenSource = new CancellationTokenSource()
};
}
// Вызов события
_dragStarted?.Invoke(this, new DragStartedEventArgs(updatedDragInfo, startPosition));
return true;
}
catch (Exception ex)
{
Interlocked.Increment(ref _errorCount);
HandleError(ex, "StartDragAsync", source);
return false;
}
}
public async Task UpdateDragAsync(Geometry.Point position)
{
ThrowIfDisposed();
DragOperationContext? context;
lock (_dragOperationLock)
{
context = _currentDragOperation;
}
if (context?.DragInfo == null || context.Source == null)
return;
try
{
// Проверка порога начала
if (!context.ThresholdExceeded && DragStartThreshold > 0)
{
var distance = CalculateDistance(context.DragInfo.StartPosition, position);
if (distance < DragStartThreshold)
return;
context.ThresholdExceeded = true;
}
var updatedDragInfo = context.DragInfo.CloneWithPosition(position);
context.DragInfo.Dispose();
context.DragInfo = updatedDragInfo;
// Поиск новой цели сброса
var newDropTarget = await FindDropTargetAsync(position, updatedDragInfo);
// Обработка смены цели
if (context.CurrentDropTarget != newDropTarget?.Target)
{
if (context.CurrentDropTarget != null)
{
await ExecuteTargetOperationAsync(
context.CurrentDropTarget,
t => t.DragLeaveAsync(),
t => t.DragLeave(),
"DragLeave");
}
context.CurrentDropTarget = newDropTarget?.Target;
if (newDropTarget != null)
{
newDropTarget.UsageCount++;
_dropTargetChanged?.Invoke(this, new DropTargetChangedEventArgs(
updatedDragInfo, newDropTarget.Target, newDropTarget.Bounds));
}
}
// Уведомление текущей цели
if (context.CurrentDropTarget != null)
{
var dropInfo = new Models.DropInfo(
updatedDragInfo.Data,
position,
updatedDragInfo.AllowedEffects,
context.CurrentDropTarget);
await ExecuteTargetOperationAsync(
context.CurrentDropTarget,
t => t.DragOverAsync(dropInfo),
t => t.DragOver(dropInfo),
"DragOver");
}
_dragUpdated?.Invoke(this, new DragUpdatedEventArgs(updatedDragInfo, position));
}
catch (Exception ex)
{
Interlocked.Increment(ref _errorCount);
HandleError(ex, "UpdateDragAsync", context);
}
}
public async Task<Enums.DragDropEffects> EndDragAsync(Geometry.Point position)
{
ThrowIfDisposed();
DragOperationContext? context;
lock (_dragOperationLock)
{
context = _currentDragOperation;
_currentDragOperation = null;
}
if (context == null || context.DragInfo == null || context.Source == null)
{
Reset();
return Enums.DragDropEffects.None;
}
try
{
var effects = Enums.DragDropEffects.None;
var operationTime = DateTime.UtcNow - context.StartTime;
Interlocked.Add(ref _totalOperationTicks, operationTime.Ticks);
// Выполнение сброса
if (context.CurrentDropTarget != null)
{
var dropInfo = new Models.DropInfo(
context.DragInfo.Data,
position,
context.DragInfo.AllowedEffects,
context.CurrentDropTarget);
await ExecuteTargetOperationAsync(
context.CurrentDropTarget,
t => t.DropAsync(dropInfo),
t => t.Drop(dropInfo),
"Drop");
if (dropInfo.Handled)
{
effects = dropInfo.SuggestedEffects;
Interlocked.Increment(ref _successfulDrops);
}
}
// Уведомление источника
await ExecuteSourceOperationAsync(
context.Source,
s => s.DragCompletedAsync(context.DragInfo, effects),
s => s.DragCompleted(context.DragInfo, effects),
"DragCompleted",
effects);
// Событие завершения
_dragCompleted?.Invoke(this, new DragCompletedEventArgs(
context.DragInfo, position, effects));
return effects;
}
catch (Exception ex)
{
Interlocked.Increment(ref _errorCount);
HandleError(ex, "EndDragAsync", context);
return Enums.DragDropEffects.None;
}
finally
{
context.Dispose();
}
}
public async Task CancelDragAsync()
{
ThrowIfDisposed();
DragOperationContext? context;
lock (_dragOperationLock)
{
context = _currentDragOperation;
_currentDragOperation = null;
}
if (context == null || context.DragInfo == null || context.Source == null)
{
Reset();
return;
}
try
{
context.CancellationTokenSource?.Cancel();
Interlocked.Increment(ref _cancelledOperations);
await ExecuteSourceOperationAsync(
context.Source,
s => s.DragCancelledAsync(context.DragInfo),
s => s.DragCancelled(context.DragInfo),
"DragCancelled");
_dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo));
}
catch (Exception ex)
{
Interlocked.Increment(ref _errorCount);
HandleError(ex, "CancelDragAsync", context);
}
finally
{
context.Dispose();
}
}
#endregion
#region Synchronous Operations (for compatibility)
public bool StartDrag(Abstractions.IDragSource source, Geometry.Point startPosition)
{
return Task.Run(() => StartDragAsync(source, startPosition)).GetAwaiter().GetResult();
}
public void UpdateDrag(Geometry.Point position)
{
Task.Run(() => UpdateDragAsync(position)).GetAwaiter().GetResult();
}
public Enums.DragDropEffects EndDrag(Geometry.Point position)
{
return Task.Run(() => EndDragAsync(position)).GetAwaiter().GetResult();
}
public void CancelDrag()
{
Task.Run(CancelDragAsync).GetAwaiter().GetResult();
}
#endregion
#region Utility Methods
public void ClearAllDropTargets()
{
ThrowIfDisposed();
_dropTargetsLock.EnterWriteLock();
try
{
foreach (var info in _dropTargets.Values)
{
info.Dispose();
}
_dropTargets.Clear();
}
finally
{
_dropTargetsLock.ExitWriteLock();
}
}
public DragDropStats GetStats()
{
return new DragDropStats
{
TotalDragOperations = _totalDragOperations,
SuccessfulDrops = _successfulDrops,
CancelledOperations = _cancelledOperations,
ErrorCount = _errorCount,
RegisteredTargets = _dropTargets.Count,
AverageOperationTime = _totalDragOperations > 0
? TimeSpan.FromTicks(_totalOperationTicks / _totalDragOperations)
: TimeSpan.Zero
};
}
#endregion
#region Private Helper Methods
private async Task<DropTargetInfo?> FindDropTargetAsync(Geometry.Point position, Models.DragInfo dragInfo)
{
DropTargetInfo? bestTarget = null;
_dropTargetsLock.EnterReadLock();
try
{
foreach (var info in _dropTargets.Values)
{
if (!info.Bounds.Contains(position))
continue;
var dropInfo = new Models.DropInfo(
dragInfo.Data,
position,
dragInfo.AllowedEffects,
info.Target);
bool canAccept;
if (EnableAsyncOperations && info.Target is Abstractions.IAsyncDropTarget asyncTarget)
{
canAccept = await ExecuteWithTimeoutAsync(
asyncTarget.CanAcceptDropAsync(dropInfo),
"CanAcceptDropAsync",
info.Target);
}
else
{
canAccept = info.Target.CanAcceptDrop(dropInfo);
}
if (canAccept)
{
info.LastAccessTime = DateTime.UtcNow;
if (bestTarget == null || info.Priority > bestTarget.Priority)
{
bestTarget = info;
}
}
}
}
finally
{
_dropTargetsLock.ExitReadLock();
}
return bestTarget;
}
private async Task ExecuteTargetOperationAsync(
Abstractions.IDropTarget target,
Func<Abstractions.IAsyncDropTarget, Task> asyncOperation,
Action<Abstractions.IDropTarget> syncOperation,
string operationName)
{
try
{
if (EnableAsyncOperations && target is Abstractions.IAsyncDropTarget asyncTarget)
{
await ExecuteWithTimeoutAsync(
asyncOperation(asyncTarget),
$"{operationName}Async",
target);
}
else
{
syncOperation(target);
}
}
catch (Exception ex)
{
Interlocked.Increment(ref _errorCount);
HandleError(ex, operationName, target);
}
}
private async Task ExecuteSourceOperationAsync(
Abstractions.IDragSource source,
Func<Abstractions.IAsyncDragSource, Task> asyncOperation,
Action<Abstractions.IDragSource> syncOperation,
string operationName,
Enums.DragDropEffects effects = Enums.DragDropEffects.None)
{
try
{
if (EnableAsyncOperations && source is Abstractions.IAsyncDragSource asyncSource)
{
await ExecuteWithTimeoutAsync(
asyncOperation(asyncSource),
$"{operationName}Async",
source);
}
else
{
syncOperation(source);
}
}
catch (Exception ex)
{
Interlocked.Increment(ref _errorCount);
HandleError(ex, operationName, source);
}
}
private async Task<T> ExecuteWithTimeoutAsync<T>(Task<T> task, string operationName, object? context = null)
{
if (AsyncOperationTimeout <= 0)
return await task;
var timeoutTask = Task.Delay(AsyncOperationTimeout);
var completedTask = await Task.WhenAny(task, timeoutTask);
if (completedTask == timeoutTask)
{
throw new TimeoutException($"{operationName} timed out after {AsyncOperationTimeout}ms");
}
return await task;
}
private async Task ExecuteWithTimeoutAsync(Task task, string operationName, object? context = null)
{
if (AsyncOperationTimeout <= 0)
{
await task;
return;
}
var timeoutTask = Task.Delay(AsyncOperationTimeout);
var completedTask = await Task.WhenAny(task, timeoutTask);
if (completedTask == timeoutTask)
{
throw new TimeoutException($"{operationName} timed out after {AsyncOperationTimeout}ms");
}
await task;
}
private double CalculateDistance(Geometry.Point p1, Geometry.Point p2)
{
var dx = p2.X - p1.X;
var dy = p2.Y - p1.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
private void Reset()
{
lock (_dragOperationLock)
{
_currentDragOperation?.Dispose();
_currentDragOperation = null;
}
}
private void CleanupExpiredTargets(object? state)
{
var expirationTime = DateTime.UtcNow.AddMinutes(-10); // Цели старше 10 минут
_dropTargetsLock.EnterWriteLock();
try
{
var idsToRemove = new List<string>();
foreach (var kvp in _dropTargets)
{
if (kvp.Value.LastAccessTime < expirationTime)
{
idsToRemove.Add(kvp.Key);
}
}
foreach (var id in idsToRemove)
{
if (_dropTargets.Remove(id, out var info))
{
info.Dispose();
}
}
}
finally
{
_dropTargetsLock.ExitWriteLock();
}
}
private void HandleError(Exception exception, string operation, object? context = null)
{
_errorOccurred?.Invoke(this, new DragDropErrorEventArgs(exception, operation, context));
}
private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
}
#endregion
#region IDisposable Implementation
public void Dispose()
{
if (_disposed) return;
lock (_dragOperationLock)
{
if (_disposed) return;
_cleanupTimer?.Dispose();
_cleanupTimer = null;
if (_currentDragOperation != null)
{
_currentDragOperation.CancellationTokenSource?.Cancel();
_currentDragOperation.Dispose();
_currentDragOperation = null;
}
ClearAllDropTargets();
_dropTargetsLock.Dispose();
// Очистка событий
_dragStarted = null;
_dragUpdated = null;
_dropTargetChanged = null;
_dragCompleted = null;
_dragCancelled = null;
_errorOccurred = null;
_disposed = true;
}
GC.SuppressFinalize(this);
}
#endregion
}

View File

@@ -0,0 +1,22 @@
using Lattice.Core.DragDrop.Models;
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Аргументы события отмены перетаскивания.
/// </summary>
public class DragCancelledEventArgs : EventArgs
{
/// <summary>
/// Информация о перетаскивании.
/// </summary>
public DragInfo DragInfo { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragCancelledEventArgs"/>.
/// </summary>
public DragCancelledEventArgs(DragInfo dragInfo)
{
DragInfo = dragInfo;
}
}

View File

@@ -0,0 +1,35 @@
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Аргументы события завершения перетаскивания.
/// </summary>
public class DragCompletedEventArgs : EventArgs
{
/// <summary>
/// Информация о перетаскивании.
/// </summary>
public DragInfo DragInfo { get; }
/// <summary>
/// Позиция завершения перетаскивания.
/// </summary>
public Point DropPosition { get; }
/// <summary>
/// Примененные эффекты перетаскивания.
/// </summary>
public Enums.DragDropEffects Effects { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragCompletedEventArgs"/>.
/// </summary>
public DragCompletedEventArgs(DragInfo dragInfo, Point dropPosition, Enums.DragDropEffects effects)
{
DragInfo = dragInfo;
DropPosition = dropPosition;
Effects = effects;
}
}

View File

@@ -0,0 +1,32 @@
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Аргументы события ошибки в операции перетаскивания.
/// </summary>
public class DragDropErrorEventArgs : EventArgs
{
/// <summary>
/// Ошибка, которая произошла.
/// </summary>
public Exception Exception { get; }
/// <summary>
/// Операция, во время которой произошла ошибка.
/// </summary>
public string Operation { get; }
/// <summary>
/// Контекст операции.
/// </summary>
public object? Context { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragDropErrorEventArgs"/>.
/// </summary>
public DragDropErrorEventArgs(Exception exception, string operation, object? context = null)
{
Exception = exception;
Operation = operation;
Context = context;
}
}

View File

@@ -0,0 +1,29 @@
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Аргументы события начала перетаскивания.
/// </summary>
public class DragStartedEventArgs : EventArgs
{
/// <summary>
/// Информация о перетаскивании.
/// </summary>
public DragInfo DragInfo { get; }
/// <summary>
/// Начальная позиция перетаскивания.
/// </summary>
public Point StartPosition { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragStartedEventArgs"/>.
/// </summary>
public DragStartedEventArgs(DragInfo dragInfo, Point startPosition)
{
DragInfo = dragInfo;
StartPosition = startPosition;
}
}

View File

@@ -0,0 +1,29 @@
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Аргументы события обновления перетаскивания.
/// </summary>
public class DragUpdatedEventArgs : EventArgs
{
/// <summary>
/// Информация о перетаскивании.
/// </summary>
public DragInfo DragInfo { get; }
/// <summary>
/// Текущая позиция перетаскивания.
/// </summary>
public Point Position { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragUpdatedEventArgs"/>.
/// </summary>
public DragUpdatedEventArgs(DragInfo dragInfo, Point position)
{
DragInfo = dragInfo;
Position = position;
}
}

View File

@@ -0,0 +1,35 @@
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Аргументы события изменения цели сброса.
/// </summary>
public class DropTargetChangedEventArgs : EventArgs
{
/// <summary>
/// Информация о перетаскивании.
/// </summary>
public DragInfo DragInfo { get; }
/// <summary>
/// Новая цель сброса.
/// </summary>
public Abstractions.IDropTarget Target { get; }
/// <summary>
/// Границы цели.
/// </summary>
public Rect TargetBounds { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DropTargetChangedEventArgs"/>.
/// </summary>
public DropTargetChangedEventArgs(DragInfo dragInfo, Abstractions.IDropTarget target, Rect targetBounds)
{
DragInfo = dragInfo;
Target = target;
TargetBounds = targetBounds;
}
}

View File

@@ -0,0 +1,174 @@
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Предоставляет централизованный сервис для управления операциями перетаскивания.
/// </summary>
public interface IDragDropService : IDisposable
{
#region Свойства
/// <summary>
/// Активна ли операция перетаскивания.
/// </summary>
bool IsDragActive { get; }
/// <summary>
/// Информация о текущей операции.
/// </summary>
Models.DragInfo? CurrentDragInfo { get; }
/// <summary>
/// Текущая цель сброса.
/// </summary>
Abstractions.IDropTarget? CurrentDropTarget { get; }
/// <summary>
/// Порог начала перетаскивания в пикселях.
/// </summary>
double DragStartThreshold { get; set; }
/// <summary>
/// Включены ли асинхронные операции.
/// </summary>
bool EnableAsyncOperations { get; set; }
/// <summary>
/// Максимальное время ожидания асинхронной операции (мс).
/// </summary>
int AsyncOperationTimeout { get; set; }
#endregion
#region События
/// <summary>
/// Событие начала операции перетаскивания.
/// </summary>
event EventHandler<DragStartedEventArgs> DragStarted;
/// <summary>
/// Событие обновления позиции перетаскивания.
/// </summary>
event EventHandler<DragUpdatedEventArgs> DragUpdated;
/// <summary>
/// Событие изменения цели сброса.
/// </summary>
event EventHandler<DropTargetChangedEventArgs> DropTargetChanged;
/// <summary>
/// Событие завершения операции перетаскивания.
/// </summary>
event EventHandler<DragCompletedEventArgs> DragCompleted;
/// <summary>
/// Событие отмены операции перетаскивания.
/// </summary>
event EventHandler<DragCancelledEventArgs> DragCancelled;
/// <summary>
/// Событие ошибки в операции перетаскивания.
/// </summary>
event EventHandler<DragDropErrorEventArgs> ErrorOccurred;
#endregion
#region Регистрация целей сброса
/// <summary>
/// Регистрирует цель сброса.
/// </summary>
string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null);
/// <summary>
/// Обновляет границы цели сброса.
/// </summary>
bool UpdateDropTargetBounds(string id, Geometry.Rect bounds);
/// <summary>
/// Отменяет регистрацию цели сброса.
/// </summary>
bool UnregisterDropTarget(string id);
/// <summary>
/// Отменяет регистрацию всех целей в группе.
/// </summary>
void UnregisterDropTargetsInGroup(string group);
#endregion
#region Асинхронные операции
/// <summary>
/// Начинает операцию перетаскивания (асинхронно).
/// </summary>
Task<bool> StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition);
/// <summary>
/// Обновляет позицию перетаскивания (асинхронно).
/// </summary>
Task UpdateDragAsync(Geometry.Point position);
/// <summary>
/// Завершает операцию перетаскивания (асинхронно).
/// </summary>
Task<Enums.DragDropEffects> EndDragAsync(Geometry.Point position);
/// <summary>
/// Отменяет операцию перетаскивания (асинхронно).
/// </summary>
Task CancelDragAsync();
#endregion
#region Синхронные операции (для обратной совместимости)
/// <summary>
/// Начинает операцию перетаскивания (синхронно).
/// </summary>
bool StartDrag(Abstractions.IDragSource source, Geometry.Point startPosition);
/// <summary>
/// Обновляет позицию перетаскивания (синхронно).
/// </summary>
void UpdateDrag(Geometry.Point position);
/// <summary>
/// Завершает операцию перетаскивания (синхронно).
/// </summary>
Enums.DragDropEffects EndDrag(Geometry.Point position);
/// <summary>
/// Отменяет операцию перетаскивания (синхронно).
/// </summary>
void CancelDrag();
#endregion
#region Утилиты
/// <summary>
/// Очищает все зарегистрированные цели.
/// </summary>
void ClearAllDropTargets();
/// <summary>
/// Получает статистику использования.
/// </summary>
DragDropStats GetStats();
#endregion
}
/// <summary>
/// Статистика использования Drag & Drop.
/// </summary>
public class DragDropStats
{
public int TotalDragOperations { get; set; }
public int SuccessfulDrops { get; set; }
public int CancelledOperations { get; set; }
public int ErrorCount { get; set; }
public int RegisteredTargets { get; set; }
public TimeSpan AverageOperationTime { get; set; }
}