Переработаны методы.

This commit is contained in:
2026-01-25 02:37:16 +03:00
parent a6ee6fcb36
commit be12154262
17 changed files with 897 additions and 1116 deletions

View File

@@ -1,13 +1,52 @@
namespace Lattice.Core.DragDrop.Services;
using Lattice.Core.DragDrop.Abstractions;
using Lattice.Core.DragDrop.Constants;
using Lattice.Core.DragDrop.Enums;
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Реализация сервиса управления операциями перетаскивания.
/// Полностью потокобезопасная реализация с поддержкой async/await.
/// Центральный сервис управления операциями перетаскивания.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="DragDropService"/> является основным компонентом системы drag-and-drop,
/// который координирует взаимодействие между источниками данных (<see cref="Abstractions.IDragSource"/>)
/// и целями сброса (<see cref="Abstractions.IDropTarget"/>).
/// </para>
/// <para>
/// Основные функции сервиса:
/// </para>
/// <list type="bullet">
/// <item>Регистрация и управление целями сброса</item>
/// <item>Оркестрация жизненного цикла операций перетаскивания</item>
/// <item>Обработка событий мыши и клавиатуры</item>
/// <item>Распространение информации между компонентами</item>
/// <item>Обеспечение потокобезопасности операций</item>
/// </list>
/// <para>
/// Сервис поддерживает полностью асинхронную модель работы, уведомления через события
/// и статистику использования. Все операции защищены от параллельного доступа
/// и обеспечивают корректную очистку ресурсов.
/// </para>
/// <para>
/// Для использования сервиса необходимо:
/// <list type="number">
/// <item>Зарегистрировать цели сброса с помощью <see cref="RegisterDropTarget"/></item>
/// <item>Вызывать методы <see cref="StartDragAsync"/>, <see cref="UpdateDragAsync"/>,
/// <see cref="EndDragAsync"/> в ответ на действия пользователя</item>
/// <item>Подписаться на события для отслеживания состояния операций</item>
/// </list>
/// </para>
/// </remarks>
public sealed class DragDropService : IDragDropService
{
#region Nested Types
/// <summary>
/// Информация о зарегистрированной цели сброса.
/// </summary>
private sealed class DropTargetInfo : IDisposable
{
public required Abstractions.IDropTarget Target { get; init; }
@@ -25,6 +64,9 @@ public sealed class DragDropService : IDragDropService
}
}
/// <summary>
/// Контекст текущей операции перетаскивания.
/// </summary>
private sealed class DragOperationContext : IDisposable
{
public Abstractions.IDragSource? Source { get; set; }
@@ -33,6 +75,7 @@ public sealed class DragDropService : IDragDropService
public CancellationTokenSource? CancellationTokenSource { get; set; }
public DateTime StartTime { get; set; } = DateTime.UtcNow;
public bool ThresholdExceeded { get; set; }
public Point LastPosition { get; set; }
public void Dispose()
{
@@ -74,48 +117,60 @@ public sealed class DragDropService : IDragDropService
#region Properties
/// <inheritdoc/>
public bool IsDragActive => Volatile.Read(ref _currentDragOperation) != null;
/// <inheritdoc/>
public Models.DragInfo? CurrentDragInfo => _currentDragOperation?.DragInfo;
/// <inheritdoc/>
public Abstractions.IDropTarget? CurrentDropTarget => _currentDragOperation?.CurrentDropTarget;
public double DragStartThreshold { get; set; } = 3.0;
/// <inheritdoc/>
public double DragStartThreshold { get; set; } = DragDropConstants.DefaultDragThreshold;
/// <inheritdoc/>
public bool EnableAsyncOperations { get; set; } = true;
public int AsyncOperationTimeout { get; set; } = 5000;
/// <inheritdoc/>
public int AsyncOperationTimeout { get; set; } = DragDropConstants.DefaultAsyncTimeout;
/// <inheritdoc/>
public event EventHandler<DragStartedEventArgs> DragStarted
{
add => _dragStarted += value;
remove => _dragStarted -= value;
}
/// <inheritdoc/>
public event EventHandler<DragUpdatedEventArgs> DragUpdated
{
add => _dragUpdated += value;
remove => _dragUpdated -= value;
}
/// <inheritdoc/>
public event EventHandler<DropTargetChangedEventArgs> DropTargetChanged
{
add => _dropTargetChanged += value;
remove => _dropTargetChanged -= value;
}
/// <inheritdoc/>
public event EventHandler<DragCompletedEventArgs> DragCompleted
{
add => _dragCompleted += value;
remove => _dragCompleted -= value;
}
/// <inheritdoc/>
public event EventHandler<DragCancelledEventArgs> DragCancelled
{
add => _dragCancelled += value;
remove => _dragCancelled -= value;
}
/// <inheritdoc/>
public event EventHandler<DragDropErrorEventArgs> ErrorOccurred
{
add => _errorOccurred += value;
@@ -126,6 +181,17 @@ public sealed class DragDropService : IDragDropService
#region Constructor
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragDropService"/>.
/// </summary>
/// <remarks>
/// Создает сервис с настройками по умолчанию:
/// <list type="bullet">
/// <item>Порог начала перетаскивания: <see cref="Constants.DragDropConstants.DefaultDragThreshold"/> пикселей</item>
/// <item>Таймаут асинхронных операций: <see cref="Constants.DragDropConstants.DefaultAsyncTimeout"/> миллисекунд</item>
/// <item>Включены асинхронные операции: true</item>
/// </list>
/// </remarks>
public DragDropService()
{
// Инициализация таймера очистки (каждые 5 минут)
@@ -138,6 +204,7 @@ public sealed class DragDropService : IDragDropService
#region Registration Methods
/// <inheritdoc/>
public string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null)
{
ThrowIfDisposed();
@@ -165,6 +232,7 @@ public sealed class DragDropService : IDragDropService
}
}
/// <inheritdoc/>
public bool UpdateDropTargetBounds(string id, Geometry.Rect bounds)
{
ThrowIfDisposed();
@@ -193,6 +261,7 @@ public sealed class DragDropService : IDragDropService
}
}
/// <inheritdoc/>
public bool UnregisterDropTarget(string id)
{
ThrowIfDisposed();
@@ -213,6 +282,7 @@ public sealed class DragDropService : IDragDropService
}
}
/// <inheritdoc/>
public void UnregisterDropTargetsInGroup(string group)
{
ThrowIfDisposed();
@@ -249,7 +319,8 @@ public sealed class DragDropService : IDragDropService
#region Async Operations
public async Task<bool> StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition)
/// <inheritdoc/>
public async Task<bool> StartDragAsync(IDragSource source, Point startPosition)
{
ThrowIfDisposed();
if (source == null) throw new ArgumentNullException(nameof(source));
@@ -264,60 +335,34 @@ public sealed class DragDropService : IDragDropService
{
Interlocked.Increment(ref _totalDragOperations);
Models.DragInfo? dragInfo = null;
DragInfo? dragInfo;
// Проверка возможности начала перетаскивания
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource)
// Пытаемся начать перетаскивание
if (EnableAsyncOperations)
{
var result = await ExecuteWithTimeoutAsync(
asyncSource.CanStartDragAsync(),
"CanStartDragAsync",
dragInfo = await ExecuteWithTimeoutAsync(
source.TryStartDragAsync(startPosition),
"TryStartDragAsync",
source);
if (!result.CanStart || result.DragInfo == null)
return false;
dragInfo = result.DragInfo;
}
else
{
var startDragResult = await source.CanStartDragAsync();
if (!startDragResult.CanStart || startDragResult.DragInfo == null)
return false;
dragInfo = startDragResult.DragInfo;
dragInfo = await source.TryStartDragAsync(startPosition);
}
if (dragInfo == null)
return false;
var updatedDragInfo = dragInfo.CloneWithPosition(startPosition);
// Начало перетаскивания
bool started;
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource2)
{
started = await ExecuteWithTimeoutAsync(
asyncSource2.StartDragAsync(updatedDragInfo),
"StartDragAsync",
source);
}
else
{
started = await source.StartDragAsync(updatedDragInfo);
}
if (!started)
{
updatedDragInfo.Dispose();
return false;
}
lock (_dragOperationLock)
{
_currentDragOperation = new DragOperationContext
{
Source = source,
DragInfo = updatedDragInfo,
CancellationTokenSource = new CancellationTokenSource()
CancellationTokenSource = new CancellationTokenSource(),
LastPosition = startPosition
};
}
@@ -333,7 +378,8 @@ public sealed class DragDropService : IDragDropService
}
}
public async Task UpdateDragAsync(Geometry.Point position)
/// <inheritdoc/>
public async Task UpdateDragAsync(Point position)
{
ThrowIfDisposed();
@@ -359,8 +405,9 @@ public sealed class DragDropService : IDragDropService
}
var updatedDragInfo = context.DragInfo.CloneWithPosition(position);
context.DragInfo = updatedDragInfo;
context.DragInfo.Dispose();
context.DragInfo = updatedDragInfo;
context.LastPosition = position;
// Поиск новой цели сброса
var newDropTarget = await FindDropTargetAsync(position, updatedDragInfo);
@@ -372,9 +419,8 @@ public sealed class DragDropService : IDragDropService
{
await ExecuteTargetOperationAsync(
context.CurrentDropTarget,
t => t.DragLeaveAsync(),
t => t.DragLeaveAsync(),
"DragLeave");
t => t.OnDragLeaveAsync(),
"OnDragLeave");
}
context.CurrentDropTarget = newDropTarget?.Target;
@@ -383,14 +429,14 @@ public sealed class DragDropService : IDragDropService
{
newDropTarget.UsageCount++;
_dropTargetChanged?.Invoke(this, new DropTargetChangedEventArgs(
updatedDragInfo, newDropTarget.Target, newDropTarget.Bounds));
updatedDragInfo, position, newDropTarget.Target, newDropTarget.Bounds));
}
}
// Уведомление текущей цели
if (context.CurrentDropTarget != null)
{
var dropInfo = new Models.DropInfo(
var dropInfo = new DropInfo(
updatedDragInfo.Data,
position,
updatedDragInfo.AllowedEffects,
@@ -398,9 +444,8 @@ public sealed class DragDropService : IDragDropService
await ExecuteTargetOperationAsync(
context.CurrentDropTarget,
t => t.DragOverAsync(dropInfo),
t => t.DragOverAsync(dropInfo),
"DragOver");
t => t.OnDragOverAsync(dropInfo),
"OnDragOver");
}
_dragUpdated?.Invoke(this, new DragUpdatedEventArgs(updatedDragInfo, position));
@@ -412,7 +457,8 @@ public sealed class DragDropService : IDragDropService
}
}
public async Task<Enums.DragDropEffects> EndDragAsync(Geometry.Point position)
/// <inheritdoc/>
public async Task<DragDropEffects> EndDragAsync(Point position)
{
ThrowIfDisposed();
@@ -426,19 +472,19 @@ public sealed class DragDropService : IDragDropService
if (context == null || context.DragInfo == null || context.Source == null)
{
Reset();
return Enums.DragDropEffects.None;
return DragDropEffects.None;
}
try
{
var effects = Enums.DragDropEffects.None;
var effects = DragDropEffects.None;
var operationTime = DateTime.UtcNow - context.StartTime;
Interlocked.Add(ref _totalOperationTicks, operationTime.Ticks);
// Выполнение сброса
if (context.CurrentDropTarget != null)
{
var dropInfo = new Models.DropInfo(
var dropInfo = new DropInfo(
context.DragInfo.Data,
position,
context.DragInfo.AllowedEffects,
@@ -446,9 +492,8 @@ public sealed class DragDropService : IDragDropService
await ExecuteTargetOperationAsync(
context.CurrentDropTarget,
t => t.DropAsync(dropInfo),
t => t.DropAsync(dropInfo),
"Drop");
t => t.OnDropAsync(dropInfo),
"OnDrop");
if (dropInfo.Handled)
{
@@ -460,10 +505,8 @@ public sealed class DragDropService : IDragDropService
// Уведомление источника
await ExecuteSourceOperationAsync(
context.Source,
s => s.DragCompletedAsync(context.DragInfo, effects),
s => s.DragCompletedAsync(context.DragInfo, effects),
"DragCompleted",
effects);
s => s.OnDragCompletedAsync(context.DragInfo, effects),
"OnDragCompleted");
// Событие завершения
_dragCompleted?.Invoke(this, new DragCompletedEventArgs(
@@ -475,7 +518,7 @@ public sealed class DragDropService : IDragDropService
{
Interlocked.Increment(ref _errorCount);
HandleError(ex, "EndDragAsync", context);
return Enums.DragDropEffects.None;
return DragDropEffects.None;
}
finally
{
@@ -483,6 +526,7 @@ public sealed class DragDropService : IDragDropService
}
}
/// <inheritdoc/>
public async Task CancelDragAsync()
{
ThrowIfDisposed();
@@ -507,11 +551,10 @@ public sealed class DragDropService : IDragDropService
await ExecuteSourceOperationAsync(
context.Source,
s => s.DragCancelledAsync(context.DragInfo),
s => s.DragCancelledAsync(context.DragInfo),
"DragCancelled");
s => s.OnDragCancelledAsync(context.DragInfo),
"OnDragCancelled");
_dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo));
_dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo, context.LastPosition));
}
catch (Exception ex)
{
@@ -528,6 +571,7 @@ public sealed class DragDropService : IDragDropService
#region Utility Methods
/// <inheritdoc/>
public void ClearAllDropTargets()
{
ThrowIfDisposed();
@@ -547,6 +591,7 @@ public sealed class DragDropService : IDragDropService
}
}
/// <inheritdoc/>
public DragDropStats GetStats()
{
return new DragDropStats
@@ -609,23 +654,22 @@ public sealed class DragDropService : IDragDropService
}
private async Task ExecuteTargetOperationAsync(
Abstractions.IDropTarget target,
Func<Abstractions.IDropTarget, Task> asyncOperation,
Action<Abstractions.IDropTarget> syncOperation,
IDropTarget target,
Func<IDropTarget, Task> operation,
string operationName)
{
try
{
if (EnableAsyncOperations && target is Abstractions.IDropTarget asyncTarget)
if (EnableAsyncOperations)
{
await ExecuteWithTimeoutAsync(
asyncOperation(asyncTarget),
operation(target),
$"{operationName}Async",
target);
}
else
{
syncOperation(target);
await operation(target);
}
}
catch (Exception ex)
@@ -636,24 +680,22 @@ public sealed class DragDropService : IDragDropService
}
private async Task ExecuteSourceOperationAsync(
Abstractions.IDragSource source,
Func<Abstractions.IDragSource, Task> asyncOperation,
Action<Abstractions.IDragSource> syncOperation,
string operationName,
Enums.DragDropEffects effects = Enums.DragDropEffects.None)
IDragSource source,
Func<IDragSource, Task> operation,
string operationName)
{
try
{
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource)
if (EnableAsyncOperations)
{
await ExecuteWithTimeoutAsync(
asyncOperation(asyncSource),
operation(source),
$"{operationName}Async",
source);
}
else
{
syncOperation(source);
await operation(source);
}
}
catch (Exception ex)

View File

@@ -1,22 +0,0 @@
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

@@ -1,35 +0,0 @@
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

@@ -1,32 +0,0 @@
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,237 @@
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
namespace Lattice.Core.DragDrop.Services;
/// <summary>
/// Предоставляет базовые данные для событий перетаскивания.
/// </summary>
/// <remarks>
/// Этот класс содержит общие свойства, которые используются в большинстве событий
/// системы перетаскивания. Является базовым классом для специализированных событий.
/// </remarks>
public abstract class DragEventArgs : EventArgs
{
/// <summary>
/// Получает информацию о текущей операции перетаскивания.
/// </summary>
/// <value>
/// Объект <see cref="DragInfo"/>, содержащий данные, эффекты и метаданные операции.
/// Всегда возвращает актуальную информацию на момент возникновения события.
/// </value>
public DragInfo DragInfo { get; }
/// <summary>
/// Получает текущую позицию курсора в координатах экрана.
/// </summary>
/// <value>
/// Точка, представляющая положение курсора мыши в момент события.
/// Используется для точного позиционирования и визуальной обратной связи.
/// </value>
public Point Position { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragEventArgs"/>.
/// </summary>
/// <param name="dragInfo">Информация о перетаскивании.</param>
/// <param name="position">Текущая позиция курсора.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, когда <paramref name="dragInfo"/> равен null.
/// </exception>
protected DragEventArgs(DragInfo dragInfo, Point position)
{
DragInfo = dragInfo ?? throw new ArgumentNullException(nameof(dragInfo));
Position = position;
}
}
/// <summary>
/// Предоставляет данные для события начала перетаскивания.
/// </summary>
/// <remarks>
/// Возникает, когда пользователь начинает операцию перетаскивания.
/// Это первое событие в жизненном цикле операции.
/// </remarks>
public sealed class DragStartedEventArgs : DragEventArgs
{
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragStartedEventArgs"/>.
/// </summary>
/// <param name="dragInfo">Информация о перетаскивании.</param>
/// <param name="position">Начальная позиция перетаскивания.</param>
public DragStartedEventArgs(DragInfo dragInfo, Point position)
: base(dragInfo, position)
{
}
}
/// <summary>
/// Предоставляет данные для события обновления позиции перетаскивания.
/// </summary>
/// <remarks>
/// Возникает при каждом перемещении курсора во время операции перетаскивания.
/// Может вызываться многократно с высокой частотой, поэтому обработчики
/// должны быть оптимизированы для производительности.
/// </remarks>
public sealed class DragUpdatedEventArgs : DragEventArgs
{
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragUpdatedEventArgs"/>.
/// </summary>
/// <param name="dragInfo">Информация о перетаскивании.</param>
/// <param name="position">Текущая позиция курсора.</param>
public DragUpdatedEventArgs(DragInfo dragInfo, Point position)
: base(dragInfo, position)
{
}
}
/// <summary>
/// Предоставляет данные для события завершения перетаскивания.
/// </summary>
/// <remarks>
/// Возникает, когда пользователь завершает операцию перетаскивания
/// (отпускает кнопку мыши над целью или вне области сброса).
/// Содержит информацию о примененных эффектах и результатах операции.
/// </remarks>
public sealed class DragCompletedEventArgs : DragEventArgs
{
/// <summary>
/// Получает эффекты, примененные при завершении операции.
/// </summary>
/// <value>
/// Комбинация флагов <see cref="Enums.DragDropEffects"/>, указывающая,
/// как были обработаны данные (копирование, перемещение и т.д.).
/// </value>
public Enums.DragDropEffects Effects { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragCompletedEventArgs"/>.
/// </summary>
/// <param name="dragInfo">Информация о перетаскивании.</param>
/// <param name="position">Позиция завершения операции.</param>
/// <param name="effects">Примененные эффекты перетаскивания.</param>
public DragCompletedEventArgs(DragInfo dragInfo, Point position, Enums.DragDropEffects effects)
: base(dragInfo, position)
{
Effects = effects;
}
}
/// <summary>
/// Предоставляет данные для события отмены перетаскивания.
/// </summary>
/// <remarks>
/// Возникает, когда операция перетаскивания была отменена пользователем
/// (например, нажатием клавиши Escape) или системой (например, при ошибке).
/// После этого события система возвращается в исходное состояние.
/// </remarks>
public sealed class DragCancelledEventArgs : DragEventArgs
{
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragCancelledEventArgs"/>.
/// </summary>
/// <param name="dragInfo">Информация о перетаскивании.</param>
/// <param name="position">Позиция в момент отмены.</param>
public DragCancelledEventArgs(DragInfo dragInfo, Point position)
: base(dragInfo, position)
{
}
}
/// <summary>
/// Предоставляет данные для события изменения цели сброса.
/// </summary>
/// <remarks>
/// Возникает, когда курсор перемещается с одной цели сброса на другую
/// или покидает область всех целей. Позволяет обновлять визуальную
/// обратную связь при изменении контекста сброса.
/// </remarks>
public sealed class DropTargetChangedEventArgs : DragEventArgs
{
/// <summary>
/// Получает новую цель сброса, над которой находится курсор.
/// </summary>
/// <value>
/// Объект <see cref="Abstractions.IDropTarget"/>, готовый принять данные,
/// или null, если курсор покинул область всех целей.
/// </value>
public Abstractions.IDropTarget? Target { get; }
/// <summary>
/// Получает границы новой цели сброса.
/// </summary>
/// <value>
/// Прямоугольник, определяющий область цели в координатах экрана.
/// Может использоваться для точного позиционирования визуальной обратной связи.
/// </value>
public Rect TargetBounds { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DropTargetChangedEventArgs"/>.
/// </summary>
/// <param name="dragInfo">Информация о перетаскивании.</param>
/// <param name="position">Текущая позиция курсора.</param>
/// <param name="target">Новая цель сброса.</param>
/// <param name="targetBounds">Границы цели сброса.</param>
public DropTargetChangedEventArgs(DragInfo dragInfo, Point position, Abstractions.IDropTarget? target, Rect targetBounds)
: base(dragInfo, position)
{
Target = target;
TargetBounds = targetBounds;
}
}
/// <summary>
/// Предоставляет данные для события ошибки в операции перетаскивания.
/// </summary>
/// <remarks>
/// Возникает при возникновении исключения в любом из компонентов
/// системы перетаскивания. Позволяет централизованно обрабатывать ошибки
/// и предоставлять пользователю информацию о проблемах.
/// </remarks>
public sealed class DragDropErrorEventArgs : EventArgs
{
/// <summary>
/// Получает исключение, вызвавшее ошибку.
/// </summary>
/// <value>
/// Объект <see cref="Exception"/>, содержащий информацию об ошибке.
/// Может быть любого типа, в зависимости от источника ошибки.
/// </value>
public Exception Exception { get; }
/// <summary>
/// Получает название операции, во время которой произошла ошибка.
/// </summary>
/// <value>
/// Строка, идентифицирующая операцию (например, "StartDragAsync",
/// "OnDropAsync", "UpdateDropTargetBounds").
/// </value>
public string Operation { get; }
/// <summary>
/// Получает контекст, в котором произошла ошибка.
/// </summary>
/// <value>
/// Объект, содержащий дополнительную информацию о контексте ошибки,
/// или null, если контекст недоступен.
/// </value>
public object? Context { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragDropErrorEventArgs"/>.
/// </summary>
/// <param name="exception">Исключение, вызвавшее ошибку.</param>
/// <param name="operation">Название операции.</param>
/// <param name="context">Контекст ошибки.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, когда <paramref name="exception"/> или <paramref name="operation"/> равны null.
/// </exception>
public DragDropErrorEventArgs(Exception exception, string operation, object? context = null)
{
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
Operation = operation ?? throw new ArgumentNullException(nameof(operation));
Context = context;
}
}

View File

@@ -1,29 +0,0 @@
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

@@ -1,29 +0,0 @@
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

@@ -1,35 +0,0 @@
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

@@ -8,33 +8,51 @@ public interface IDragDropService : IDisposable
#region Свойства
/// <summary>
/// Активна ли операция перетаскивания.
/// Получает значение, указывающее, активна ли операция перетаскивания.
/// </summary>
/// <value>true, если операция перетаскивания активна; в противном случае — false.</value>
bool IsDragActive { get; }
/// <summary>
/// Информация о текущей операции.
/// Получает информацию о текущей операции перетаскивания.
/// </summary>
/// <value>
/// Объект <see cref="Models.DragInfo"/>, содержащий данные текущей операции,
/// или null, если операция не активна.
/// </value>
Models.DragInfo? CurrentDragInfo { get; }
/// <summary>
/// Текущая цель сброса.
/// Получает текущую цель сброса.
/// </summary>
/// <value>
/// Объект <see cref="Abstractions.IDropTarget"/>, над которым находится курсор,
/// или null, если курсор не над зарегистрированной целью.
/// </value>
Abstractions.IDropTarget? CurrentDropTarget { get; }
/// <summary>
/// Порог начала перетаскивания в пикселях.
/// Получает или задает порог начала перетаскивания в пикселях.
/// </summary>
/// <value>
/// Минимальное расстояние, которое должен пройти курсор мыши, чтобы начать операцию перетаскивания.
/// Значение по умолчанию: <see cref="Constants.DragDropConstants.DefaultDragThreshold"/>.
/// </value>
double DragStartThreshold { get; set; }
/// <summary>
/// Включены ли асинхронные операции.
/// Получает или задает значение, указывающее, включены ли асинхронные операции.
/// </summary>
/// <value>true, если асинхронные операции включены; в противном случае — false.</value>
bool EnableAsyncOperations { get; set; }
/// <summary>
/// Максимальное время ожидания асинхронной операции (мс).
/// Получает или задает максимальное время ожидания асинхронной операции в миллисекундах.
/// </summary>
/// <value>
/// Время ожидания в миллисекундах. Значение 0 или меньше означает отсутствие таймаута.
/// Значение по умолчанию: <see cref="Constants.DragDropConstants.DefaultAsyncTimeout"/>.
/// </value>
int AsyncOperationTimeout { get; set; }
#endregion
@@ -42,32 +60,32 @@ public interface IDragDropService : IDisposable
#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;
@@ -76,23 +94,39 @@ public interface IDragDropService : IDisposable
#region Регистрация целей сброса
/// <summary>
/// Регистрирует цель сброса.
/// Регистрирует цель сброса в системе.
/// </summary>
/// <param name="target">Цель сброса для регистрации.</param>
/// <param name="bounds">Границы области цели в координатах экрана.</param>
/// <param name="priority">Приоритет цели (высшие значения обрабатываются первыми).</param>
/// <param name="group">Имя группы для групповой отмены регистрации.</param>
/// <returns>Уникальный идентификатор зарегистрированной цели.</returns>
/// <exception cref="ArgumentNullException">Выбрасывается, когда <paramref name="target"/> равен null.</exception>
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null);
/// <summary>
/// Обновляет границы цели сброса.
/// </summary>
/// <param name="id">Идентификатор цели сброса.</param>
/// <param name="bounds">Новые границы области цели.</param>
/// <returns>true, если границы успешно обновлены; в противном случае — false.</returns>
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
bool UpdateDropTargetBounds(string id, Geometry.Rect bounds);
/// <summary>
/// Отменяет регистрацию цели сброса.
/// </summary>
/// <param name="id">Идентификатор цели сброса.</param>
/// <returns>true, если цель успешно удалена; в противном случае — false.</returns>
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
bool UnregisterDropTarget(string id);
/// <summary>
/// Отменяет регистрацию всех целей в группе.
/// Отменяет регистрацию всех целей сброса в указанной группе.
/// </summary>
/// <param name="group">Имя группы для удаления.</param>
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
void UnregisterDropTargetsInGroup(string group);
#endregion
@@ -100,23 +134,54 @@ public interface IDragDropService : IDisposable
#region Асинхронные операции
/// <summary>
/// Начинает операцию перетаскивания (асинхронно).
/// Начинает операцию перетаскивания из указанной позиции.
/// </summary>
/// <param name="source">Источник данных для перетаскивания.</param>
/// <param name="startPosition">Начальная позиция операции в координатах экрана.</param>
/// <returns>
/// Задача, представляющая асинхронную операцию. Результат содержит true, если операция успешно начата;
/// в противном случае — false.
/// </returns>
/// <exception cref="ArgumentNullException">Выбрасывается, когда <paramref name="source"/> равен null.</exception>
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
/// <remarks>
/// Этот метод следует вызывать в ответ на событие нажатия кнопки мыши или начала жеста перетаскивания.
/// </remarks>
Task<bool> StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition);
/// <summary>
/// Обновляет позицию перетаскивания (асинхронно).
/// Обновляет позицию текущей операции перетаскивания.
/// </summary>
/// <param name="position">Новая позиция курсора в координатах экрана.</param>
/// <returns>Задача, представляющая асинхронную операцию.</returns>
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
/// <remarks>
/// Этот метод следует вызывать при каждом перемещении мыши во время операции перетаскивания.
/// </remarks>
Task UpdateDragAsync(Geometry.Point position);
/// <summary>
/// Завершает операцию перетаскивания (асинхронно).
/// Завершает текущую операцию перетаскивания в указанной позиции.
/// </summary>
/// <param name="position">Позиция завершения операции в координатах экрана.</param>
/// <returns>
/// Задача, представляющая асинхронную операцию. Результат содержит эффекты, примененные при завершении операции.
/// </returns>
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
/// <remarks>
/// Этот метод следует вызывать при отпускании кнопки мыши или завершении жеста перетаскивания.
/// </remarks>
Task<Enums.DragDropEffects> EndDragAsync(Geometry.Point position);
/// <summary>
/// Отменяет операцию перетаскивания (асинхронно).
/// Отменяет текущую операцию перетаскивания.
/// </summary>
/// <returns>Задача, представляющая асинхронную операцию.</returns>
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
/// <remarks>
/// Этот метод следует вызывать при отмене операции пользователем (например, нажатием клавиши Escape)
/// или при возникновении ошибки.
/// </remarks>
Task CancelDragAsync();
#endregion
@@ -124,27 +189,52 @@ public interface IDragDropService : IDisposable
#region Утилиты
/// <summary>
/// Очищает все зарегистрированные цели.
/// Очищает все зарегистрированные цели сброса.
/// </summary>
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
void ClearAllDropTargets();
/// <summary>
/// Получает статистику использования.
/// Получает статистику использования системы перетаскивания.
/// </summary>
/// <returns>Объект <see cref="DragDropStats"/> со статистикой использования.</returns>
DragDropStats GetStats();
#endregion
}
/// <summary>
/// Статистика использования Drag & Drop.
/// Содержит статистику использования системы перетаскивания.
/// </summary>
public class DragDropStats
{
/// <summary>
/// Получает или задает общее количество операций перетаскивания.
/// </summary>
public int TotalDragOperations { get; set; }
/// <summary>
/// Получает или задает количество успешных сбросов.
/// </summary>
public int SuccessfulDrops { get; set; }
/// <summary>
/// Получает или задает количество отмененных операций.
/// </summary>
public int CancelledOperations { get; set; }
/// <summary>
/// Получает или задает количество ошибок.
/// </summary>
public int ErrorCount { get; set; }
/// <summary>
/// Получает или задает количество зарегистрированных целей сброса.
/// </summary>
public int RegisteredTargets { get; set; }
/// <summary>
/// Получает или задает среднее время операции перетаскивания.
/// </summary>
public TimeSpan AverageOperationTime { get; set; }
}