Убраны синхронные методы
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
namespace Lattice.Core.DragDrop.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Определяет контракт для объектов, которые могут быть источником данных
|
||||
/// в операции перетаскивания с поддержкой асинхронных операций.
|
||||
/// </summary>
|
||||
public interface IAsyncDragSource : IDragSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Определяет, может ли объект начать операцию перетаскивания (асинхронно).
|
||||
/// </summary>
|
||||
Task<(bool CanStart, Models.DragInfo? DragInfo)> CanStartDragAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Начинает операцию перетаскивания (асинхронно).
|
||||
/// </summary>
|
||||
Task<bool> StartDragAsync(Models.DragInfo dragInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при завершении операции перетаскивания (асинхронно).
|
||||
/// </summary>
|
||||
Task DragCompletedAsync(Models.DragInfo dragInfo, Enums.DragDropEffects effects);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при отмене операции перетаскивания (асинхронно).
|
||||
/// </summary>
|
||||
Task DragCancelledAsync(Models.DragInfo dragInfo);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace Lattice.Core.DragDrop.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Определяет контракт для объектов, которые могут принимать сбрасываемые данные
|
||||
/// в операции перетаскивания с поддержкой асинхронных операций.
|
||||
/// </summary>
|
||||
public interface IAsyncDropTarget : IDropTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Определяет, может ли объект принять сбрасываемые данные (асинхронно).
|
||||
/// </summary>
|
||||
Task<bool> CanAcceptDropAsync(Models.DropInfo dropInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается, когда перетаскиваемый объект находится над целью (асинхронно).
|
||||
/// </summary>
|
||||
Task DragOverAsync(Models.DropInfo dropInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается, когда пользователь сбрасывает данные на цель (асинхронно).
|
||||
/// </summary>
|
||||
Task DropAsync(Models.DropInfo dropInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается, когда перетаскиваемый объект покидает область цели (асинхронно).
|
||||
/// </summary>
|
||||
Task DragLeaveAsync();
|
||||
}
|
||||
@@ -13,18 +13,15 @@ public interface IDragSource
|
||||
/// <summary>
|
||||
/// Определяет, может ли объект начать операцию перетаскивания.
|
||||
/// </summary>
|
||||
/// <param name="dragInfo">
|
||||
/// Информация о перетаскивании, которая будет заполнена данными, если операция разрешена.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// true, если объект может начать перетаскивание; в противном случае — false.
|
||||
/// Кортеж, содержащий флаг возможности начала перетаскивания и информацию о перетаскивании.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Этот метод вызывается системой перетаскивания для проверки возможности
|
||||
/// начала операции. Если метод возвращает true, он должен заполнить
|
||||
/// <paramref name="dragInfo"/> необходимыми данными.
|
||||
/// DragInfo необходимыми данными.
|
||||
/// </remarks>
|
||||
bool CanStartDrag(out Models.DragInfo? dragInfo);
|
||||
Task<(bool CanStart, Models.DragInfo? DragInfo)> CanStartDragAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Начинает операцию перетаскивания.
|
||||
@@ -38,7 +35,7 @@ public interface IDragSource
|
||||
/// Реализация должна подготовить данные для перетаскивания и, возможно,
|
||||
/// создать визуальное представление перетаскиваемого объекта.
|
||||
/// </remarks>
|
||||
bool StartDrag(Models.DragInfo dragInfo);
|
||||
Task<bool> StartDragAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при завершении операции перетаскивания.
|
||||
@@ -50,7 +47,7 @@ public interface IDragSource
|
||||
/// (успешного или неуспешного). Реализация может выполнить очистку
|
||||
/// или обновить состояние на основе результата операции.
|
||||
/// </remarks>
|
||||
void DragCompleted(Models.DragInfo dragInfo, Enums.DragDropEffects effects);
|
||||
Task DragCompletedAsync(Models.DragInfo dragInfo, Enums.DragDropEffects effects, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при отмене операции перетаскивания.
|
||||
@@ -60,5 +57,5 @@ public interface IDragSource
|
||||
/// Этот метод вызывается, когда операция перетаскивания была отменена
|
||||
/// пользователем (например, нажатием клавиши Escape).
|
||||
/// </remarks>
|
||||
void DragCancelled(Models.DragInfo dragInfo);
|
||||
Task DragCancelledAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public interface IDropTarget
|
||||
/// Реализация должна проверить, совместимы ли данные с целью, и установить
|
||||
/// предлагаемые эффекты в <paramref name="dropInfo"/>.
|
||||
/// </remarks>
|
||||
bool CanAcceptDrop(Models.DropInfo dropInfo);
|
||||
Task<bool> CanAcceptDropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается, когда перетаскиваемый объект находится над целью.
|
||||
@@ -32,7 +32,7 @@ public interface IDropTarget
|
||||
/// Этот метод вызывается постоянно, пока пользователь перемещает объект над целью.
|
||||
/// Реализация может обновить визуальную обратную связь или изменить предлагаемые эффекты.
|
||||
/// </remarks>
|
||||
void DragOver(Models.DropInfo dropInfo);
|
||||
Task DragOverAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается, когда пользователь сбрасывает данные на цель.
|
||||
@@ -42,7 +42,7 @@ public interface IDropTarget
|
||||
/// Этот метод вызывается, когда пользователь отпускает кнопку мыши над целью.
|
||||
/// Реализация должна обработать принятие данных и выполнить соответствующее действие.
|
||||
/// </remarks>
|
||||
void Drop(Models.DropInfo dropInfo);
|
||||
Task DropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается, когда перетаскиваемый объект покидает область цели.
|
||||
@@ -51,5 +51,5 @@ public interface IDropTarget
|
||||
/// Этот метод вызывается, когда пользователь перемещает объект за пределы цели.
|
||||
/// Реализация должна очистить любую визуальную обратную связь, установленную ранее.
|
||||
/// </remarks>
|
||||
void DragLeave();
|
||||
Task DragLeaveAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
27
Lattice.Core.DragDrop/Constants/DragDropConstants.cs
Normal file
27
Lattice.Core.DragDrop/Constants/DragDropConstants.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace Lattice.Core.DragDrop.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Константы для системы перетаскивания.
|
||||
/// </summary>
|
||||
public static class DragDropConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Порог начала перетаскивания по умолчанию (пикселей).
|
||||
/// </summary>
|
||||
public const double DefaultDragThreshold = 3.0;
|
||||
|
||||
/// <summary>
|
||||
/// Интервал очистки по умолчанию (миллисекунды).
|
||||
/// </summary>
|
||||
public const int DefaultCleanupInterval = 60000;
|
||||
|
||||
/// <summary>
|
||||
/// Таймаут асинхронных операций по умолчанию (миллисекунды).
|
||||
/// </summary>
|
||||
public const int DefaultAsyncTimeout = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Время жизни неиспользуемых целей (минуты).
|
||||
/// </summary>
|
||||
public const int TargetLifetimeMinutes = 10;
|
||||
}
|
||||
@@ -88,7 +88,7 @@ public static class DragDropEffectsExtensions
|
||||
/// </summary>
|
||||
public static DragDropEffects GetEffectFromKeys(bool controlKey, bool shiftKey, bool altKey)
|
||||
{
|
||||
if (controlKey && altKey)
|
||||
if (controlKey && shiftKey)
|
||||
return DragDropEffects.Link;
|
||||
if (controlKey)
|
||||
return DragDropEffects.Copy;
|
||||
|
||||
47
Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs
Normal file
47
Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace Lattice.Core.DragDrop.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Методы расширения для DropInfo.
|
||||
/// </summary>
|
||||
public static class DropInfoExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Проверяет, могут ли данные быть приведены к указанному типу.
|
||||
/// </summary>
|
||||
public static bool CanAccept<T>(this Models.DropInfo dropInfo)
|
||||
where T : class
|
||||
{
|
||||
return dropInfo.Data is T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Пытается получить данные как указанный тип.
|
||||
/// </summary>
|
||||
public static T? GetDataAs<T>(this Models.DropInfo dropInfo)
|
||||
where T : class
|
||||
{
|
||||
return dropInfo.Data as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает данные как указанный тип или выбрасывает исключение.
|
||||
/// </summary>
|
||||
public static T GetRequiredDataAs<T>(this Models.DropInfo dropInfo)
|
||||
where T : class
|
||||
{
|
||||
if (dropInfo.Data is not T data)
|
||||
{
|
||||
throw new InvalidCastException(
|
||||
$"Ожидался тип {typeof(T).Name}, но получен {dropInfo.Data?.GetType().Name ?? "null"}");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, содержится ли позиция в указанных границах.
|
||||
/// </summary>
|
||||
public static bool IsInBounds(this Models.DropInfo dropInfo, Geometry.Rect bounds)
|
||||
{
|
||||
return bounds.Contains(dropInfo.Position);
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,15 @@ public class DragInfo : IDisposable, ICloneable
|
||||
public DragInfo(object data, Enums.DragDropEffects allowedEffects, Point startPosition, object? source = null)
|
||||
{
|
||||
Data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
|
||||
// Проверка допустимых значений перечисления
|
||||
if (!Enum.IsDefined(typeof(Enums.DragDropEffects), allowedEffects))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Недопустимое значение для {nameof(allowedEffects)}: {allowedEffects}",
|
||||
nameof(allowedEffects));
|
||||
}
|
||||
|
||||
AllowedEffects = allowedEffects;
|
||||
StartPosition = startPosition;
|
||||
Source = source;
|
||||
@@ -145,7 +154,10 @@ public class DragInfo : IDisposable, ICloneable
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var clone = new DragInfo(Data, AllowedEffects, newPosition, Source);
|
||||
var clone = new DragInfo(Data, AllowedEffects, newPosition, Source)
|
||||
{
|
||||
_disposed = false,
|
||||
};
|
||||
|
||||
foreach (var kvp in _parameters)
|
||||
{
|
||||
@@ -209,6 +221,14 @@ public class DragInfo : IDisposable, ICloneable
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
foreach (var value in _parameters.Values)
|
||||
{
|
||||
if (value is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_parameters.Clear();
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -130,7 +130,8 @@ public sealed class DragDropService : IDragDropService
|
||||
{
|
||||
// Инициализация таймера очистки (каждые 5 минут)
|
||||
_cleanupTimer = new Timer(CleanupExpiredTargets, null,
|
||||
TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
TimeSpan.FromMinutes(Constants.DragDropConstants.TargetLifetimeMinutes / 2),
|
||||
TimeSpan.FromMinutes(Constants.DragDropConstants.TargetLifetimeMinutes / 2));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -266,7 +267,7 @@ public sealed class DragDropService : IDragDropService
|
||||
Models.DragInfo? dragInfo = null;
|
||||
|
||||
// Проверка возможности начала перетаскивания
|
||||
if (EnableAsyncOperations && source is Abstractions.IAsyncDragSource asyncSource)
|
||||
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource)
|
||||
{
|
||||
var result = await ExecuteWithTimeoutAsync(
|
||||
asyncSource.CanStartDragAsync(),
|
||||
@@ -280,8 +281,11 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!source.CanStartDrag(out dragInfo) || dragInfo == null)
|
||||
var startDragResult = await source.CanStartDragAsync();
|
||||
if (!startDragResult.CanStart || startDragResult.DragInfo == null)
|
||||
return false;
|
||||
|
||||
dragInfo = startDragResult.DragInfo;
|
||||
}
|
||||
|
||||
var updatedDragInfo = dragInfo.CloneWithPosition(startPosition);
|
||||
@@ -289,7 +293,7 @@ public sealed class DragDropService : IDragDropService
|
||||
// Начало перетаскивания
|
||||
bool started;
|
||||
|
||||
if (EnableAsyncOperations && source is Abstractions.IAsyncDragSource asyncSource2)
|
||||
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource2)
|
||||
{
|
||||
started = await ExecuteWithTimeoutAsync(
|
||||
asyncSource2.StartDragAsync(updatedDragInfo),
|
||||
@@ -298,7 +302,7 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
else
|
||||
{
|
||||
started = source.StartDrag(updatedDragInfo);
|
||||
started = await source.StartDragAsync(updatedDragInfo);
|
||||
}
|
||||
|
||||
if (!started)
|
||||
@@ -355,8 +359,8 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
|
||||
var updatedDragInfo = context.DragInfo.CloneWithPosition(position);
|
||||
context.DragInfo.Dispose();
|
||||
context.DragInfo = updatedDragInfo;
|
||||
context.DragInfo.Dispose();
|
||||
|
||||
// Поиск новой цели сброса
|
||||
var newDropTarget = await FindDropTargetAsync(position, updatedDragInfo);
|
||||
@@ -369,7 +373,7 @@ public sealed class DragDropService : IDragDropService
|
||||
await ExecuteTargetOperationAsync(
|
||||
context.CurrentDropTarget,
|
||||
t => t.DragLeaveAsync(),
|
||||
t => t.DragLeave(),
|
||||
t => t.DragLeaveAsync(),
|
||||
"DragLeave");
|
||||
}
|
||||
|
||||
@@ -395,7 +399,7 @@ public sealed class DragDropService : IDragDropService
|
||||
await ExecuteTargetOperationAsync(
|
||||
context.CurrentDropTarget,
|
||||
t => t.DragOverAsync(dropInfo),
|
||||
t => t.DragOver(dropInfo),
|
||||
t => t.DragOverAsync(dropInfo),
|
||||
"DragOver");
|
||||
}
|
||||
|
||||
@@ -443,7 +447,7 @@ public sealed class DragDropService : IDragDropService
|
||||
await ExecuteTargetOperationAsync(
|
||||
context.CurrentDropTarget,
|
||||
t => t.DropAsync(dropInfo),
|
||||
t => t.Drop(dropInfo),
|
||||
t => t.DropAsync(dropInfo),
|
||||
"Drop");
|
||||
|
||||
if (dropInfo.Handled)
|
||||
@@ -457,7 +461,7 @@ public sealed class DragDropService : IDragDropService
|
||||
await ExecuteSourceOperationAsync(
|
||||
context.Source,
|
||||
s => s.DragCompletedAsync(context.DragInfo, effects),
|
||||
s => s.DragCompleted(context.DragInfo, effects),
|
||||
s => s.DragCompletedAsync(context.DragInfo, effects),
|
||||
"DragCompleted",
|
||||
effects);
|
||||
|
||||
@@ -504,7 +508,7 @@ public sealed class DragDropService : IDragDropService
|
||||
await ExecuteSourceOperationAsync(
|
||||
context.Source,
|
||||
s => s.DragCancelledAsync(context.DragInfo),
|
||||
s => s.DragCancelled(context.DragInfo),
|
||||
s => s.DragCancelledAsync(context.DragInfo),
|
||||
"DragCancelled");
|
||||
|
||||
_dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo));
|
||||
@@ -522,30 +526,6 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
#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()
|
||||
@@ -593,39 +573,30 @@ public sealed class DragDropService : IDragDropService
|
||||
_dropTargetsLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
foreach (var info in _dropTargets.Values)
|
||||
{
|
||||
if (!info.Bounds.Contains(position))
|
||||
continue;
|
||||
// Фильтруем цели по границам и сортируем по приоритету
|
||||
var candidates = _dropTargets.Values
|
||||
.Where(info => info.Bounds.Contains(position))
|
||||
.OrderByDescending(info => info.Priority)
|
||||
.ToList();
|
||||
|
||||
foreach (var info in candidates)
|
||||
{
|
||||
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);
|
||||
}
|
||||
bool canAccept = await ExecuteWithTimeoutAsync(
|
||||
info.Target.CanAcceptDropAsync(dropInfo),
|
||||
"CanAcceptDropAsync",
|
||||
info.Target);
|
||||
|
||||
if (canAccept)
|
||||
{
|
||||
info.LastAccessTime = DateTime.UtcNow;
|
||||
|
||||
if (bestTarget == null || info.Priority > bestTarget.Priority)
|
||||
{
|
||||
bestTarget = info;
|
||||
}
|
||||
bestTarget = info;
|
||||
break; // Берем первую подходящую с наивысшим приоритетом
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -639,13 +610,13 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
private async Task ExecuteTargetOperationAsync(
|
||||
Abstractions.IDropTarget target,
|
||||
Func<Abstractions.IAsyncDropTarget, Task> asyncOperation,
|
||||
Func<Abstractions.IDropTarget, Task> asyncOperation,
|
||||
Action<Abstractions.IDropTarget> syncOperation,
|
||||
string operationName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (EnableAsyncOperations && target is Abstractions.IAsyncDropTarget asyncTarget)
|
||||
if (EnableAsyncOperations && target is Abstractions.IDropTarget asyncTarget)
|
||||
{
|
||||
await ExecuteWithTimeoutAsync(
|
||||
asyncOperation(asyncTarget),
|
||||
@@ -666,14 +637,14 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
private async Task ExecuteSourceOperationAsync(
|
||||
Abstractions.IDragSource source,
|
||||
Func<Abstractions.IAsyncDragSource, Task> asyncOperation,
|
||||
Func<Abstractions.IDragSource, Task> asyncOperation,
|
||||
Action<Abstractions.IDragSource> syncOperation,
|
||||
string operationName,
|
||||
Enums.DragDropEffects effects = Enums.DragDropEffects.None)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (EnableAsyncOperations && source is Abstractions.IAsyncDragSource asyncSource)
|
||||
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource)
|
||||
{
|
||||
await ExecuteWithTimeoutAsync(
|
||||
asyncOperation(asyncSource),
|
||||
@@ -745,16 +716,17 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
private void CleanupExpiredTargets(object? state)
|
||||
{
|
||||
var expirationTime = DateTime.UtcNow.AddMinutes(-10); // Цели старше 10 минут
|
||||
var expirationTime = DateTime.UtcNow.AddMinutes(-Constants.DragDropConstants.TargetLifetimeMinutes);
|
||||
|
||||
_dropTargetsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
var idsToRemove = new List<string>();
|
||||
var currentTarget = _currentDragOperation?.CurrentDropTarget;
|
||||
|
||||
foreach (var kvp in _dropTargets)
|
||||
{
|
||||
if (kvp.Value.LastAccessTime < expirationTime)
|
||||
if (kvp.Value.LastAccessTime < expirationTime && !ReferenceEquals(kvp.Value.Target, currentTarget))
|
||||
{
|
||||
idsToRemove.Add(kvp.Key);
|
||||
}
|
||||
@@ -797,6 +769,9 @@ public sealed class DragDropService : IDragDropService
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
var timer = Interlocked.Exchange(ref _cleanupTimer, null);
|
||||
timer?.Dispose();
|
||||
|
||||
_cleanupTimer?.Dispose();
|
||||
_cleanupTimer = null;
|
||||
|
||||
|
||||
@@ -121,30 +121,6 @@ public interface IDragDropService : IDisposable
|
||||
|
||||
#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>
|
||||
|
||||
@@ -12,7 +12,7 @@ public static class AsyncDragDropUtilities
|
||||
/// <summary>
|
||||
/// Создает асинхронную реализацию источника перетаскивания.
|
||||
/// </summary>
|
||||
public static IAsyncDragSource CreateAsyncDragSource(
|
||||
public static IDragSource CreateAsyncDragSource(
|
||||
Func<Task<object>> dataProviderAsync,
|
||||
Func<Task<bool>>? canDragAsync = null,
|
||||
Func<DragInfo, DragDropEffects, Task>? onCompletedAsync = null,
|
||||
@@ -24,7 +24,7 @@ public static class AsyncDragDropUtilities
|
||||
/// <summary>
|
||||
/// Создает асинхронную реализацию цели сброса.
|
||||
/// </summary>
|
||||
public static IAsyncDropTarget CreateAsyncDropTarget(
|
||||
public static IDropTarget CreateAsyncDropTarget(
|
||||
Func<DropInfo, Task<bool>>? canAcceptAsync = null,
|
||||
Func<DropInfo, Task>? onDragOverAsync = null,
|
||||
Func<DropInfo, Task>? onDropAsync = null,
|
||||
@@ -33,28 +33,39 @@ public static class AsyncDragDropUtilities
|
||||
return new AsyncDropTargetWrapper(canAcceptAsync, onDragOverAsync, onDropAsync, onDragLeaveAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает адаптер для преобразования синхронного источника в асинхронный.
|
||||
/// </summary>
|
||||
public static IAsyncDragSource CreateAsyncAdapter(IDragSource syncSource)
|
||||
{
|
||||
return new SyncToAsyncDragSourceAdapter(syncSource);
|
||||
}
|
||||
|
||||
#region Factory Methods
|
||||
|
||||
/// <summary>
|
||||
/// Создает адаптер для преобразования синхронной цели в асинхронную.
|
||||
/// Создает информацию о перетаскивании.
|
||||
/// </summary>
|
||||
public static IAsyncDropTarget CreateAsyncAdapter(IDropTarget syncTarget)
|
||||
public static Models.DragInfo CreateDragInfo(
|
||||
object data,
|
||||
Geometry.Point startPosition,
|
||||
Enums.DragDropEffects allowedEffects = Enums.DragDropEffects.Copy | Enums.DragDropEffects.Move,
|
||||
object? source = null,
|
||||
Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
return new SyncToAsyncDropTargetAdapter(syncTarget);
|
||||
var dragInfo = new Models.DragInfo(data, allowedEffects, startPosition, source);
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
dragInfo.SetParameter(param.Key, param.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return dragInfo;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Обертки-реализации
|
||||
|
||||
/// <summary>
|
||||
/// Обертка для создания асинхронного источника перетаскивания.
|
||||
/// </summary>
|
||||
private sealed class AsyncDragSourceWrapper : IAsyncDragSource
|
||||
private sealed class AsyncDragSourceWrapper : IDragSource
|
||||
{
|
||||
private readonly Func<Task<object>> _dataProviderAsync;
|
||||
private readonly Func<Task<bool>>? _canDragAsync;
|
||||
@@ -73,17 +84,14 @@ public static class AsyncDragDropUtilities
|
||||
_onCancelledAsync = onCancelledAsync;
|
||||
}
|
||||
|
||||
public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
|
||||
public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Проверяем, может ли начаться перетаскивание
|
||||
if (_canDragAsync != null)
|
||||
{
|
||||
var canDrag = await _canDragAsync().ConfigureAwait(false);
|
||||
if (!canDrag)
|
||||
return (false, null);
|
||||
}
|
||||
var canDrag = _canDragAsync != null ? await _canDragAsync().ConfigureAwait(false) : true;
|
||||
if (!canDrag)
|
||||
return (false, null);
|
||||
|
||||
// Получаем данные
|
||||
var data = await _dataProviderAsync().ConfigureAwait(false);
|
||||
@@ -91,7 +99,7 @@ public static class AsyncDragDropUtilities
|
||||
return (false, null);
|
||||
|
||||
// Создаем информацию о перетаскивании
|
||||
var dragInfo = DragDropUtilities.CreateDragInfo(
|
||||
var dragInfo = CreateDragInfo(
|
||||
data,
|
||||
Geometry.Point.Zero,
|
||||
DragDropEffects.Copy | DragDropEffects.Move,
|
||||
@@ -105,13 +113,13 @@ public static class AsyncDragDropUtilities
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> StartDragAsync(DragInfo dragInfo)
|
||||
public Task<bool> StartDragAsync(DragInfo dragInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Базовая реализация всегда разрешает начало
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects)
|
||||
public async Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_onCompletedAsync != null)
|
||||
{
|
||||
@@ -119,46 +127,19 @@ public static class AsyncDragDropUtilities
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DragCancelledAsync(DragInfo dragInfo)
|
||||
public async Task DragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_onCancelledAsync != null)
|
||||
{
|
||||
await _onCancelledAsync(dragInfo).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
#region Синхронная реализация (для IDragSource)
|
||||
|
||||
public bool CanStartDrag(out DragInfo? dragInfo)
|
||||
{
|
||||
// Для синхронного вызова используем Task.Result
|
||||
var result = Task.Run(() => CanStartDragAsync()).GetAwaiter().GetResult();
|
||||
dragInfo = result.DragInfo;
|
||||
return result.CanStart;
|
||||
}
|
||||
|
||||
public bool StartDrag(DragInfo dragInfo)
|
||||
{
|
||||
return Task.Run(() => StartDragAsync(dragInfo)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public void DragCompleted(DragInfo dragInfo, DragDropEffects effects)
|
||||
{
|
||||
Task.Run(() => DragCompletedAsync(dragInfo, effects)).Wait();
|
||||
}
|
||||
|
||||
public void DragCancelled(DragInfo dragInfo)
|
||||
{
|
||||
Task.Run(() => DragCancelledAsync(dragInfo)).Wait();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обертка для создания асинхронной цели сброса.
|
||||
/// </summary>
|
||||
private sealed class AsyncDropTargetWrapper : IAsyncDropTarget
|
||||
private sealed class AsyncDropTargetWrapper : IDropTarget
|
||||
{
|
||||
private readonly Func<DropInfo, Task<bool>>? _canAcceptAsync;
|
||||
private readonly Func<DropInfo, Task>? _onDragOverAsync;
|
||||
@@ -177,7 +158,7 @@ public static class AsyncDragDropUtilities
|
||||
_onDragLeaveAsync = onDragLeaveAsync;
|
||||
}
|
||||
|
||||
public async Task<bool> CanAcceptDropAsync(DropInfo dropInfo)
|
||||
public async Task<bool> CanAcceptDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -193,7 +174,7 @@ public static class AsyncDragDropUtilities
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DragOverAsync(DropInfo dropInfo)
|
||||
public async Task DragOverAsync(DropInfo dropInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -208,7 +189,7 @@ public static class AsyncDragDropUtilities
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DropAsync(DropInfo dropInfo)
|
||||
public async Task DropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -223,7 +204,7 @@ public static class AsyncDragDropUtilities
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DragLeaveAsync()
|
||||
public async Task DragLeaveAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -237,477 +218,8 @@ public static class AsyncDragDropUtilities
|
||||
// Игнорируем ошибки в обработчике
|
||||
}
|
||||
}
|
||||
|
||||
#region Синхронная реализация (для IDropTarget)
|
||||
|
||||
public bool CanAcceptDrop(DropInfo dropInfo)
|
||||
{
|
||||
return Task.Run(() => CanAcceptDropAsync(dropInfo)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public void DragOver(DropInfo dropInfo)
|
||||
{
|
||||
Task.Run(() => DragOverAsync(dropInfo)).Wait();
|
||||
}
|
||||
|
||||
public void Drop(DropInfo dropInfo)
|
||||
{
|
||||
Task.Run(() => DropAsync(dropInfo)).Wait();
|
||||
}
|
||||
|
||||
public void DragLeave()
|
||||
{
|
||||
Task.Run(DragLeaveAsync).Wait();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Адаптер для преобразования синхронного источника в асинхронный.
|
||||
/// </summary>
|
||||
private sealed class SyncToAsyncDragSourceAdapter : IAsyncDragSource
|
||||
{
|
||||
private readonly IDragSource _syncSource;
|
||||
|
||||
public SyncToAsyncDragSourceAdapter(IDragSource syncSource)
|
||||
{
|
||||
_syncSource = syncSource ?? throw new ArgumentNullException(nameof(syncSource));
|
||||
}
|
||||
|
||||
public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var canStart = _syncSource.CanStartDrag(out var dragInfo);
|
||||
return (canStart, dragInfo);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> StartDragAsync(DragInfo dragInfo)
|
||||
{
|
||||
return await Task.Run(() => _syncSource.StartDrag(dragInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects)
|
||||
{
|
||||
await Task.Run(() => _syncSource.DragCompleted(dragInfo, effects)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DragCancelledAsync(DragInfo dragInfo)
|
||||
{
|
||||
await Task.Run(() => _syncSource.DragCancelled(dragInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#region Синхронная реализация (делегируем синхронному источнику)
|
||||
|
||||
public bool CanStartDrag(out DragInfo? dragInfo)
|
||||
{
|
||||
return _syncSource.CanStartDrag(out dragInfo);
|
||||
}
|
||||
|
||||
public bool StartDrag(DragInfo dragInfo)
|
||||
{
|
||||
return _syncSource.StartDrag(dragInfo);
|
||||
}
|
||||
|
||||
public void DragCompleted(DragInfo dragInfo, DragDropEffects effects)
|
||||
{
|
||||
_syncSource.DragCompleted(dragInfo, effects);
|
||||
}
|
||||
|
||||
public void DragCancelled(DragInfo dragInfo)
|
||||
{
|
||||
_syncSource.DragCancelled(dragInfo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Адаптер для преобразования синхронной цели в асинхронную.
|
||||
/// </summary>
|
||||
private sealed class SyncToAsyncDropTargetAdapter : IAsyncDropTarget
|
||||
{
|
||||
private readonly IDropTarget _syncTarget;
|
||||
|
||||
public SyncToAsyncDropTargetAdapter(IDropTarget syncTarget)
|
||||
{
|
||||
_syncTarget = syncTarget ?? throw new ArgumentNullException(nameof(syncTarget));
|
||||
}
|
||||
|
||||
public async Task<bool> CanAcceptDropAsync(DropInfo dropInfo)
|
||||
{
|
||||
return await Task.Run(() => _syncTarget.CanAcceptDrop(dropInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DragOverAsync(DropInfo dropInfo)
|
||||
{
|
||||
await Task.Run(() => _syncTarget.DragOver(dropInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DropAsync(DropInfo dropInfo)
|
||||
{
|
||||
await Task.Run(() => _syncTarget.Drop(dropInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DragLeaveAsync()
|
||||
{
|
||||
await Task.Run(() => _syncTarget.DragLeave()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#region Синхронная реализация (делегируем синхронной цели)
|
||||
|
||||
public bool CanAcceptDrop(DropInfo dropInfo)
|
||||
{
|
||||
return _syncTarget.CanAcceptDrop(dropInfo);
|
||||
}
|
||||
|
||||
public void DragOver(DropInfo dropInfo)
|
||||
{
|
||||
_syncTarget.DragOver(dropInfo);
|
||||
}
|
||||
|
||||
public void Drop(DropInfo dropInfo)
|
||||
{
|
||||
_syncTarget.Drop(dropInfo);
|
||||
}
|
||||
|
||||
public void DragLeave()
|
||||
{
|
||||
_syncTarget.DragLeave();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Утилитарные методы
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет асинхронную операцию с таймаутом.
|
||||
/// </summary>
|
||||
public static async Task<T> ExecuteWithTimeoutAsync<T>(
|
||||
Task<T> task,
|
||||
TimeSpan timeout,
|
||||
T defaultValue = default!)
|
||||
{
|
||||
if (timeout <= TimeSpan.Zero)
|
||||
return await task.ConfigureAwait(false);
|
||||
|
||||
var delayTask = Task.Delay(timeout);
|
||||
var completedTask = await Task.WhenAny(task, delayTask).ConfigureAwait(false);
|
||||
|
||||
if (completedTask == delayTask)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет асинхронную операцию с таймаутом.
|
||||
/// </summary>
|
||||
public static async Task<bool> ExecuteWithTimeoutAsync(
|
||||
Task task,
|
||||
TimeSpan timeout)
|
||||
{
|
||||
if (timeout <= TimeSpan.Zero)
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
var delayTask = Task.Delay(timeout);
|
||||
var completedTask = await Task.WhenAny(task, delayTask).ConfigureAwait(false);
|
||||
|
||||
return completedTask == task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет асинхронную операцию с таймаутом и обработкой ошибок.
|
||||
/// </summary>
|
||||
public static async Task<T?> ExecuteSafeWithTimeoutAsync<T>(
|
||||
Task<T> task,
|
||||
TimeSpan timeout,
|
||||
Func<Exception, T?> errorHandler = null) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
if (timeout <= TimeSpan.Zero)
|
||||
return await task.ConfigureAwait(false);
|
||||
|
||||
var delayTask = Task.Delay(timeout);
|
||||
var completedTask = await Task.WhenAny(task, delayTask).ConfigureAwait(false);
|
||||
|
||||
if (completedTask == delayTask)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return errorHandler?.Invoke(ex) ?? default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает комбинированный источник из синхронного и асинхронного.
|
||||
/// </summary>
|
||||
public static IAsyncDragSource Combine(
|
||||
IDragSource syncSource,
|
||||
IAsyncDragSource asyncSource,
|
||||
bool preferAsync = true)
|
||||
{
|
||||
return new CombinedDragSource(syncSource, asyncSource, preferAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает комбинированную цель из синхронной и асинхронной.
|
||||
/// </summary>
|
||||
public static IAsyncDropTarget Combine(
|
||||
IDropTarget syncTarget,
|
||||
IAsyncDropTarget asyncTarget,
|
||||
bool preferAsync = true)
|
||||
{
|
||||
return new CombinedDropTarget(syncTarget, asyncTarget, preferAsync);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Комбинированные реализации
|
||||
|
||||
/// <summary>
|
||||
/// Комбинированный источник, поддерживающий как синхронный, так и асинхронный API.
|
||||
/// </summary>
|
||||
private sealed class CombinedDragSource : IAsyncDragSource
|
||||
{
|
||||
private readonly IDragSource _syncSource;
|
||||
private readonly IAsyncDragSource _asyncSource;
|
||||
private readonly bool _preferAsync;
|
||||
|
||||
public CombinedDragSource(IDragSource syncSource, IAsyncDragSource asyncSource, bool preferAsync)
|
||||
{
|
||||
_syncSource = syncSource ?? throw new ArgumentNullException(nameof(syncSource));
|
||||
_asyncSource = asyncSource ?? throw new ArgumentNullException(nameof(asyncSource));
|
||||
_preferAsync = preferAsync;
|
||||
}
|
||||
|
||||
public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
|
||||
{
|
||||
if (_preferAsync)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _asyncSource.CanStartDragAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// В случае ошибки пробуем синхронную версию
|
||||
}
|
||||
}
|
||||
|
||||
// Используем синхронную версию в отдельной задаче
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var canStart = _syncSource.CanStartDrag(out var dragInfo);
|
||||
return (canStart, dragInfo);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> StartDragAsync(DragInfo dragInfo)
|
||||
{
|
||||
if (_preferAsync)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _asyncSource.StartDragAsync(dragInfo).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// В случае ошибки пробуем синхронную версию
|
||||
}
|
||||
}
|
||||
|
||||
return await Task.Run(() => _syncSource.StartDrag(dragInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects)
|
||||
{
|
||||
if (_preferAsync)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _asyncSource.DragCompletedAsync(dragInfo, effects).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// В случае ошибки пробуем синхронную версию
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Run(() => _syncSource.DragCompleted(dragInfo, effects)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DragCancelledAsync(DragInfo dragInfo)
|
||||
{
|
||||
if (_preferAsync)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _asyncSource.DragCancelledAsync(dragInfo).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// В случае ошибки пробуем синхронную версию
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Run(() => _syncSource.DragCancelled(dragInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#region Синхронная реализация
|
||||
|
||||
public bool CanStartDrag(out DragInfo? dragInfo)
|
||||
{
|
||||
return _syncSource.CanStartDrag(out dragInfo);
|
||||
}
|
||||
|
||||
public bool StartDrag(DragInfo dragInfo)
|
||||
{
|
||||
return _syncSource.StartDrag(dragInfo);
|
||||
}
|
||||
|
||||
public void DragCompleted(DragInfo dragInfo, DragDropEffects effects)
|
||||
{
|
||||
_syncSource.DragCompleted(dragInfo, effects);
|
||||
}
|
||||
|
||||
public void DragCancelled(DragInfo dragInfo)
|
||||
{
|
||||
_syncSource.DragCancelled(dragInfo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Комбинированная цель, поддерживающая как синхронный, так и асинхронный API.
|
||||
/// </summary>
|
||||
private sealed class CombinedDropTarget : IAsyncDropTarget
|
||||
{
|
||||
private readonly IDropTarget _syncTarget;
|
||||
private readonly IAsyncDropTarget _asyncTarget;
|
||||
private readonly bool _preferAsync;
|
||||
|
||||
public CombinedDropTarget(IDropTarget syncTarget, IAsyncDropTarget asyncTarget, bool preferAsync)
|
||||
{
|
||||
_syncTarget = syncTarget ?? throw new ArgumentNullException(nameof(syncTarget));
|
||||
_asyncTarget = asyncTarget ?? throw new ArgumentNullException(nameof(asyncTarget));
|
||||
_preferAsync = preferAsync;
|
||||
}
|
||||
|
||||
public async Task<bool> CanAcceptDropAsync(DropInfo dropInfo)
|
||||
{
|
||||
if (_preferAsync)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _asyncTarget.CanAcceptDropAsync(dropInfo).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// В случае ошибки пробуем синхронную версию
|
||||
}
|
||||
}
|
||||
|
||||
return await Task.Run(() => _syncTarget.CanAcceptDrop(dropInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DragOverAsync(DropInfo dropInfo)
|
||||
{
|
||||
if (_preferAsync)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _asyncTarget.DragOverAsync(dropInfo).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// В случае ошибки пробуем синхронную версию
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Run(() => _syncTarget.DragOver(dropInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DropAsync(DropInfo dropInfo)
|
||||
{
|
||||
if (_preferAsync)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _asyncTarget.DropAsync(dropInfo).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// В случае ошибки пробуем синхронную версию
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Run(() => _syncTarget.Drop(dropInfo)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DragLeaveAsync()
|
||||
{
|
||||
if (_preferAsync)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _asyncTarget.DragLeaveAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// В случае ошибки пробуем синхронную версию
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Run(() => _syncTarget.DragLeave()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#region Синхронная реализация
|
||||
|
||||
public bool CanAcceptDrop(DropInfo dropInfo)
|
||||
{
|
||||
return _syncTarget.CanAcceptDrop(dropInfo);
|
||||
}
|
||||
|
||||
public void DragOver(DropInfo dropInfo)
|
||||
{
|
||||
_syncTarget.DragOver(dropInfo);
|
||||
}
|
||||
|
||||
public void Drop(DropInfo dropInfo)
|
||||
{
|
||||
_syncTarget.Drop(dropInfo);
|
||||
}
|
||||
|
||||
public void DragLeave()
|
||||
{
|
||||
_syncTarget.DragLeave();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
namespace Lattice.Core.DragDrop.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Утилиты для работы с системой перетаскивания.
|
||||
/// </summary>
|
||||
public static class DragDropUtilities
|
||||
{
|
||||
#region Effect Utilities
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, совместимы ли эффекты источника и цели.
|
||||
/// </summary>
|
||||
public static bool AreEffectsCompatible(Enums.DragDropEffects sourceEffects, Enums.DragDropEffects targetEffects)
|
||||
{
|
||||
if (sourceEffects == Enums.DragDropEffects.None || targetEffects == Enums.DragDropEffects.None)
|
||||
return false;
|
||||
|
||||
return (sourceEffects & targetEffects) != Enums.DragDropEffects.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает наиболее подходящий эффект на основе доступных.
|
||||
/// </summary>
|
||||
public static Enums.DragDropEffects GetBestEffect(Enums.DragDropEffects available, Enums.DragDropEffects preferred)
|
||||
{
|
||||
if ((available & preferred) != Enums.DragDropEffects.None)
|
||||
return available & preferred;
|
||||
|
||||
if ((available & Enums.DragDropEffects.Move) != Enums.DragDropEffects.None)
|
||||
return Enums.DragDropEffects.Move;
|
||||
|
||||
if ((available & Enums.DragDropEffects.Copy) != Enums.DragDropEffects.None)
|
||||
return Enums.DragDropEffects.Copy;
|
||||
|
||||
if ((available & Enums.DragDropEffects.Link) != Enums.DragDropEffects.None)
|
||||
return Enums.DragDropEffects.Link;
|
||||
|
||||
return Enums.DragDropEffects.None;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Geometry Utilities
|
||||
|
||||
/// <summary>
|
||||
/// Вычисляет расстояние между двумя точками.
|
||||
/// </summary>
|
||||
public static 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, превысило ли перемещение пороговое значение.
|
||||
/// </summary>
|
||||
public static bool HasExceededDragThreshold(Geometry.Point startPoint, Geometry.Point currentPoint, double threshold)
|
||||
{
|
||||
var distance = CalculateDistance(startPoint, currentPoint);
|
||||
return distance >= threshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Определяет позицию сброса относительно прямоугольника.
|
||||
/// </summary>
|
||||
public static Enums.DropPosition GetDropPosition(Geometry.Point point, Geometry.Rect bounds, double edgeThreshold = 20.0)
|
||||
{
|
||||
if (!bounds.Contains(new Geometry.Point(point.X, point.Y)))
|
||||
return Enums.DropPosition.Inside;
|
||||
|
||||
var relativeX = (point.X - bounds.X) / bounds.Width;
|
||||
var relativeY = (point.Y - bounds.Y) / bounds.Height;
|
||||
|
||||
if (relativeX < edgeThreshold / bounds.Width)
|
||||
return Enums.DropPosition.Left;
|
||||
if (relativeX > 1 - edgeThreshold / bounds.Width)
|
||||
return Enums.DropPosition.Right;
|
||||
if (relativeY < edgeThreshold / bounds.Height)
|
||||
return Enums.DropPosition.Top;
|
||||
if (relativeY > 1 - edgeThreshold / bounds.Height)
|
||||
return Enums.DropPosition.Bottom;
|
||||
|
||||
return Enums.DropPosition.Center;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Factory Methods
|
||||
|
||||
/// <summary>
|
||||
/// Создает информацию о перетаскивании.
|
||||
/// </summary>
|
||||
public static Models.DragInfo CreateDragInfo(
|
||||
object data,
|
||||
Geometry.Point startPosition,
|
||||
Enums.DragDropEffects allowedEffects = Enums.DragDropEffects.Copy | Enums.DragDropEffects.Move,
|
||||
object? source = null,
|
||||
Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
var dragInfo = new Models.DragInfo(data, allowedEffects, startPosition, source);
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
dragInfo.SetParameter(param.Key, param.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return dragInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает простую реализацию источника перетаскивания.
|
||||
/// </summary>
|
||||
public static Abstractions.IDragSource CreateSimpleDragSource(
|
||||
Func<object> dataProvider,
|
||||
Func<bool>? canDrag = null,
|
||||
Action<Models.DragInfo, Enums.DragDropEffects>? onCompleted = null,
|
||||
Action<Models.DragInfo>? onCancelled = null)
|
||||
{
|
||||
return new SimpleDragSource(dataProvider, canDrag, onCompleted, onCancelled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает простую реализацию цели сброса.
|
||||
/// </summary>
|
||||
public static Abstractions.IDropTarget CreateSimpleDropTarget(
|
||||
Func<Models.DropInfo, bool>? canAccept = null,
|
||||
Action<Models.DropInfo>? onDragOver = null,
|
||||
Action<Models.DropInfo>? onDrop = null,
|
||||
Action? onDragLeave = null)
|
||||
{
|
||||
return new SimpleDropTarget(canAccept, onDragOver, onDrop, onDragLeave);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Извлекает данные из элемента с поддержкой различных паттернов.
|
||||
/// </summary>
|
||||
public static object? ExtractData(object? element)
|
||||
{
|
||||
if (element == null)
|
||||
return null;
|
||||
|
||||
// Проверяем, реализует ли элемент специальный интерфейс
|
||||
if (element is Abstractions.IDragSource dragSource)
|
||||
{
|
||||
if (dragSource.CanStartDrag(out var dragInfo) && dragInfo != null)
|
||||
return dragInfo.Data;
|
||||
}
|
||||
|
||||
// В реальной реализации здесь будет рефлексия для проверки свойств
|
||||
// DataContext, Content и т.д.
|
||||
|
||||
// Возвращаем сам элемент как данные
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, совместимы ли данные с указанными типами.
|
||||
/// </summary>
|
||||
public static bool IsDataCompatible(object? data, IEnumerable<Type>? acceptedTypes)
|
||||
{
|
||||
if (data == null || acceptedTypes == null)
|
||||
return false;
|
||||
|
||||
var dataType = data.GetType();
|
||||
|
||||
foreach (var acceptedType in acceptedTypes)
|
||||
{
|
||||
if (acceptedType.IsAssignableFrom(dataType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Classes
|
||||
|
||||
private sealed class SimpleDragSource : Abstractions.IDragSource
|
||||
{
|
||||
private readonly Func<object> _dataProvider;
|
||||
private readonly Func<bool>? _canDrag;
|
||||
private readonly Action<Models.DragInfo, Enums.DragDropEffects>? _onCompleted;
|
||||
private readonly Action<Models.DragInfo>? _onCancelled;
|
||||
|
||||
public SimpleDragSource(
|
||||
Func<object> dataProvider,
|
||||
Func<bool>? canDrag = null,
|
||||
Action<Models.DragInfo, Enums.DragDropEffects>? onCompleted = null,
|
||||
Action<Models.DragInfo>? onCancelled = null)
|
||||
{
|
||||
_dataProvider = dataProvider ?? throw new ArgumentNullException(nameof(dataProvider));
|
||||
_canDrag = canDrag;
|
||||
_onCompleted = onCompleted;
|
||||
_onCancelled = onCancelled;
|
||||
}
|
||||
|
||||
public bool CanStartDrag(out Models.DragInfo? dragInfo)
|
||||
{
|
||||
dragInfo = null;
|
||||
|
||||
if (_canDrag?.Invoke() == false)
|
||||
return false;
|
||||
|
||||
var data = _dataProvider();
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
dragInfo = CreateDragInfo(data, Geometry.Point.Zero, Enums.DragDropEffects.Copy | Enums.DragDropEffects.Move, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool StartDrag(Models.DragInfo dragInfo) => true;
|
||||
|
||||
public void DragCompleted(Models.DragInfo dragInfo, Enums.DragDropEffects effects)
|
||||
{
|
||||
_onCompleted?.Invoke(dragInfo, effects);
|
||||
}
|
||||
|
||||
public void DragCancelled(Models.DragInfo dragInfo)
|
||||
{
|
||||
_onCancelled?.Invoke(dragInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SimpleDropTarget : Abstractions.IDropTarget
|
||||
{
|
||||
private readonly Func<Models.DropInfo, bool>? _canAccept;
|
||||
private readonly Action<Models.DropInfo>? _onDragOver;
|
||||
private readonly Action<Models.DropInfo>? _onDrop;
|
||||
private readonly Action? _onDragLeave;
|
||||
|
||||
public SimpleDropTarget(
|
||||
Func<Models.DropInfo, bool>? canAccept = null,
|
||||
Action<Models.DropInfo>? onDragOver = null,
|
||||
Action<Models.DropInfo>? onDrop = null,
|
||||
Action? onDragLeave = null)
|
||||
{
|
||||
_canAccept = canAccept;
|
||||
_onDragOver = onDragOver;
|
||||
_onDrop = onDrop;
|
||||
_onDragLeave = onDragLeave;
|
||||
}
|
||||
|
||||
public bool CanAcceptDrop(Models.DropInfo dropInfo)
|
||||
{
|
||||
return _canAccept?.Invoke(dropInfo) ?? true;
|
||||
}
|
||||
|
||||
public void DragOver(Models.DropInfo dropInfo)
|
||||
{
|
||||
_onDragOver?.Invoke(dropInfo);
|
||||
}
|
||||
|
||||
public void Drop(Models.DropInfo dropInfo)
|
||||
{
|
||||
_onDrop?.Invoke(dropInfo);
|
||||
}
|
||||
|
||||
public void DragLeave()
|
||||
{
|
||||
_onDragLeave?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user