Files
Lattice/Lattice.Core.DragDrop/Utilities/AsyncDragDropUtilities.cs
2026-01-18 16:33:35 +03:00

713 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Lattice.Core.DragDrop.Abstractions;
using Lattice.Core.DragDrop.Enums;
using Lattice.Core.DragDrop.Models;
namespace Lattice.Core.DragDrop.Utilities;
/// <summary>
/// Предоставляет утилитарные методы и фабричные методы для работы с системой перетаскивания с поддержкой async.
/// </summary>
public static class AsyncDragDropUtilities
{
/// <summary>
/// Создает асинхронную реализацию источника перетаскивания.
/// </summary>
public static IAsyncDragSource CreateAsyncDragSource(
Func<Task<object>> dataProviderAsync,
Func<Task<bool>>? canDragAsync = null,
Func<DragInfo, DragDropEffects, Task>? onCompletedAsync = null,
Func<DragInfo, Task>? onCancelledAsync = null)
{
return new AsyncDragSourceWrapper(dataProviderAsync, canDragAsync, onCompletedAsync, onCancelledAsync);
}
/// <summary>
/// Создает асинхронную реализацию цели сброса.
/// </summary>
public static IAsyncDropTarget CreateAsyncDropTarget(
Func<DropInfo, Task<bool>>? canAcceptAsync = null,
Func<DropInfo, Task>? onDragOverAsync = null,
Func<DropInfo, Task>? onDropAsync = null,
Func<Task>? onDragLeaveAsync = null)
{
return new AsyncDropTargetWrapper(canAcceptAsync, onDragOverAsync, onDropAsync, onDragLeaveAsync);
}
/// <summary>
/// Создает адаптер для преобразования синхронного источника в асинхронный.
/// </summary>
public static IAsyncDragSource CreateAsyncAdapter(IDragSource syncSource)
{
return new SyncToAsyncDragSourceAdapter(syncSource);
}
/// <summary>
/// Создает адаптер для преобразования синхронной цели в асинхронную.
/// </summary>
public static IAsyncDropTarget CreateAsyncAdapter(IDropTarget syncTarget)
{
return new SyncToAsyncDropTargetAdapter(syncTarget);
}
#region Обертки-реализации
/// <summary>
/// Обертка для создания асинхронного источника перетаскивания.
/// </summary>
private sealed class AsyncDragSourceWrapper : IAsyncDragSource
{
private readonly Func<Task<object>> _dataProviderAsync;
private readonly Func<Task<bool>>? _canDragAsync;
private readonly Func<DragInfo, DragDropEffects, Task>? _onCompletedAsync;
private readonly Func<DragInfo, Task>? _onCancelledAsync;
public AsyncDragSourceWrapper(
Func<Task<object>> dataProviderAsync,
Func<Task<bool>>? canDragAsync = null,
Func<DragInfo, DragDropEffects, Task>? onCompletedAsync = null,
Func<DragInfo, Task>? onCancelledAsync = null)
{
_dataProviderAsync = dataProviderAsync ?? throw new ArgumentNullException(nameof(dataProviderAsync));
_canDragAsync = canDragAsync;
_onCompletedAsync = onCompletedAsync;
_onCancelledAsync = onCancelledAsync;
}
public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
{
try
{
// Проверяем, может ли начаться перетаскивание
if (_canDragAsync != null)
{
var canDrag = await _canDragAsync().ConfigureAwait(false);
if (!canDrag)
return (false, null);
}
// Получаем данные
var data = await _dataProviderAsync().ConfigureAwait(false);
if (data == null)
return (false, null);
// Создаем информацию о перетаскивании
var dragInfo = DragDropUtilities.CreateDragInfo(
data,
Geometry.Point.Zero,
DragDropEffects.Copy | DragDropEffects.Move,
this);
return (true, dragInfo);
}
catch
{
return (false, null);
}
}
public Task<bool> StartDragAsync(DragInfo dragInfo)
{
// Базовая реализация всегда разрешает начало
return Task.FromResult(true);
}
public async Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects)
{
if (_onCompletedAsync != null)
{
await _onCompletedAsync(dragInfo, effects).ConfigureAwait(false);
}
}
public async Task DragCancelledAsync(DragInfo dragInfo)
{
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 readonly Func<DropInfo, Task<bool>>? _canAcceptAsync;
private readonly Func<DropInfo, Task>? _onDragOverAsync;
private readonly Func<DropInfo, Task>? _onDropAsync;
private readonly Func<Task>? _onDragLeaveAsync;
public AsyncDropTargetWrapper(
Func<DropInfo, Task<bool>>? canAcceptAsync = null,
Func<DropInfo, Task>? onDragOverAsync = null,
Func<DropInfo, Task>? onDropAsync = null,
Func<Task>? onDragLeaveAsync = null)
{
_canAcceptAsync = canAcceptAsync;
_onDragOverAsync = onDragOverAsync;
_onDropAsync = onDropAsync;
_onDragLeaveAsync = onDragLeaveAsync;
}
public async Task<bool> CanAcceptDropAsync(DropInfo dropInfo)
{
try
{
if (_canAcceptAsync != null)
{
return await _canAcceptAsync(dropInfo).ConfigureAwait(false);
}
return true; // По умолчанию принимаем все
}
catch
{
return false; // При ошибке не принимаем
}
}
public async Task DragOverAsync(DropInfo dropInfo)
{
try
{
if (_onDragOverAsync != null)
{
await _onDragOverAsync(dropInfo).ConfigureAwait(false);
}
}
catch
{
// Игнорируем ошибки в обработчике
}
}
public async Task DropAsync(DropInfo dropInfo)
{
try
{
if (_onDropAsync != null)
{
await _onDropAsync(dropInfo).ConfigureAwait(false);
}
}
catch
{
// Игнорируем ошибки в обработчике
}
}
public async Task DragLeaveAsync()
{
try
{
if (_onDragLeaveAsync != null)
{
await _onDragLeaveAsync().ConfigureAwait(false);
}
}
catch
{
// Игнорируем ошибки в обработчике
}
}
#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
}