From a6ee6fcb36789a38f50cc5f249307f977b452815 Mon Sep 17 00:00:00 2001 From: FrigaT Date: Sun, 25 Jan 2026 01:52:03 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BD=D1=8B=20=D1=81?= =?UTF-8?q?=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Abstractions/IAsyncDragSource.cs | 28 - .../Abstractions/IAsyncDropTarget.cs | 28 - .../Abstractions/IDragSource.cs | 15 +- .../Abstractions/IDropTarget.cs | 8 +- .../Constants/DragDropConstants.cs | 27 + .../Enums/DragDropEffects.cs | 2 +- .../Extensions/DropInfoExtensions.cs | 47 ++ Lattice.Core.DragDrop/Models/DragInfo.cs | 22 +- .../Services/DragDropService.cs | 101 +-- .../Services/IDragDropService.cs | 24 - .../Utilities/AsyncDragDropUtilities.cs | 562 +-------------- .../Utilities/DragDropUtilities.cs | 275 ------- .../Behaviors/DragSource.cs | 49 ++ .../Behaviors/WinUIDragSourceBehavior.cs | 507 +++++-------- .../Behaviors/WinUIDropTargetBehavior.cs | 681 ++++++++---------- .../Extensions/DragDropExtensions.cs | 224 ++---- .../Extensions/FrameworkElementExtensions.cs | 26 + .../WinUIDragDropIntegrationService.cs | 443 +++++------- .../Services/WinUIDragDropHost.cs | 72 ++ .../Services/WinUIDragVisualProvider.cs | 47 +- .../Behaviors/DragSourceBehaviorBase.cs | 31 +- .../Behaviors/DropTargetBehaviorBase.cs | 26 +- 22 files changed, 1108 insertions(+), 2137 deletions(-) delete mode 100644 Lattice.Core.DragDrop/Abstractions/IAsyncDragSource.cs delete mode 100644 Lattice.Core.DragDrop/Abstractions/IAsyncDropTarget.cs create mode 100644 Lattice.Core.DragDrop/Constants/DragDropConstants.cs create mode 100644 Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs delete mode 100644 Lattice.Core.DragDrop/Utilities/DragDropUtilities.cs create mode 100644 Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs create mode 100644 Lattice.UI.DragDrop.WinUI/Extensions/FrameworkElementExtensions.cs create mode 100644 Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropHost.cs diff --git a/Lattice.Core.DragDrop/Abstractions/IAsyncDragSource.cs b/Lattice.Core.DragDrop/Abstractions/IAsyncDragSource.cs deleted file mode 100644 index 834b788..0000000 --- a/Lattice.Core.DragDrop/Abstractions/IAsyncDragSource.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Lattice.Core.DragDrop.Abstractions; - -/// -/// Определяет контракт для объектов, которые могут быть источником данных -/// в операции перетаскивания с поддержкой асинхронных операций. -/// -public interface IAsyncDragSource : IDragSource -{ - /// - /// Определяет, может ли объект начать операцию перетаскивания (асинхронно). - /// - Task<(bool CanStart, Models.DragInfo? DragInfo)> CanStartDragAsync(); - - /// - /// Начинает операцию перетаскивания (асинхронно). - /// - Task StartDragAsync(Models.DragInfo dragInfo); - - /// - /// Вызывается при завершении операции перетаскивания (асинхронно). - /// - Task DragCompletedAsync(Models.DragInfo dragInfo, Enums.DragDropEffects effects); - - /// - /// Вызывается при отмене операции перетаскивания (асинхронно). - /// - Task DragCancelledAsync(Models.DragInfo dragInfo); -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Abstractions/IAsyncDropTarget.cs b/Lattice.Core.DragDrop/Abstractions/IAsyncDropTarget.cs deleted file mode 100644 index 99aa78c..0000000 --- a/Lattice.Core.DragDrop/Abstractions/IAsyncDropTarget.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Lattice.Core.DragDrop.Abstractions; - -/// -/// Определяет контракт для объектов, которые могут принимать сбрасываемые данные -/// в операции перетаскивания с поддержкой асинхронных операций. -/// -public interface IAsyncDropTarget : IDropTarget -{ - /// - /// Определяет, может ли объект принять сбрасываемые данные (асинхронно). - /// - Task CanAcceptDropAsync(Models.DropInfo dropInfo); - - /// - /// Вызывается, когда перетаскиваемый объект находится над целью (асинхронно). - /// - Task DragOverAsync(Models.DropInfo dropInfo); - - /// - /// Вызывается, когда пользователь сбрасывает данные на цель (асинхронно). - /// - Task DropAsync(Models.DropInfo dropInfo); - - /// - /// Вызывается, когда перетаскиваемый объект покидает область цели (асинхронно). - /// - Task DragLeaveAsync(); -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Abstractions/IDragSource.cs b/Lattice.Core.DragDrop/Abstractions/IDragSource.cs index b5fea3b..65822e9 100644 --- a/Lattice.Core.DragDrop/Abstractions/IDragSource.cs +++ b/Lattice.Core.DragDrop/Abstractions/IDragSource.cs @@ -13,18 +13,15 @@ public interface IDragSource /// /// Определяет, может ли объект начать операцию перетаскивания. /// - /// - /// Информация о перетаскивании, которая будет заполнена данными, если операция разрешена. - /// /// - /// true, если объект может начать перетаскивание; в противном случае — false. + /// Кортеж, содержащий флаг возможности начала перетаскивания и информацию о перетаскивании. /// /// /// Этот метод вызывается системой перетаскивания для проверки возможности /// начала операции. Если метод возвращает true, он должен заполнить - /// необходимыми данными. + /// DragInfo необходимыми данными. /// - bool CanStartDrag(out Models.DragInfo? dragInfo); + Task<(bool CanStart, Models.DragInfo? DragInfo)> CanStartDragAsync(CancellationToken cancellationToken = default); /// /// Начинает операцию перетаскивания. @@ -38,7 +35,7 @@ public interface IDragSource /// Реализация должна подготовить данные для перетаскивания и, возможно, /// создать визуальное представление перетаскиваемого объекта. /// - bool StartDrag(Models.DragInfo dragInfo); + Task StartDragAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default); /// /// Вызывается при завершении операции перетаскивания. @@ -50,7 +47,7 @@ public interface IDragSource /// (успешного или неуспешного). Реализация может выполнить очистку /// или обновить состояние на основе результата операции. /// - void DragCompleted(Models.DragInfo dragInfo, Enums.DragDropEffects effects); + Task DragCompletedAsync(Models.DragInfo dragInfo, Enums.DragDropEffects effects, CancellationToken cancellationToken = default); /// /// Вызывается при отмене операции перетаскивания. @@ -60,5 +57,5 @@ public interface IDragSource /// Этот метод вызывается, когда операция перетаскивания была отменена /// пользователем (например, нажатием клавиши Escape). /// - void DragCancelled(Models.DragInfo dragInfo); + Task DragCancelledAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Abstractions/IDropTarget.cs b/Lattice.Core.DragDrop/Abstractions/IDropTarget.cs index 736666e..b3eb5f5 100644 --- a/Lattice.Core.DragDrop/Abstractions/IDropTarget.cs +++ b/Lattice.Core.DragDrop/Abstractions/IDropTarget.cs @@ -22,7 +22,7 @@ public interface IDropTarget /// Реализация должна проверить, совместимы ли данные с целью, и установить /// предлагаемые эффекты в . /// - bool CanAcceptDrop(Models.DropInfo dropInfo); + Task CanAcceptDropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); /// /// Вызывается, когда перетаскиваемый объект находится над целью. @@ -32,7 +32,7 @@ public interface IDropTarget /// Этот метод вызывается постоянно, пока пользователь перемещает объект над целью. /// Реализация может обновить визуальную обратную связь или изменить предлагаемые эффекты. /// - void DragOver(Models.DropInfo dropInfo); + Task DragOverAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); /// /// Вызывается, когда пользователь сбрасывает данные на цель. @@ -42,7 +42,7 @@ public interface IDropTarget /// Этот метод вызывается, когда пользователь отпускает кнопку мыши над целью. /// Реализация должна обработать принятие данных и выполнить соответствующее действие. /// - void Drop(Models.DropInfo dropInfo); + Task DropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); /// /// Вызывается, когда перетаскиваемый объект покидает область цели. @@ -51,5 +51,5 @@ public interface IDropTarget /// Этот метод вызывается, когда пользователь перемещает объект за пределы цели. /// Реализация должна очистить любую визуальную обратную связь, установленную ранее. /// - void DragLeave(); + Task DragLeaveAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Constants/DragDropConstants.cs b/Lattice.Core.DragDrop/Constants/DragDropConstants.cs new file mode 100644 index 0000000..6bb931c --- /dev/null +++ b/Lattice.Core.DragDrop/Constants/DragDropConstants.cs @@ -0,0 +1,27 @@ +namespace Lattice.Core.DragDrop.Constants; + +/// +/// Константы для системы перетаскивания. +/// +public static class DragDropConstants +{ + /// + /// Порог начала перетаскивания по умолчанию (пикселей). + /// + public const double DefaultDragThreshold = 3.0; + + /// + /// Интервал очистки по умолчанию (миллисекунды). + /// + public const int DefaultCleanupInterval = 60000; + + /// + /// Таймаут асинхронных операций по умолчанию (миллисекунды). + /// + public const int DefaultAsyncTimeout = 5000; + + /// + /// Время жизни неиспользуемых целей (минуты). + /// + public const int TargetLifetimeMinutes = 10; +} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Enums/DragDropEffects.cs b/Lattice.Core.DragDrop/Enums/DragDropEffects.cs index 808b92f..9fcc970 100644 --- a/Lattice.Core.DragDrop/Enums/DragDropEffects.cs +++ b/Lattice.Core.DragDrop/Enums/DragDropEffects.cs @@ -88,7 +88,7 @@ public static class DragDropEffectsExtensions /// public static DragDropEffects GetEffectFromKeys(bool controlKey, bool shiftKey, bool altKey) { - if (controlKey && altKey) + if (controlKey && shiftKey) return DragDropEffects.Link; if (controlKey) return DragDropEffects.Copy; diff --git a/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs b/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs new file mode 100644 index 0000000..f362db6 --- /dev/null +++ b/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs @@ -0,0 +1,47 @@ +namespace Lattice.Core.DragDrop.Extensions; + +/// +/// Методы расширения для DropInfo. +/// +public static class DropInfoExtensions +{ + /// + /// Проверяет, могут ли данные быть приведены к указанному типу. + /// + public static bool CanAccept(this Models.DropInfo dropInfo) + where T : class + { + return dropInfo.Data is T; + } + + /// + /// Пытается получить данные как указанный тип. + /// + public static T? GetDataAs(this Models.DropInfo dropInfo) + where T : class + { + return dropInfo.Data as T; + } + + /// + /// Получает данные как указанный тип или выбрасывает исключение. + /// + public static T GetRequiredDataAs(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; + } + + /// + /// Проверяет, содержится ли позиция в указанных границах. + /// + public static bool IsInBounds(this Models.DropInfo dropInfo, Geometry.Rect bounds) + { + return bounds.Contains(dropInfo.Position); + } +} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Models/DragInfo.cs b/Lattice.Core.DragDrop/Models/DragInfo.cs index 6f2d85f..8547d1e 100644 --- a/Lattice.Core.DragDrop/Models/DragInfo.cs +++ b/Lattice.Core.DragDrop/Models/DragInfo.cs @@ -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); diff --git a/Lattice.Core.DragDrop/Services/DragDropService.cs b/Lattice.Core.DragDrop/Services/DragDropService.cs index 4e21943..f0a9b99 100644 --- a/Lattice.Core.DragDrop/Services/DragDropService.cs +++ b/Lattice.Core.DragDrop/Services/DragDropService.cs @@ -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 asyncOperation, + Func asyncOperation, Action 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 asyncOperation, + Func asyncOperation, Action 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(); + 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; diff --git a/Lattice.Core.DragDrop/Services/IDragDropService.cs b/Lattice.Core.DragDrop/Services/IDragDropService.cs index 9d01977..e8220d9 100644 --- a/Lattice.Core.DragDrop/Services/IDragDropService.cs +++ b/Lattice.Core.DragDrop/Services/IDragDropService.cs @@ -121,30 +121,6 @@ public interface IDragDropService : IDisposable #endregion - #region Синхронные операции (для обратной совместимости) - - /// - /// Начинает операцию перетаскивания (синхронно). - /// - bool StartDrag(Abstractions.IDragSource source, Geometry.Point startPosition); - - /// - /// Обновляет позицию перетаскивания (синхронно). - /// - void UpdateDrag(Geometry.Point position); - - /// - /// Завершает операцию перетаскивания (синхронно). - /// - Enums.DragDropEffects EndDrag(Geometry.Point position); - - /// - /// Отменяет операцию перетаскивания (синхронно). - /// - void CancelDrag(); - - #endregion - #region Утилиты /// diff --git a/Lattice.Core.DragDrop/Utilities/AsyncDragDropUtilities.cs b/Lattice.Core.DragDrop/Utilities/AsyncDragDropUtilities.cs index a0f605f..0e31e45 100644 --- a/Lattice.Core.DragDrop/Utilities/AsyncDragDropUtilities.cs +++ b/Lattice.Core.DragDrop/Utilities/AsyncDragDropUtilities.cs @@ -12,7 +12,7 @@ public static class AsyncDragDropUtilities /// /// Создает асинхронную реализацию источника перетаскивания. /// - public static IAsyncDragSource CreateAsyncDragSource( + public static IDragSource CreateAsyncDragSource( Func> dataProviderAsync, Func>? canDragAsync = null, Func? onCompletedAsync = null, @@ -24,7 +24,7 @@ public static class AsyncDragDropUtilities /// /// Создает асинхронную реализацию цели сброса. /// - public static IAsyncDropTarget CreateAsyncDropTarget( + public static IDropTarget CreateAsyncDropTarget( Func>? canAcceptAsync = null, Func? onDragOverAsync = null, Func? onDropAsync = null, @@ -33,28 +33,39 @@ public static class AsyncDragDropUtilities return new AsyncDropTargetWrapper(canAcceptAsync, onDragOverAsync, onDropAsync, onDragLeaveAsync); } - /// - /// Создает адаптер для преобразования синхронного источника в асинхронный. - /// - public static IAsyncDragSource CreateAsyncAdapter(IDragSource syncSource) - { - return new SyncToAsyncDragSourceAdapter(syncSource); - } + + #region Factory Methods /// - /// Создает адаптер для преобразования синхронной цели в асинхронную. + /// Создает информацию о перетаскивании. /// - 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? 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 Обертки-реализации /// /// Обертка для создания асинхронного источника перетаскивания. /// - private sealed class AsyncDragSourceWrapper : IAsyncDragSource + private sealed class AsyncDragSourceWrapper : IDragSource { private readonly Func> _dataProviderAsync; private readonly Func>? _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 StartDragAsync(DragInfo dragInfo) + public Task 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 } /// /// Обертка для создания асинхронной цели сброса. /// - private sealed class AsyncDropTargetWrapper : IAsyncDropTarget + private sealed class AsyncDropTargetWrapper : IDropTarget { private readonly Func>? _canAcceptAsync; private readonly Func? _onDragOverAsync; @@ -177,7 +158,7 @@ public static class AsyncDragDropUtilities _onDragLeaveAsync = onDragLeaveAsync; } - public async Task CanAcceptDropAsync(DropInfo dropInfo) + public async Task 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 - } - - /// - /// Адаптер для преобразования синхронного источника в асинхронный. - /// - 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 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 - } - - /// - /// Адаптер для преобразования синхронной цели в асинхронную. - /// - private sealed class SyncToAsyncDropTargetAdapter : IAsyncDropTarget - { - private readonly IDropTarget _syncTarget; - - public SyncToAsyncDropTargetAdapter(IDropTarget syncTarget) - { - _syncTarget = syncTarget ?? throw new ArgumentNullException(nameof(syncTarget)); - } - - public async Task 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 Утилитарные методы - - /// - /// Выполняет асинхронную операцию с таймаутом. - /// - public static async Task ExecuteWithTimeoutAsync( - Task 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); - } - - /// - /// Выполняет асинхронную операцию с таймаутом. - /// - public static async Task 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; - } - - /// - /// Выполняет асинхронную операцию с таймаутом и обработкой ошибок. - /// - public static async Task ExecuteSafeWithTimeoutAsync( - Task task, - TimeSpan timeout, - Func 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; - } - } - - /// - /// Создает комбинированный источник из синхронного и асинхронного. - /// - public static IAsyncDragSource Combine( - IDragSource syncSource, - IAsyncDragSource asyncSource, - bool preferAsync = true) - { - return new CombinedDragSource(syncSource, asyncSource, preferAsync); - } - - /// - /// Создает комбинированную цель из синхронной и асинхронной. - /// - public static IAsyncDropTarget Combine( - IDropTarget syncTarget, - IAsyncDropTarget asyncTarget, - bool preferAsync = true) - { - return new CombinedDropTarget(syncTarget, asyncTarget, preferAsync); - } - - #endregion - - #region Комбинированные реализации - - /// - /// Комбинированный источник, поддерживающий как синхронный, так и асинхронный API. - /// - 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 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 - } - - /// - /// Комбинированная цель, поддерживающая как синхронный, так и асинхронный API. - /// - 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 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 } \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Utilities/DragDropUtilities.cs b/Lattice.Core.DragDrop/Utilities/DragDropUtilities.cs deleted file mode 100644 index c1486f7..0000000 --- a/Lattice.Core.DragDrop/Utilities/DragDropUtilities.cs +++ /dev/null @@ -1,275 +0,0 @@ -namespace Lattice.Core.DragDrop.Utilities; - -/// -/// Утилиты для работы с системой перетаскивания. -/// -public static class DragDropUtilities -{ - #region Effect Utilities - - /// - /// Проверяет, совместимы ли эффекты источника и цели. - /// - 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; - } - - /// - /// Получает наиболее подходящий эффект на основе доступных. - /// - 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 - - /// - /// Вычисляет расстояние между двумя точками. - /// - 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); - } - - /// - /// Проверяет, превысило ли перемещение пороговое значение. - /// - public static bool HasExceededDragThreshold(Geometry.Point startPoint, Geometry.Point currentPoint, double threshold) - { - var distance = CalculateDistance(startPoint, currentPoint); - return distance >= threshold; - } - - /// - /// Определяет позицию сброса относительно прямоугольника. - /// - 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 - - /// - /// Создает информацию о перетаскивании. - /// - public static Models.DragInfo CreateDragInfo( - object data, - Geometry.Point startPosition, - Enums.DragDropEffects allowedEffects = Enums.DragDropEffects.Copy | Enums.DragDropEffects.Move, - object? source = null, - Dictionary? 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; - } - - /// - /// Создает простую реализацию источника перетаскивания. - /// - public static Abstractions.IDragSource CreateSimpleDragSource( - Func dataProvider, - Func? canDrag = null, - Action? onCompleted = null, - Action? onCancelled = null) - { - return new SimpleDragSource(dataProvider, canDrag, onCompleted, onCancelled); - } - - /// - /// Создает простую реализацию цели сброса. - /// - public static Abstractions.IDropTarget CreateSimpleDropTarget( - Func? canAccept = null, - Action? onDragOver = null, - Action? onDrop = null, - Action? onDragLeave = null) - { - return new SimpleDropTarget(canAccept, onDragOver, onDrop, onDragLeave); - } - - #endregion - - #region Data Extraction - - /// - /// Извлекает данные из элемента с поддержкой различных паттернов. - /// - 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; - } - - /// - /// Проверяет, совместимы ли данные с указанными типами. - /// - public static bool IsDataCompatible(object? data, IEnumerable? 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 _dataProvider; - private readonly Func? _canDrag; - private readonly Action? _onCompleted; - private readonly Action? _onCancelled; - - public SimpleDragSource( - Func dataProvider, - Func? canDrag = null, - Action? onCompleted = null, - Action? 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? _canAccept; - private readonly Action? _onDragOver; - private readonly Action? _onDrop; - private readonly Action? _onDragLeave; - - public SimpleDropTarget( - Func? canAccept = null, - Action? onDragOver = null, - Action? 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 -} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs new file mode 100644 index 0000000..55454d0 --- /dev/null +++ b/Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs @@ -0,0 +1,49 @@ +using Microsoft.UI.Xaml; + +namespace Lattice.UI.DragDrop.WinUI.Behaviors; + +/// +/// Attached properties для DragSource. +/// +public static class DragSource +{ + public static readonly DependencyProperty IsEnabledProperty = + DependencyProperty.RegisterAttached( + "IsEnabled", + typeof(bool), + typeof(DragSource), + new PropertyMetadata(false, OnIsEnabledChanged)); + + public static readonly DependencyProperty DragDataProperty = + DependencyProperty.RegisterAttached( + "DragData", + typeof(object), + typeof(DragSource), + new PropertyMetadata(null)); + + public static bool GetIsEnabled(UIElement element) => + (bool)element.GetValue(IsEnabledProperty); + + public static void SetIsEnabled(UIElement element, bool value) => + element.SetValue(IsEnabledProperty, value); + + public static object GetDragData(UIElement element) => + element.GetValue(DragDataProperty); + + public static void SetDragData(UIElement element, object value) => + element.SetValue(DragDataProperty, value); + + private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not UIElement element) return; + + // TODO: Здесь нужно создать экземпляр WinUIDragSourceBehavior + // и прикрепить его к элементу через DI + // Пока что устанавливаем данные в Tag + if ((bool)e.NewValue) + { + var data = GetDragData(element); + element.Tag = data; + } + } +} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs index 2958c1b..aa738c1 100644 --- a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs +++ b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs @@ -1,58 +1,31 @@ -using Lattice.Core.Geometry; +using Lattice.Core.DragDrop.Models; +using Lattice.Core.Geometry; +using Lattice.UI.DragDrop.Behaviors; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Lattice.UI.DragDrop.WinUI.Behaviors; /// -/// Поведение источника перетаскивания для WinUI UIElement. -/// Исправленная версия с правильной очисткой ресурсов. +/// Поведение источника перетаскивания для WinUI FrameworkElement. /// -public static class WinUIDragSourceBehavior +public class WinUIDragSourceBehavior : DragSourceBehaviorBase { - #region Вложенные типы - - private sealed class DragSourceContext : IDisposable - { - public Point DragStartPosition { get; set; } - public bool IsDragging { get; set; } - public UIElement? CurrentDragElement { get; set; } - public object? DragData { get; set; } - public Action? DragStartedHandler { get; set; } - public Action? DragCompletedHandler { get; set; } - public Core.DragDrop.Enums.DragDropEffects AllowedEffects { get; set; } - = Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move; - - public void Dispose() - { - DragStartedHandler = null; - DragCompletedHandler = null; - DragData = null; - CurrentDragElement = null; - } - } - - #endregion - - #region Прикрепленные свойства - /// - /// Идентификатор свойства для привязки данных перетаскивания. + /// Прикрепленное свойство для данных перетаскивания. /// public static readonly DependencyProperty DragDataProperty = DependencyProperty.RegisterAttached( "DragData", typeof(object), typeof(WinUIDragSourceBehavior), - new PropertyMetadata(null, OnDragDataChanged)); + new PropertyMetadata(null)); /// - /// Идентификатор свойства для включения перетаскивания. + /// Прикрепленное свойство для включения перетаскивания. /// public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached( @@ -62,368 +35,256 @@ public static class WinUIDragSourceBehavior new PropertyMetadata(false, OnIsEnabledChanged)); /// - /// Идентификатор свойства для разрешенных эффектов перетаскивания. + /// Получает значение DragData. /// - public static readonly DependencyProperty AllowedEffectsProperty = - DependencyProperty.RegisterAttached( - "AllowedEffects", - typeof(Core.DragDrop.Enums.DragDropEffects), - typeof(WinUIDragSourceBehavior), - new PropertyMetadata(Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move)); - - /// - /// Идентификатор свойства для обработчика начала перетаскивания. - /// - public static readonly DependencyProperty DragStartedHandlerProperty = - DependencyProperty.RegisterAttached( - "DragStartedHandler", - typeof(Action), - typeof(WinUIDragSourceBehavior), - new PropertyMetadata(null)); - - /// - /// Идентификатор свойства для обработчика завершения перетаскивания. - /// - public static readonly DependencyProperty DragCompletedHandlerProperty = - DependencyProperty.RegisterAttached( - "DragCompletedHandler", - typeof(Action), - typeof(WinUIDragSourceBehavior), - new PropertyMetadata(null)); - - /// - /// Идентификатор свойства для обработчика создания данных перетаскивания. - /// - public static readonly DependencyProperty DragDataFactoryProperty = - DependencyProperty.RegisterAttached( - "DragDataFactory", - typeof(Func>), - typeof(WinUIDragSourceBehavior), - new PropertyMetadata(null)); - - #endregion - - #region Статические поля - - private static readonly ConditionalWeakTable _contexts = new(); - - #endregion - - #region Методы доступа к свойствам - - /// - /// Получает значение свойства DragData для указанного элемента. - /// - public static object GetDragData(UIElement element) => - element.GetValue(DragDataProperty); - - /// - /// Устанавливает значение свойства DragData для указанного элемента. - /// - public static void SetDragData(UIElement element, object value) => - element.SetValue(DragDataProperty, value); - - /// - /// Получает значение свойства IsEnabled для указанного элемента. - /// - public static bool GetIsEnabled(UIElement element) => - (bool)element.GetValue(IsEnabledProperty); - - /// - /// Устанавливает значение свойства IsEnabled для указанного элемента. - /// - public static void SetIsEnabled(UIElement element, bool value) => - element.SetValue(IsEnabledProperty, value); - - /// - /// Получает значение свойства AllowedEffects для указанного элемента. - /// - public static Core.DragDrop.Enums.DragDropEffects GetAllowedEffects(UIElement element) => - (Core.DragDrop.Enums.DragDropEffects)element.GetValue(AllowedEffectsProperty); - - /// - /// Устанавливает значение свойства AllowedEffects для указанного элемента. - /// - public static void SetAllowedEffects(UIElement element, Core.DragDrop.Enums.DragDropEffects value) => - element.SetValue(AllowedEffectsProperty, value); - - /// - /// Получает обработчик начала перетаскивания. - /// - public static Action GetDragStartedHandler(UIElement element) => - (Action)element.GetValue(DragStartedHandlerProperty); - - /// - /// Устанавливает обработчик начала перетаскивания. - /// - public static void SetDragStartedHandler(UIElement element, Action value) => - element.SetValue(DragStartedHandlerProperty, value); - - /// - /// Получает обработчик завершения перетаскивания. - /// - public static Action GetDragCompletedHandler(UIElement element) => - (Action)element.GetValue(DragCompletedHandlerProperty); - - /// - /// Устанавливает обработчик завершения перетаскивания. - /// - public static void SetDragCompletedHandler(UIElement element, Action value) => - element.SetValue(DragCompletedHandlerProperty, value); - - /// - /// Получает фабрику данных перетаскивания. - /// - public static Func> GetDragDataFactory(UIElement element) => - (Func>)element.GetValue(DragDataFactoryProperty); - - /// - /// Устанавливает фабрику данных перетаскивания. - /// - public static void SetDragDataFactory(UIElement element, Func> value) => - element.SetValue(DragDataFactoryProperty, value); - - #endregion - - #region Обработчики изменений свойств - - private static void OnDragDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + public static object GetDragData(FrameworkElement element) { - if (d is UIElement element) - { - if (_contexts.TryGetValue(element, out var context)) - { - context.DragData = e.NewValue; - } - } + return element.GetValue(DragDataProperty); + } + + /// + /// Устанавливает значение DragData. + /// + public static void SetDragData(FrameworkElement element, object value) + { + element.SetValue(DragDataProperty, value); + } + + /// + /// Получает значение IsEnabled. + /// + public static bool GetIsEnabled(FrameworkElement element) + { + return (bool)element.GetValue(IsEnabledProperty); + } + + /// + /// Устанавливает значение IsEnabled. + /// + public static void SetIsEnabled(FrameworkElement element, bool value) + { + element.SetValue(IsEnabledProperty, value); } private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is UIElement element) + if (d is FrameworkElement element) { + // Получаем или создаем экземпляр поведения через Attached Property + var behavior = GetBehavior(element); + if ((bool)e.NewValue) { - EnableDrag(element); + if (behavior == null) + { + behavior = new WinUIDragSourceBehavior(); + SetBehavior(element, behavior); + } + + behavior.AssociatedElement = element; } else { - DisableDrag(element); + if (behavior != null) + { + behavior.Detach(); + SetBehavior(element, null); + } } } } - #endregion - - #region Включение/выключение перетаскивания - - private static void EnableDrag(UIElement element) + public WinUIDragSourceBehavior() + : base(ServiceProviderHelper.GetServiceProvider()) { - if (_contexts.TryGetValue(element, out _)) - return; - - var context = new DragSourceContext - { - DragData = GetDragData(element), - DragStartedHandler = GetDragStartedHandler(element), - DragCompletedHandler = GetDragCompletedHandler(element), - AllowedEffects = GetAllowedEffects(element) - }; - - _contexts.Add(element, context); + } + protected override void SubscribeToEvents(FrameworkElement element) + { element.PointerPressed += OnPointerPressed; element.PointerMoved += OnPointerMoved; element.PointerReleased += OnPointerReleased; element.PointerCanceled += OnPointerCanceled; element.PointerCaptureLost += OnPointerCaptureLost; - element.Unloaded += OnElementUnloaded; + element.LostFocus += OnLostFocus; } - private static void DisableDrag(UIElement element) + protected override void UnsubscribeFromEvents(FrameworkElement element) { element.PointerPressed -= OnPointerPressed; element.PointerMoved -= OnPointerMoved; element.PointerReleased -= OnPointerReleased; element.PointerCanceled -= OnPointerCanceled; element.PointerCaptureLost -= OnPointerCaptureLost; - element.Unloaded -= OnElementUnloaded; + element.LostFocus -= OnLostFocus; + } - if (_contexts.TryGetValue(element, out var context)) + private void OnPointerPressed(object sender, PointerRoutedEventArgs e) + { + if (AssociatedElement == null) return; + + var point = e.GetCurrentPoint(AssociatedElement); + OnInteractionStarted(new Point(point.Position.X, point.Position.Y)); + } + + private void OnPointerMoved(object sender, PointerRoutedEventArgs e) + { + if (AssociatedElement == null) return; + + var point = e.GetCurrentPoint(AssociatedElement); + OnInteractionMoved(new Point(point.Position.X, point.Position.Y)); + } + + private void OnPointerReleased(object sender, PointerRoutedEventArgs e) + { + OnInteractionEnded(); + } + + private void OnPointerCanceled(object sender, PointerRoutedEventArgs e) + { + OnInteractionCancelled(); + } + + private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) + { + OnInteractionCancelled(); + } + + private void OnLostFocus(object sender, RoutedEventArgs e) + { + OnInteractionCancelled(); + } + + protected override Point ConvertToScreenCoordinates(Point point) + { + if (AssociatedElement == null) + return point; + + var transform = AssociatedElement.TransformToVisual(null); + var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y)); + return new Point(screenPoint.X, screenPoint.Y); + } + + /// + public override async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync() + { + if (AssociatedElement == null) { - context.Dispose(); - _contexts.Remove(element); + return (false, null); } - } - #endregion - - #region Обработчики событий - - private static void OnPointerPressed(object sender, PointerRoutedEventArgs e) - { - if (sender is UIElement element && _contexts.TryGetValue(element, out var context)) + var data = GetDragData(AssociatedElement); + if (data == null) { - var point = e.GetCurrentPoint(element); - context.DragStartPosition = new Point(point.Position.X, point.Position.Y); - context.CurrentDragElement = element; - element.CapturePointer(e.Pointer); + // Пробуем получить данные из Tag или других источников + data = AssociatedElement.Tag ?? AssociatedElement.DataContext; } - } - private static async void OnPointerMoved(object sender, PointerRoutedEventArgs e) - { - if (sender is UIElement element && _contexts.TryGetValue(element, out var context)) + if (data == null) { - if (context.IsDragging || context.CurrentDragElement == null) - return; - - var currentPoint = e.GetCurrentPoint(context.CurrentDragElement); - var currentPosition = new Point(currentPoint.Position.X, currentPoint.Position.Y); - - var distance = CalculateDistance(context.DragStartPosition, currentPosition); - if (distance > 3.0) // Порог перетаскивания - { - context.IsDragging = true; - await StartDragAsync(context.CurrentDragElement, currentPosition, context); - } + return (false, null); } + + // Получаем начальную позицию в экранных координатах + var startPosition = ConvertToScreenCoordinates(_dragStartPosition); + + // Создаем DragInfo с учетом вашего конструктора + var dragInfo = new DragInfo( + data: data, + allowedEffects: Core.DragDrop.Enums.DragDropEffects.Move | + Core.DragDrop.Enums.DragDropEffects.Copy, + startPosition: startPosition, + source: this + ); + + return (true, dragInfo); } - private static async Task StartDragAsync(UIElement element, Point currentPosition, DragSourceContext context) + protected override void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects) { - try - { - object? data = context.DragData; + base.OnDragCompleted(dragInfo, effects); - // Если есть фабрика данных, используем ее - var factory = GetDragDataFactory(element); - if (factory != null) - { - data = await factory(element); - } - - if (data != null) - { - context.DragData = data; - - // Вызываем обработчик начала перетаскивания - context.DragStartedHandler?.Invoke(element); - - // Устанавливаем визуальный эффект - SetDragVisualState(element, true); - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"Error starting drag: {ex.Message}"); - context.IsDragging = false; - } + // Визуальная обратная связь при завершении + SetVisualState(AssociatedElement, "Normal"); } - private static void OnPointerReleased(object sender, PointerRoutedEventArgs e) + protected override void OnDragCancelled(DragInfo dragInfo) { - CompleteDrag(sender as UIElement, Core.DragDrop.Enums.DragDropEffects.Copy); + base.OnDragCancelled(dragInfo); + + // Визуальная обратная связь при отмене + SetVisualState(AssociatedElement, "Normal"); } - private static void OnPointerCanceled(object sender, PointerRoutedEventArgs e) + private void SetVisualState(FrameworkElement? element, string stateName) { - CompleteDrag(sender as UIElement, Core.DragDrop.Enums.DragDropEffects.None); - } - - private static void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) - { - CompleteDrag(sender as UIElement, Core.DragDrop.Enums.DragDropEffects.None); - } - - private static void OnElementUnloaded(object sender, RoutedEventArgs e) - { - if (sender is UIElement element) - { - DisableDrag(element); - } - } - - #endregion - - #region Вспомогательные методы - - private static void CompleteDrag(UIElement? element, Core.DragDrop.Enums.DragDropEffects effects) - { - if (element != null && _contexts.TryGetValue(element, out var context)) - { - // Вызываем обработчик завершения перетаскивания - context.DragCompletedHandler?.Invoke(element, effects); - - // Сбрасываем визуальный эффект - SetDragVisualState(element, false); - - element.ReleasePointerCaptures(); - - ResetDragState(context); - } - } - - private static void SetDragVisualState(UIElement element, bool isDragging) - { - // Для элементов, которые являются Control, используем VisualStateManager if (element is Control control) { - // Пытаемся перейти к состоянию "Dragging" или "Normal" try { - VisualStateManager.GoToState(control, isDragging ? "Dragging" : "Normal", true); + VisualStateManager.GoToState(control, stateName, true); } catch { - // Если состояния не определены, меняем свойства напрямую - control.Opacity = isDragging ? 0.7 : 1.0; + // Fallback + control.Opacity = 1.0; } } - else + else if (element != null) { - // Для обычных UIElement меняем свойства напрямую - element.Opacity = isDragging ? 0.7 : 1.0; + // Альтернативная визуальная обратная связь для не-Control элементов + element.Opacity = 1.0; } } - private static double CalculateDistance(Point p1, Point p2) + // Attached property для хранения экземпляра поведения + private static readonly DependencyProperty BehaviorProperty = + DependencyProperty.RegisterAttached( + "Behavior", + typeof(WinUIDragSourceBehavior), + typeof(WinUIDragSourceBehavior), + new PropertyMetadata(null)); + + private static WinUIDragSourceBehavior GetBehavior(FrameworkElement element) { - var dx = p2.X - p1.X; - var dy = p2.Y - p1.Y; - return Math.Sqrt(dx * dx + dy * dy); + return (WinUIDragSourceBehavior)element.GetValue(BehaviorProperty); } - private static void ResetDragState(DragSourceContext context) + private static void SetBehavior(FrameworkElement element, WinUIDragSourceBehavior? value) { - context.IsDragging = false; - context.CurrentDragElement = null; - context.DragStartPosition = default; + element.SetValue(BehaviorProperty, value); } +} - #endregion +/// +/// Вспомогательный класс для получения IServiceProvider. +/// +internal static class ServiceProviderHelper +{ + private static IServiceProvider? _serviceProvider; - #region Методы очистки - - /// - /// Очищает все ресурсы, связанные с поведением перетаскивания. - /// - public static void Cleanup() + public static IServiceProvider GetServiceProvider() { - var elements = new List(); - - foreach (var kvp in _contexts) + if (_serviceProvider == null) { - elements.Add(kvp.Key); + // Ищем IServiceProvider в Application.Current.Resources + if (Application.Current.Resources.TryGetValue("ServiceProvider", out var provider) && + provider is IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + else + { + throw new InvalidOperationException( + "IServiceProvider не найден. Убедитесь, что ServiceProvider зарегистрирован в ресурсах приложения."); + } } - foreach (var element in elements) - { - DisableDrag(element); - } - - _contexts.Clear(); + return _serviceProvider; } - #endregion + public static void SetServiceProvider(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } } \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs index 17b491d..a5848e9 100644 --- a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs +++ b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs @@ -1,402 +1,345 @@ -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; +using Lattice.Core.DragDrop.Models; +using Lattice.Core.Geometry; +using Lattice.UI.DragDrop.Behaviors; +using Microsoft.UI.Xaml; using System; -using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Threading.Tasks; -namespace Lattice.UI.DragDrop.WinUI.Behaviors; - -/// -/// Поведение цели сброса для WinUI UIElement. -/// -public static class WinUIDropTargetBehavior +namespace Lattice.UI.DragDrop.WinUI.Behaviors { /// - /// Идентификатор свойства для включения цели сброса. + /// Поведение цели сброса для элементов WinUI. + /// Позволяет элементам принимать данные при операции перетаскивания. /// - public static readonly DependencyProperty IsEnabledProperty = - DependencyProperty.RegisterAttached( - "IsEnabled", - typeof(bool), - typeof(WinUIDropTargetBehavior), - new PropertyMetadata(false, OnIsEnabledChanged)); - - /// - /// Идентификатор свойства для фильтрации принимаемых данных. - /// - public static readonly DependencyProperty AcceptsDataTypesProperty = - DependencyProperty.RegisterAttached( - "AcceptsDataTypes", - typeof(Type[]), - typeof(WinUIDropTargetBehavior), - new PropertyMetadata(null)); - - /// - /// Идентификатор свойства для обработчика сброса. - /// - public static readonly DependencyProperty DropHandlerProperty = - DependencyProperty.RegisterAttached( - "DropHandler", - typeof(Core.DragDrop.Abstractions.IDropTarget), - typeof(WinUIDropTargetBehavior), - new PropertyMetadata(null)); - - /// - /// Идентификатор свойства для отображения визуальной обратной связи. - /// - public static readonly DependencyProperty ShowVisualFeedbackProperty = - DependencyProperty.RegisterAttached( - "ShowVisualFeedback", - typeof(bool), - typeof(WinUIDropTargetBehavior), - new PropertyMetadata(true)); - - /// - /// Идентификатор свойства для стиля визуальной обратной связи. - /// - public static readonly DependencyProperty FeedbackStyleProperty = - DependencyProperty.RegisterAttached( - "FeedbackStyle", - typeof(Style), - typeof(WinUIDropTargetBehavior), - new PropertyMetadata(null)); - - /// - /// Идентификатор свойства для обработчика входа перетаскивания. - /// - public static readonly DependencyProperty DragEnterHandlerProperty = - DependencyProperty.RegisterAttached( - "DragEnterHandler", - typeof(Action), - typeof(WinUIDropTargetBehavior), - new PropertyMetadata(null)); - - /// - /// Идентификатор свойства для обработчика выхода перетаскивания. - /// - public static readonly DependencyProperty DragLeaveHandlerProperty = - DependencyProperty.RegisterAttached( - "DragLeaveHandler", - typeof(Action), - typeof(WinUIDropTargetBehavior), - new PropertyMetadata(null)); - - /// - /// Идентификатор свойства для обработчика сброса. - /// - public static readonly DependencyProperty DropHandlerActionProperty = - DependencyProperty.RegisterAttached( - "DropHandlerAction", - typeof(Action), - typeof(WinUIDropTargetBehavior), - new PropertyMetadata(null)); - - // Словарь для отслеживания текущих перетаскиваемых элементов - private static readonly Dictionary _dragOverElements = new(); - - /// - /// Получает значение свойства IsEnabled для указанного элемента. - /// - public static bool GetIsEnabled(UIElement element) => - (bool)element.GetValue(IsEnabledProperty); - - /// - /// Устанавливает значение свойства IsEnabled для указанного элемента. - /// - public static void SetIsEnabled(UIElement element, bool value) => - element.SetValue(IsEnabledProperty, value); - - /// - /// Получает значение свойства AcceptsDataTypes для указанного элемента. - /// - public static Type[] GetAcceptsDataTypes(UIElement element) => - (Type[])element.GetValue(AcceptsDataTypesProperty); - - /// - /// Устанавливает значение свойства AcceptsDataTypes для указанного элемента. - /// - public static void SetAcceptsDataTypes(UIElement element, Type[] value) => - element.SetValue(AcceptsDataTypesProperty, value); - - /// - /// Получает значение свойства DropHandler для указанного элемента. - /// - public static Core.DragDrop.Abstractions.IDropTarget GetDropHandler(UIElement element) => - (Core.DragDrop.Abstractions.IDropTarget)element.GetValue(DropHandlerProperty); - - /// - /// Устанавливает значение свойства DropHandler для указанного элемента. - /// - public static void SetDropHandler(UIElement element, Core.DragDrop.Abstractions.IDropTarget value) => - element.SetValue(DropHandlerProperty, value); - - /// - /// Получает значение свойства ShowVisualFeedback для указанного элемента. - /// - public static bool GetShowVisualFeedback(UIElement element) => - (bool)element.GetValue(ShowVisualFeedbackProperty); - - /// - /// Устанавливает значение свойства ShowVisualFeedback для указанного элемента. - /// - public static void SetShowVisualFeedback(UIElement element, bool value) => - element.SetValue(ShowVisualFeedbackProperty, value); - - /// - /// Получает значение свойства FeedbackStyle для указанного элемента. - /// - public static Style GetFeedbackStyle(UIElement element) => - (Style)element.GetValue(FeedbackStyleProperty); - - /// - /// Устанавливает значение свойства FeedbackStyle для указанного элемента. - /// - public static void SetFeedbackStyle(UIElement element, Style value) => - element.SetValue(FeedbackStyleProperty, value); - - /// - /// Получает обработчик входа перетаскивания. - /// - public static Action GetDragEnterHandler(UIElement element) => - (Action)element.GetValue(DragEnterHandlerProperty); - - /// - /// Устанавливает обработчик входа перетаскивания. - /// - public static void SetDragEnterHandler(UIElement element, Action value) => - element.SetValue(DragEnterHandlerProperty, value); - - /// - /// Получает обработчик выхода перетаскивания. - /// - public static Action GetDragLeaveHandler(UIElement element) => - (Action)element.GetValue(DragLeaveHandlerProperty); - - /// - /// Устанавливает обработчик выхода перетаскивания. - /// - public static void SetDragLeaveHandler(UIElement element, Action value) => - element.SetValue(DragLeaveHandlerProperty, value); - - /// - /// Получает обработчик сброса. - /// - public static Action GetDropHandlerAction(UIElement element) => - (Action)element.GetValue(DropHandlerActionProperty); - - /// - /// Устанавливает обработчик сброса. - /// - public static void SetDropHandlerAction(UIElement element, Action value) => - element.SetValue(DropHandlerActionProperty, value); - - private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + /// + /// + /// Это поведение должно быть прикреплено к , который должен выступать в качестве цели сброса. + /// Поведение автоматически регистрирует элемент в системе перетаскивания и обрабатывает все аспекты операции сброса. + /// + /// + /// Для использования необходимо: + /// 1. Создать экземпляр поведения с помощью или через DI. + /// 2. Переопределить методы и для реализации логики принятия данных. + /// 3. При необходимости переопределить для настройки визуальной обратной связи. + /// + /// + public class WinUIDropTargetBehavior : DropTargetBehaviorBase { - if (d is UIElement element) + private static readonly ConcurrentDictionary _attachedBehaviors = new(); + private readonly WeakReference? _weakElement; + + /// + /// Инициализирует новый экземпляр класса . + /// + /// Провайдер сервисов. + /// + /// Конструктор создает экземпляр поведения, но не прикрепляет его к элементу. + /// Для прикрепления используйте метод . + /// + public WinUIDropTargetBehavior(IServiceProvider serviceProvider) + : base(serviceProvider) { - if ((bool)e.NewValue) + } + + /// + /// Инициализирует новый экземпляр класса . + /// + /// Провайдер сервисов. + /// Элемент, к которому прикрепляется поведение. + /// + /// Конструктор создает экземпляр поведения и сразу прикрепляет его к указанному элементу. + /// + public WinUIDropTargetBehavior(IServiceProvider serviceProvider, FrameworkElement element) + : base(serviceProvider) + { + AssociatedElement = element ?? throw new ArgumentNullException(nameof(element)); + } + + /// + /// Прикрепляет поведение к указанному элементу. + /// + /// Элемент, к которому прикрепляется поведение. + /// Провайдер сервисов. + /// + /// Экземпляр поведения, прикрепленного к элементу. Если к элементу уже прикреплено поведение, + /// возвращает существующий экземпляр. + /// + /// + /// Выбрасывается, когда или равны null. + /// + /// + /// + /// Этот метод обеспечивает, что к каждому элементу прикреплен только один экземпляр поведения. + /// Если метод вызывается повторно для того же элемента, возвращается существующий экземпляр. + /// + /// + /// Прикрепленное поведение автоматически отслеживает изменения макета элемента и обновляет + /// его границы в системе перетаскивания. + /// + /// + public static WinUIDropTargetBehavior Attach(FrameworkElement element, IServiceProvider serviceProvider) + { + if (element == null) throw new ArgumentNullException(nameof(element)); + if (serviceProvider == null) throw new ArgumentNullException(nameof(serviceProvider)); + + return _attachedBehaviors.GetOrAdd(element, key => { - EnableDrop(element); + var behavior = new WinUIDropTargetBehavior(serviceProvider, key); + + // Подписка на события жизненного цикла элемента + key.Unloaded += OnElementUnloaded; + return behavior; + }); + } + + /// + /// Открепляет поведение от указанного элемента. + /// + /// Элемент, от которого открепляется поведение. + /// + /// true, если поведение было успешно откреплено; false, если поведение не было прикреплено к элементу. + /// + /// + /// Этот метод освобождает все ресурсы, связанные с поведением, и отписывается от событий элемента. + /// После вызова этого метода элемент перестает быть целью сброса. + /// + public static bool Detach(FrameworkElement element) + { + if (element == null) return false; + + if (_attachedBehaviors.TryRemove(element, out var behavior)) + { + element.Unloaded -= OnElementUnloaded; + behavior.Detach(); + return true; } - else + + return false; + } + + /// + /// Получает поведение, прикрепленное к указанному элементу. + /// + /// Элемент, для которого требуется получить поведение. + /// + /// Экземпляр поведения, прикрепленного к элементу, или null, если поведение не прикреплено. + /// + public static WinUIDropTargetBehavior? GetAttachedBehavior(FrameworkElement element) + { + _attachedBehaviors.TryGetValue(element, out var behavior); + return behavior; + } + + /// + /// Подписывается на события элемента. + /// + /// Элемент, к которому прикрепляется поведение. + /// + /// + /// Этот метод подписывается на следующие события: + /// + /// + /// - для отслеживания изменений макета + /// - для отслеживания изменений размера + /// - для инициализации при загрузке элемента + /// + /// + /// Переопределите этот метод, чтобы добавить подписку на дополнительные события. + /// + /// + protected override void SubscribeToEvents(FrameworkElement element) + { + if (element == null) return; + + element.LayoutUpdated += OnLayoutUpdated; + element.SizeChanged += OnSizeChanged; + element.Loaded += OnLoaded; + + // Если элемент уже загружен, сразу обновляем границы + if (element.IsLoaded) { - DisableDrop(element); + UpdateBounds(); } } - } - private static void EnableDrop(UIElement element) - { - element.AllowDrop = true; - element.DragEnter += OnDragEnter; - element.DragOver += OnDragOver; - element.DragLeave += OnDragLeave; - element.Drop += OnDrop; - } - - private static void DisableDrop(UIElement element) - { - element.AllowDrop = false; - element.DragEnter -= OnDragEnter; - element.DragOver -= OnDragOver; - element.DragLeave -= OnDragLeave; - element.Drop -= OnDrop; - } - - private static void OnDragEnter(object sender, DragEventArgs e) - { - var element = sender as UIElement; - if (element == null) return; - - // Проверяем, можно ли принять данные - if (CanAcceptData(element, e.DataView)) + /// + /// Отписывается от событий элемента. + /// + /// Элемент, от которого отписывается поведение. + /// + /// Этот метод отписывается от всех событий, на которые подписался . + /// + protected override void UnsubscribeFromEvents(FrameworkElement element) { - e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; + if (element == null) return; - // Визуальная обратная связь - ShowVisualFeedback(element, true); - - // Вызываем пользовательский обработчик - GetDragEnterHandler(element)?.Invoke(element); - - // Добавляем в словарь - _dragOverElements[element] = true; - } - else - { - e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None; + element.LayoutUpdated -= OnLayoutUpdated; + element.SizeChanged -= OnSizeChanged; + element.Loaded -= OnLoaded; } - e.Handled = true; - } - - private static void OnDragOver(object sender, DragEventArgs e) - { - var element = sender as UIElement; - if (element == null) return; - - if (CanAcceptData(element, e.DataView)) + /// + /// Получает границы элемента в экранных координатах. + /// + /// Элемент, границы которого нужно получить. + /// + /// Прямоугольник, описывающий границы элемента в экранных координатах. + /// + /// + /// + /// Метод использует преобразование координат через + /// для получения глобальных координат элемента. + /// + /// + /// Если элемент не прикреплен к визуальному дереву или его границы не могут быть вычислены, + /// возвращается пустой прямоугольник. + /// + /// + protected override Rect GetScreenBounds(FrameworkElement element) { - e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; + if (element == null || !element.IsLoaded) + return Rect.Empty; - // Обновляем визуальную обратную связь на основе позиции - var position = e.GetPosition(element); - UpdateVisualFeedback(element, position); - } - else - { - e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None; - } - - e.Handled = true; - } - - private static void OnDragLeave(object sender, DragEventArgs e) - { - var element = sender as UIElement; - if (element == null) return; - - // Скрываем визуальную обратную связь - ShowVisualFeedback(element, false); - - // Вызываем пользовательский обработчик - GetDragLeaveHandler(element)?.Invoke(element); - - // Удаляем из словаря - _dragOverElements.Remove(element); - } - - private static void OnDrop(object sender, DragEventArgs e) - { - var element = sender as UIElement; - if (element == null) return; - - // Скрываем визуальную обратную связь - ShowVisualFeedback(element, false); - - // Получаем данные - var data = ExtractData(e.DataView); - - // Получаем обработчик или используем встроенный - var handler = GetDropHandler(element); - if (handler != null) - { - // Создаем DropInfo для обработчика - var dropInfo = CreateDropInfo(element, e, data); - handler.Drop(dropInfo); - } - - // Вызываем пользовательский обработчик - var actionHandler = GetDropHandlerAction(element); - if (actionHandler != null && data != null) - { - actionHandler.Invoke(element, data); - } - - // Удаляем из словаря - _dragOverElements.Remove(element); - - e.Handled = true; - } - - private static bool CanAcceptData(UIElement element, Windows.ApplicationModel.DataTransfer.DataPackageView dataView) - { - // Проверяем фильтр типов данных - var acceptedTypes = GetAcceptsDataTypes(element); - if (acceptedTypes != null && acceptedTypes.Length > 0) - { - // В реальной реализации нужно проверять доступные форматы данных - // Здесь упрощенная проверка - return dataView.AvailableFormats.Count > 0; - } - - // Если нет фильтра, принимаем все - return true; - } - - private static object? ExtractData(Windows.ApplicationModel.DataTransfer.DataPackageView dataView) - { - // Упрощенная реализация извлечения данных - // В реальном приложении нужно обрабатывать разные форматы данных - try - { - if (dataView.Contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.Text)) - { - // В WinUI 3 нужно использовать async/await, но это упрощенный пример - // В реальном коде нужно использовать async методы - return "Text data from drag"; - } - } - catch - { - // Игнорируем ошибки извлечения данных - } - - return null; - } - - private static void ShowVisualFeedback(UIElement element, bool show) - { - if (!GetShowVisualFeedback(element)) return; - - // Для элементов, которые являются Control, используем VisualStateManager - if (element is Control control) - { - // Пытаемся перейти к состоянию "DragOver" или "Normal" try { - VisualStateManager.GoToState(control, show ? "DragOver" : "Normal", true); + // Получаем корневой элемент окна + var rootVisual = element.XamlRoot?.Content as UIElement; + if (rootVisual == null) + return Rect.Empty; + + // Преобразуем границы элемента в координаты корневого элемента + var transform = element.TransformToVisual(rootVisual); + var position = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); + + return new Rect( + position.X, + position.Y, + element.ActualWidth, + element.ActualHeight); } catch { - // Если состояния не определены, меняем свойства напрямую - control.Background = show ? - new Microsoft.UI.Xaml.Media.SolidColorBrush(Windows.UI.Color.FromArgb(30, 0, 120, 215)) : - null; + // В случае ошибки возвращаем пустой прямоугольник + return Rect.Empty; } } - else + + /// + /// Определяет, может ли элемент принять сбрасываемые данные. + /// + /// Информация о сбросе. + /// + /// true, если элемент может принять данные; в противном случае — false. + /// + /// + /// + /// Этот метод является абстрактным и должен быть переопределен в производных классах + /// для реализации логики принятия данных. + /// + /// + /// Базовая реализация всегда возвращает false. Переопределите этот метод, чтобы определить, + /// какие типы данных может принимать ваш элемент и при каких условиях. + /// + /// + /// Пример реализации: + /// + /// public override bool CanAcceptDrop(DropInfo dropInfo) + /// { + /// // Принимаем только строковые данные + /// return dropInfo.Data is string; + /// } + /// + /// + /// + public override async Task CanAcceptDropAsync(DropInfo dropInfo) { - // Для обычных UIElement меняем свойства напрямую - element.Opacity = show ? 0.8 : 1.0; + // Базовая реализация - не принимает никакие данные. + // Переопределите этот метод в производных классах. + return false; } - } - private static void UpdateVisualFeedback(UIElement element, Windows.Foundation.Point position) - { - // Можно добавить логику для различных зон сброса - // Например, подсветка разных частей элемента - } + /// + /// Обрабатывает сброс данных на элемент. + /// + /// Информация о сбросе. + /// + /// + /// Этот метод вызывается, когда пользователь отпускает кнопку мыши над элементом, + /// и данные должны быть приняты. + /// + /// + /// Базовая реализация ничего не делает. Переопределите этот метод, чтобы реализовать + /// логику обработки принятых данных. + /// + /// + /// Пример реализации: + /// + /// public override void Drop(DropInfo dropInfo) + /// { + /// if (dropInfo.Data is string text) + /// { + /// // Обработка текстовых данных + /// AssociatedElement.SetValue(TextBlock.TextProperty, text); + /// dropInfo.MarkAsHandled(); + /// } + /// } + /// + /// + /// + public override async Task DropAsync(DropInfo dropInfo) + { + // Базовая реализация ничего не делает. + // Переопределите этот метод в производных классах. + } - private static Core.DragDrop.Models.DropInfo CreateDropInfo(UIElement element, DragEventArgs e, object? data) - { - var position = e.GetPosition(element); - var screenPosition = element.TransformToVisual(null).TransformPoint(position); + /// + /// Освобождает ресурсы, связанные с поведением. + /// + /// + /// + /// Этот метод отписывается от всех событий, отменяет регистрацию в сервисе перетаскивания + /// и очищает все ресурсы. + /// + /// + /// После вызова этого метода поведение больше не может быть использовано. + /// + /// + public override void Detach() + { + if (AssociatedElement != null && _attachedBehaviors.TryGetValue(AssociatedElement, out _)) + { + _attachedBehaviors.TryRemove(AssociatedElement, out _); + } - return new Core.DragDrop.Models.DropInfo( - data: data, - position: new Core.Geometry.Point(screenPosition.X, screenPosition.Y), - allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, - target: GetDropHandler(element) - ); + base.Detach(); + } + + #region Event Handlers + + private void OnLayoutUpdated(object? sender, object e) + { + OnElementLayoutChanged(); + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + OnElementLayoutChanged(); + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + UpdateBounds(); + } + + private static void OnElementUnloaded(object sender, RoutedEventArgs e) + { + if (sender is FrameworkElement element) + { + Detach(element); + } + } + + #endregion } } \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs b/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs index 2bf51f9..1cd8ccf 100644 --- a/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs +++ b/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs @@ -16,7 +16,7 @@ public static class DragDropExtensions /// /// Делает элемент источником перетаскивания с указанными данными. /// - public static void MakeDragSource(this UIElement element, object dragData) + public static void MakeDragSource(this FrameworkElement element, object dragData) { Behaviors.WinUIDragSourceBehavior.SetDragData(element, dragData); Behaviors.WinUIDragSourceBehavior.SetIsEnabled(element, true); @@ -25,53 +25,33 @@ public static class DragDropExtensions /// /// Делает элемент источником перетаскивания с фабрикой данных. /// - public static void MakeDragSource(this UIElement element, Func dataFactory) + public static void MakeDragSource(this FrameworkElement element, Func dataFactory) { element.MakeDragSource(dataFactory()); } - /// - /// Делает элемент источником перетаскивания с настраиваемыми параметрами. - /// - public static void MakeDragSource(this UIElement element, - object dragData, - Core.DragDrop.Enums.DragDropEffects allowedEffects, - Func? canDrag = null) - { - element.MakeDragSource(dragData); - element.SetAllowedEffects(allowedEffects); - - if (canDrag != null) - { - // Можно добавить кастомную логику проверки - } - } - /// /// Удаляет возможность перетаскивания с элемента. /// - public static void RemoveDragSource(this UIElement element) + public static void RemoveDragSource(this FrameworkElement element) { Behaviors.WinUIDragSourceBehavior.SetIsEnabled(element, false); } /// - /// Устанавливает разрешенные эффекты перетаскивания для элемента. + /// Проверяет, является ли элемент источником перетаскивания. /// - public static void SetAllowedEffects(this UIElement element, Core.DragDrop.Enums.DragDropEffects effects) + public static bool IsDragSource(this FrameworkElement element) { - Behaviors.WinUIDragSourceBehavior.SetAllowedEffects(element, effects); + return Behaviors.WinUIDragSourceBehavior.GetIsEnabled(element); } /// - /// Устанавливает смещение для визуального элемента перетаскивания. + /// Получает данные перетаскивания из элемента. /// - public static void SetDragVisualOffset(this UIElement element, double offsetX, double offsetY) + public static object? GetDragData(this FrameworkElement element) { - if (element is FrameworkElement frameworkElement) - { - frameworkElement.Tag = new Windows.Foundation.Point(offsetX, offsetY); - } + return Behaviors.WinUIDragSourceBehavior.GetDragData(element); } #endregion @@ -81,80 +61,65 @@ public static class DragDropExtensions /// /// Делает элемент целью сброса. /// - public static void MakeDropTarget(this UIElement element) + public static void MakeDropTarget(this FrameworkElement element) { - Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, true); + // Включаем AllowDrop для WinUI + element.AllowDrop = true; + element.SetValue(IsDropTargetProperty, true); } /// /// Делает элемент целью сброса с фильтром типов данных. /// - public static void MakeDropTarget(this UIElement element, params Type[] acceptedTypes) + public static void MakeDropTarget(this FrameworkElement element, params Type[] acceptedTypes) { - Behaviors.WinUIDropTargetBehavior.SetAcceptsDataTypes(element, acceptedTypes); - Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, true); - } - - /// - /// Делает элемент целью сброса с обработчиком. - /// - public static void MakeDropTarget(this UIElement element, Core.DragDrop.Abstractions.IDropTarget handler) - { - Behaviors.WinUIDropTargetBehavior.SetDropHandler(element, handler); - Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, true); - } - - /// - /// Делает элемент целью сброса с полной настройкой. - /// - public static void MakeDropTarget(this UIElement element, - Type[] acceptedTypes, - Core.DragDrop.Abstractions.IDropTarget? handler = null, - bool showVisualFeedback = true, - Style? feedbackStyle = null) - { - if (acceptedTypes != null && acceptedTypes.Length > 0) - { - Behaviors.WinUIDropTargetBehavior.SetAcceptsDataTypes(element, acceptedTypes); - } - - if (handler != null) - { - Behaviors.WinUIDropTargetBehavior.SetDropHandler(element, handler); - } - - Behaviors.WinUIDropTargetBehavior.SetShowVisualFeedback(element, showVisualFeedback); - - if (feedbackStyle != null) - { - Behaviors.WinUIDropTargetBehavior.SetFeedbackStyle(element, feedbackStyle); - } - - Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, true); + element.SetValue(AcceptsDataTypesProperty, acceptedTypes); + element.MakeDropTarget(); } /// /// Удаляет возможность сброса с элемента. /// - public static void RemoveDropTarget(this UIElement element) + public static void RemoveDropTarget(this FrameworkElement element) { - Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, false); + element.AllowDrop = false; + element.SetValue(IsDropTargetProperty, false); } /// - /// Устанавливает стиль визуальной обратной связи для цели сброса. + /// Проверяет, является ли элемент целью сброса. /// - public static void SetDropFeedbackStyle(this UIElement element, Style style) + public static bool IsDropTarget(this FrameworkElement element) { - Behaviors.WinUIDropTargetBehavior.SetFeedbackStyle(element, style); + return (bool)element.GetValue(IsDropTargetProperty); } /// - /// Включает или выключает визуальную обратную связь при сбросе. + /// Attached property для отметки цели сброса. /// - public static void SetDropVisualFeedback(this UIElement element, bool enabled) + public static readonly DependencyProperty IsDropTargetProperty = + DependencyProperty.RegisterAttached( + "IsDropTarget", + typeof(bool), + typeof(DragDropExtensions), + new PropertyMetadata(false)); + + /// + /// Attached property для фильтра типов данных. + /// + public static readonly DependencyProperty AcceptsDataTypesProperty = + DependencyProperty.RegisterAttached( + "AcceptsDataTypes", + typeof(Type[]), + typeof(DragDropExtensions), + new PropertyMetadata(null)); + + /// + /// Получает фильтр типов данных. + /// + public static Type[]? GetAcceptsDataTypes(this FrameworkElement element) { - Behaviors.WinUIDropTargetBehavior.SetShowVisualFeedback(element, enabled); + return (Type[]?)element.GetValue(AcceptsDataTypesProperty); } #endregion @@ -171,32 +136,17 @@ public static class DragDropExtensions { control.Style = style; } - } - - /// - /// Применяет стиль цели сброса к элементу. - /// - public static void ApplyDropTargetStyle(this Control control) - { - var style = Application.Current.Resources["DropTargetStyle"] as Style; - if (style != null) + else { - control.Style = style; + // Fallback стиль + var brush = Application.Current.Resources["SystemControlBackgroundAccentBrush"] as SolidColorBrush; + if (brush != null) + { + control.Background = brush; + } } } - /// - /// Включает визуальную обратную связь для элемента при перетаскивании. - /// - public static void EnableDragVisualFeedback(this Control control) - { - control.Loaded += (sender, e) => - { - // Убеждаемся, что у элемента есть визуальные состояния - EnsureVisualStates(control); - }; - } - /// /// Переключает визуальное состояние элемента для перетаскивания. /// @@ -208,22 +158,18 @@ public static class DragDropExtensions } catch { - // Если состояние не найдено, используем альтернативные методы + // Fallback для элементов без визуальных состояний switch (stateName) { case "Dragging": control.Opacity = 0.7; - control.RenderTransform = new ScaleTransform { ScaleX = 0.95, ScaleY = 0.95 }; break; case "DragOver": - control.Background = Application.Current.Resources["DragOverBackgroundBrush"] as Brush; - control.BorderBrush = Application.Current.Resources["DragOverBorderBrush"] as Brush; + control.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(50, 0, 120, 215)); break; case "Normal": control.ClearValue(Control.OpacityProperty); - control.ClearValue(Control.RenderTransformProperty); control.ClearValue(Control.BackgroundProperty); - control.ClearValue(Control.BorderBrushProperty); break; } } @@ -233,17 +179,6 @@ public static class DragDropExtensions #region Advanced Configuration - /// - /// Настраивает элемент для работы с сервисом перетаскивания. - /// - public static void ConfigureForDragDropService(this UIElement element, - Core.DragDrop.Services.IDragDropService service, - string? dropTargetGroup = null) - { - // Этот метод позволяет интегрировать элемент с централизованным сервисом - // В реальной реализации нужно регистрировать элемент в сервисе - } - /// /// Создает контейнер с поддержкой перетаскивания для элементов. /// @@ -260,8 +195,7 @@ public static class DragDropExtensions if (enableReordering) { - // Настраиваем контейнер для переупорядочивания элементов - container.MakeDropTarget(typeof(UIElement)); + container.MakeDropTarget(typeof(FrameworkElement)); } return container; @@ -270,9 +204,9 @@ public static class DragDropExtensions /// /// Делает все дочерние элементы перетаскиваемыми. /// - public static void MakeChildrenDraggable(this Panel container, Func dataSelector) + public static void MakeChildrenDraggable(this Panel container, Func dataSelector) { - foreach (var child in container.Children.OfType()) + foreach (var child in container.Children.OfType()) { var data = dataSelector(child); child.MakeDragSource(data); @@ -280,48 +214,4 @@ public static class DragDropExtensions } #endregion - - #region Utility Methods - - /// - /// Проверяет, является ли элемент источником перетаскивания. - /// - public static bool IsDragSource(this UIElement element) - { - return Behaviors.WinUIDragSourceBehavior.GetIsEnabled(element); - } - - /// - /// Проверяет, является ли элемент целью сброса. - /// - public static bool IsDropTarget(this UIElement element) - { - return Behaviors.WinUIDropTargetBehavior.GetIsEnabled(element); - } - - /// - /// Получает данные перетаскивания из элемента. - /// - public static object? GetDragData(this UIElement element) - { - return Behaviors.WinUIDragSourceBehavior.GetDragData(element); - } - - #endregion - - #region Helper Methods - - private static void EnsureVisualStates(Control control) - { - // В WinUI 3 визуальные состояния должны быть определены в стиле элемента - // Этот метод проверяет, что у элемента есть базовые визуальные состояния - - if (control.Template == null && control.Style == null) - { - // Применяем стандартный стиль, если у элемента нет своего - control.ApplyDragStyle(); - } - } - - #endregion } \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Extensions/FrameworkElementExtensions.cs b/Lattice.UI.DragDrop.WinUI/Extensions/FrameworkElementExtensions.cs new file mode 100644 index 0000000..69b82f5 --- /dev/null +++ b/Lattice.UI.DragDrop.WinUI/Extensions/FrameworkElementExtensions.cs @@ -0,0 +1,26 @@ +using Microsoft.UI.Xaml; + +namespace Lattice.UI.DragDrop.WinUI.Helpers; + +public static class FrameworkElementExtensions +{ + /// + /// Получает фактические размеры FrameworkElement. + /// + public static Windows.Foundation.Size GetActualSize(this FrameworkElement element) + { + return new Windows.Foundation.Size(element.ActualWidth, element.ActualHeight); + } + + /// + /// Получает границы элемента в экранных координатах. + /// + public static Windows.Foundation.Rect GetScreenBounds(this FrameworkElement element) + { + var transform = element.TransformToVisual(null); + var topLeft = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); + var bottomRight = transform.TransformPoint(new Windows.Foundation.Point(element.ActualWidth, element.ActualHeight)); + + return new Windows.Foundation.Rect(topLeft, bottomRight); + } +} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Integration/WinUIDragDropIntegrationService.cs b/Lattice.UI.DragDrop.WinUI/Integration/WinUIDragDropIntegrationService.cs index 1aaa7e0..d7b7327 100644 --- a/Lattice.UI.DragDrop.WinUI/Integration/WinUIDragDropIntegrationService.cs +++ b/Lattice.UI.DragDrop.WinUI/Integration/WinUIDragDropIntegrationService.cs @@ -3,6 +3,7 @@ using Lattice.Core.DragDrop.Models; using Lattice.Core.DragDrop.Services; using Lattice.Core.Geometry; using Lattice.UI.DragDrop.Abstractions; +using Lattice.UI.DragDrop.WinUI.Behaviors; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; using System; @@ -14,25 +15,23 @@ namespace Lattice.UI.DragDrop.WinUI.Integration; /// /// Сервис интеграции Drag & Drop с WinUI приложением. -/// Исправленная версия с правильной обработкой событий и очисткой ресурсов. /// public sealed class WinUIDragDropIntegrationService : IDisposable { #region Вложенные типы - private sealed class DragSourceAdapter : IDisposable + private sealed class DragSourceAdapter : IDragSource { - private readonly UIElement _element; - private readonly Func> _dragInfoFactory; + private readonly FrameworkElement _element; + private readonly Func _dragInfoFactory; private readonly IDragDropService _dragDropService; private Point _dragStartPosition; private bool _isDragging; private bool _disposed; - private object? _dragData; public DragSourceAdapter( - UIElement element, - Func> dragInfoFactory, + FrameworkElement element, + Func dragInfoFactory, IDragDropService dragDropService) { _element = element ?? throw new ArgumentNullException(nameof(element)); @@ -42,24 +41,6 @@ public sealed class WinUIDragDropIntegrationService : IDisposable SubscribeToEvents(); } - public DragSourceAdapter( - UIElement element, - object dragData, - IDragDropService dragDropService) - { - _element = element ?? throw new ArgumentNullException(nameof(element)); - _dragData = dragData ?? throw new ArgumentNullException(nameof(dragData)); - _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); - - _dragInfoFactory = async () => new DragInfo( - _dragData, - Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, - Point.Zero, - _element); - - SubscribeToEvents(); - } - private void SubscribeToEvents() { _element.PointerPressed += OnPointerPressed; @@ -86,7 +67,7 @@ public sealed class WinUIDragDropIntegrationService : IDisposable _dragStartPosition = new Point(point.Position.X, point.Position.Y); } - private async void OnPointerMoved(object sender, PointerRoutedEventArgs e) + private void OnPointerMoved(object sender, PointerRoutedEventArgs e) { if (_isDragging || _disposed) return; @@ -96,16 +77,24 @@ public sealed class WinUIDragDropIntegrationService : IDisposable var distance = CalculateDistance(_dragStartPosition, currentPosition); if (distance > _dragDropService.DragStartThreshold) { - await StartDragAsync(currentPosition); + StartDragOperation(currentPosition); } } - private async Task StartDragAsync(Point position) + private async Task StartDragOperation(Point position) { try { - var dragInfo = await _dragInfoFactory(); + var dragInfo = _dragInfoFactory(); + + // Обновляем позицию в dragInfo var screenPosition = GetScreenPosition(position); + dragInfo = new DragInfo( + dragInfo.Data, + dragInfo.AllowedEffects, + screenPosition, + dragInfo.Source + ); _isDragging = await _dragDropService.StartDragAsync(this, screenPosition); } @@ -134,131 +123,71 @@ public sealed class WinUIDragDropIntegrationService : IDisposable { if (!_isDragging || _disposed) return; - var point = e.GetCurrentPoint(_element); - var position = GetScreenPosition(new Point(point.Position.X, point.Position.Y)); - await _dragDropService.EndDragAsync(position); + await _dragDropService.EndDragAsync(); _isDragging = false; } - private async void OnPointerCanceled(object sender, PointerRoutedEventArgs e) + private void OnPointerCanceled(object sender, PointerRoutedEventArgs e) { if (!_isDragging || _disposed) return; - await _dragDropService.CancelDragAsync(); + _dragDropService.CancelDragAsync(); _isDragging = false; } - private async void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) + private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) { if (!_isDragging || _disposed) return; - await _dragDropService.CancelDragAsync(); + _dragDropService.CancelDragAsync(); _isDragging = false; } + #region IDragSource Implementation + + public async Task<(bool, DragInfo? dragInfo)> CancelDragAsync() + { + try + { + dragInfo = _dragInfoFactory(); + return dragInfo != null; + } + catch + { + dragInfo = null; + return false; + } + } + + public async Task StartDragAsync(DragInfo dragInfo) + { + return true; + } + + public async Task DragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects) + { + _isDragging = false; + } + + public async Task DragCancelledAsync(DragInfo dragInfo) + { + _isDragging = false; + } + + #endregion + public void Dispose() { if (_disposed) return; UnsubscribeFromEvents(); - - if (_isDragging) - { - Task.Run(async () => await _dragDropService.CancelDragAsync()).Wait(1000); - } - _disposed = true; GC.SuppressFinalize(this); } - } - private sealed class DropTargetAdapter : IDisposable - { - private readonly UIElement _element; - private readonly IDropTarget _dropTarget; - private readonly IDragDropService _dragDropService; - private string? _registrationId; - private bool _disposed; - - public DropTargetAdapter( - UIElement element, - IDropTarget dropTarget, - IDragDropService dragDropService) + public Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync() { - _element = element ?? throw new ArgumentNullException(nameof(element)); - _dropTarget = dropTarget ?? throw new ArgumentNullException(nameof(dropTarget)); - _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); - - SubscribeToEvents(); - UpdateRegistration(); - } - - private void SubscribeToEvents() - { - _element.LayoutUpdated += OnLayoutUpdated; - _element.Unloaded += OnElementUnloaded; - } - - private void UnsubscribeFromEvents() - { - _element.LayoutUpdated -= OnLayoutUpdated; - _element.Unloaded -= OnElementUnloaded; - } - - private void UpdateRegistration() - { - var bounds = GetElementBounds(); - if (_registrationId is null) - { - _registrationId = _dragDropService.RegisterDropTarget(_dropTarget, bounds); - } - else - { - _dragDropService.UpdateDropTargetBounds(_registrationId, bounds); - } - } - - private Rect GetElementBounds() - { - var transform = _element.TransformToVisual(null); - var topLeft = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); - var bottomRight = transform.TransformPoint(new Windows.Foundation.Point(_element.ActualWidth, _element.ActualHeight)); - - return new Rect( - topLeft.X, - topLeft.Y, - bottomRight.X - topLeft.X, - bottomRight.Y - topLeft.Y); - } - - private void OnLayoutUpdated(object? sender, object e) - { - if (_disposed) return; - UpdateRegistration(); - } - - private void OnElementUnloaded(object sender, RoutedEventArgs e) - { - Dispose(); - } - - public void Dispose() - { - if (_disposed) return; - - UnsubscribeFromEvents(); - - if (_registrationId is not null) - { - _dragDropService.UnregisterDropTarget(_registrationId); - _registrationId = null; - } - - if (_dropTarget is IDisposable disposable) - disposable.Dispose(); - - _disposed = true; - GC.SuppressFinalize(this); + throw new NotImplementedException(); } } @@ -269,30 +198,22 @@ public sealed class WinUIDragDropIntegrationService : IDisposable private readonly IDragDropService _dragDropService; private readonly IDragVisualProvider _dragVisualProvider; private readonly IDragDropHost _dragDropHost; - private readonly Dictionary _dragSources = new(); - private readonly Dictionary _dropTargets = new(); - private readonly Window _window; + private readonly Dictionary _dragSources = new(); + private readonly Dictionary _dropTargets = new(); private bool _disposed; #endregion #region Конструктор - /// - /// Инициализирует новый экземпляр. - /// public WinUIDragDropIntegrationService( - Window window, IDragDropService dragDropService, IDragVisualProvider dragVisualProvider, IDragDropHost dragDropHost) { - _window = window ?? throw new ArgumentNullException(nameof(window)); _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); _dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider)); _dragDropHost = dragDropHost ?? throw new ArgumentNullException(nameof(dragDropHost)); - - InitializeEvents(); } #endregion @@ -302,9 +223,9 @@ public sealed class WinUIDragDropIntegrationService : IDisposable /// /// Регистрирует элемент как источник перетаскивания. /// - public void RegisterDragSource(UIElement element, Func> dragInfoFactory) + public void RegisterDragSource(FrameworkElement element, Func dragInfoFactory) { - ThrowIfDisposed(); + if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); ArgumentNullException.ThrowIfNull(element); ArgumentNullException.ThrowIfNull(dragInfoFactory); @@ -317,39 +238,130 @@ public sealed class WinUIDragDropIntegrationService : IDisposable /// /// Регистрирует элемент как источник перетаскивания с данными. /// - public void RegisterDragSource(UIElement element, object dragData) + public void RegisterDragSource(FrameworkElement element, object dragData) { - ThrowIfDisposed(); - ArgumentNullException.ThrowIfNull(element); - ArgumentNullException.ThrowIfNull(dragData); - - if (_dragSources.ContainsKey(element)) return; - - var adapter = new DragSourceAdapter(element, dragData, _dragDropService); - _dragSources[element] = adapter; + RegisterDragSource(element, () => + { + var position = GetScreenPosition(element, new Point(0, 0)); + return new DragInfo( + dragData, + Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, + position, + element + ); + }); } /// /// Регистрирует элемент как цель сброса. /// - public void RegisterDropTarget(UIElement element, IDropTarget dropTarget) + public void RegisterDropTarget(FrameworkElement element) { - ThrowIfDisposed(); + if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); ArgumentNullException.ThrowIfNull(element); - ArgumentNullException.ThrowIfNull(dropTarget); if (_dropTargets.ContainsKey(element)) return; - var adapter = new DropTargetAdapter(element, dropTarget, _dragDropService); - _dropTargets[element] = adapter; + var behavior = new WinUIDropTargetBehavior(ServiceProviderHelper.GetServiceProvider()) + { + AssociatedElement = element + }; + + _dropTargets[element] = behavior; + + // Настраиваем события WinUI + element.AllowDrop = true; + element.DragEnter += OnDragEnter; + element.DragOver += OnDragOver; + element.DragLeave += OnDragLeave; + element.Drop += OnDrop; + } + + private Point GetScreenPosition(FrameworkElement element, Point relativePoint) + { + var transform = element.TransformToVisual(null); + var point = transform.TransformPoint(new Windows.Foundation.Point(relativePoint.X, relativePoint.Y)); + return new Point(point.X, point.Y); + } + + private void OnDragEnter(object sender, DragEventArgs e) + { + if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) + { + var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint()); + var dropInfo = CreateDropInfo(e, position, element); + + if (behavior.CanAcceptDrop(dropInfo)) + { + e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; + behavior.DragOver(dropInfo); + } + else + { + e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None; + } + } + } + + private void OnDragOver(object sender, DragEventArgs e) + { + if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) + { + var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint()); + var dropInfo = CreateDropInfo(e, position, element); + + if (behavior.CanAcceptDrop(dropInfo)) + { + e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; + behavior.DragOver(dropInfo); + } + } + } + + private void OnDragLeave(object sender, DragEventArgs e) + { + if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) + { + behavior.DragLeave(); + } + } + + private void OnDrop(object sender, DragEventArgs e) + { + if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) + { + var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint()); + var dropInfo = CreateDropInfo(e, position, element); + behavior.Drop(dropInfo); + } + } + + private DropInfo CreateDropInfo(DragEventArgs e, Point position, FrameworkElement target) + { + // Извлекаем данные из DragEventArgs + object? data = null; + + // В реальной реализации нужно извлечь данные из e.DataView + // Это упрощенная версия + if (e.DataView.Properties.TryGetValue("DragData", out var dragData)) + { + data = dragData; + } + + return new DropInfo( + data: data, + position: position, + allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, + target: target + ); } /// /// Удаляет регистрацию элемента как источника. /// - public void UnregisterDragSource(UIElement element) + public void UnregisterDragSource(FrameworkElement element) { - ThrowIfDisposed(); + if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); if (_dragSources.Remove(element, out var adapter)) { @@ -360,122 +372,38 @@ public sealed class WinUIDragDropIntegrationService : IDisposable /// /// Удаляет регистрацию элемента как цели. /// - public void UnregisterDropTarget(UIElement element) + public void UnregisterDropTarget(FrameworkElement element) { - ThrowIfDisposed(); + if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); - if (_dropTargets.Remove(element, out var adapter)) + if (_dropTargets.Remove(element, out var behavior)) { - adapter.Dispose(); + behavior.Detach(); + element.AllowDrop = false; + element.DragEnter -= OnDragEnter; + element.DragOver -= OnDragOver; + element.DragLeave -= OnDragLeave; + element.Drop -= OnDrop; } } #endregion - #region Обработка событий - - private void InitializeEvents() - { - _dragDropService.DragStarted += OnDragStarted; - _dragDropService.DragUpdated += OnDragUpdated; - _dragDropService.DragCompleted += OnDragCompleted; - _dragDropService.DragCancelled += OnDragCancelled; - _dragDropService.ErrorOccurred += OnErrorOccurred; - } - - private void UnsubscribeFromEvents() - { - _dragDropService.DragStarted -= OnDragStarted; - _dragDropService.DragUpdated -= OnDragUpdated; - _dragDropService.DragCompleted -= OnDragCompleted; - _dragDropService.DragCancelled -= OnDragCancelled; - _dragDropService.ErrorOccurred -= OnErrorOccurred; - } - - private void OnDragStarted(object? sender, DragStartedEventArgs e) - { - try - { - var visual = _dragVisualProvider.CreateDragVisual(e.DragInfo, e.StartPosition); - _dragDropHost.ShowDragVisual(visual, e.StartPosition); - } - catch (Exception ex) - { - Debug.WriteLine($"Error in OnDragStarted: {ex.Message}"); - } - } - - private void OnDragUpdated(object? sender, DragUpdatedEventArgs e) - { - try - { - // Обновление визуала будет через хост - } - catch (Exception ex) - { - Debug.WriteLine($"Error in OnDragUpdated: {ex.Message}"); - } - } - - private void OnDragCompleted(object? sender, DragCompletedEventArgs e) - { - try - { - _dragDropHost.HideDragVisual(null!); // В реальности нужно передать конкретный визуал - } - catch (Exception ex) - { - Debug.WriteLine($"Error in OnDragCompleted: {ex.Message}"); - } - } - - private void OnDragCancelled(object? sender, DragCancelledEventArgs e) - { - try - { - _dragDropHost.HideDragVisual(null!); - } - catch (Exception ex) - { - Debug.WriteLine($"Error in OnDragCancelled: {ex.Message}"); - } - } - - private void OnErrorOccurred(object? sender, DragDropErrorEventArgs e) - { - Debug.WriteLine($"DragDrop error in {e.Operation}: {e.Exception.Message}"); - } - - #endregion - - #region Вспомогательные методы - - private void ThrowIfDisposed() - { - if (_disposed) - throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); - } - - #endregion - #region IDisposable - /// public void Dispose() { if (_disposed) return; - UnsubscribeFromEvents(); - foreach (var adapter in _dragSources.Values) { adapter.Dispose(); } _dragSources.Clear(); - foreach (var adapter in _dropTargets.Values) + foreach (var behavior in _dropTargets.Values) { - adapter.Dispose(); + behavior.Detach(); } _dropTargets.Clear(); @@ -484,4 +412,15 @@ public sealed class WinUIDragDropIntegrationService : IDisposable } #endregion +} + +/// +/// Методы расширения для преобразования координат. +/// +internal static class PointExtensions +{ + public static Point ToCorePoint(this Windows.Foundation.Point point) + { + return new Point(point.X, point.Y); + } } \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropHost.cs b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropHost.cs new file mode 100644 index 0000000..cddd3c6 --- /dev/null +++ b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropHost.cs @@ -0,0 +1,72 @@ +using Lattice.Core.Geometry; +using Lattice.UI.DragDrop.Abstractions; +using Lattice.UI.DragDrop.WinUI.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; + +namespace Lattice.UI.DragDrop.WinUI.Services; + +public class WinUIDragDropHost : IDragDropHost +{ + private readonly DragDropOverlay _overlay; + private readonly Window _window; + + public WinUIDragDropHost(Window window) + { + _window = window ?? throw new ArgumentNullException(nameof(window)); + _overlay = new DragDropOverlay(); + + // Добавляем оверлей в окно + if (_window.Content is Panel panel) + { + panel.Children.Add(_overlay); + } + } + + public void ShowDragVisual(object dragVisual, Point position) + { + if (dragVisual is UIElement element) + { + _overlay.ShowDragVisual(element, position.X, position.Y); + } + } + + public void UpdateDragVisualPosition(object dragVisual, Point position) + { + if (dragVisual is UIElement element) + { + _overlay.UpdateDragVisualPosition(element, position.X, position.Y); + } + } + + public void HideDragVisual(object dragVisual) + { + if (dragVisual is UIElement element) + { + _overlay.HideDragVisual(element); + } + else + { + // Скрываем все, если передан null + var current = _overlay.GetCurrentDragVisual(); + if (current != null) + { + _overlay.HideDragVisual(current); + } + } + } + + public void ShowDropAdorner(IDropVisualAdorner adorner) + { + if (adorner is DropPreviewAdorner dropAdorner) + { + // TODO: Показываем превью сброса + } + } + + public void HideDropAdorner(IDropVisualAdorner adorner) + { + _overlay.HideAllDropPreviews(); + } +} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragVisualProvider.cs b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragVisualProvider.cs index b2ac7ff..64baeb0 100644 --- a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragVisualProvider.cs +++ b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragVisualProvider.cs @@ -2,45 +2,22 @@ using Lattice.Core.Geometry; using Lattice.UI.DragDrop.Abstractions; using Lattice.UI.DragDrop.WinUI.Controls; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using System; namespace Lattice.UI.DragDrop.WinUI.Services; -/// -/// Поставщик визуального представления для WinUI. -/// public class WinUIDragVisualProvider : IDragVisualProvider { - private readonly ResourceDictionary _resources; private DragAdorner? _currentAdorner; - /// - /// Инициализирует новый экземпляр класса . - /// - /// Ресурсы для стилей. - public WinUIDragVisualProvider(ResourceDictionary resources) - { - _resources = resources ?? throw new ArgumentNullException(nameof(resources)); - } - - /// public object CreateDragVisual(DragInfo dragInfo, Point initialPosition) { - // Создаем новый DragAdorner + // Создаем DragAdorner на основе данных _currentAdorner = new DragAdorner { DragData = dragInfo.Data, OpacityLevel = 0.8 }; - // Применяем стиль из ресурсов, если есть - if (_resources.ContainsKey("DragAdornerStyle")) - { - _currentAdorner.Style = _resources["DragAdornerStyle"] as Style; - } - // Настраиваем начальную позицию _currentAdorner.UpdatePosition(initialPosition); _currentAdorner.Show(); @@ -48,7 +25,6 @@ public class WinUIDragVisualProvider : IDragVisualProvider return _currentAdorner; } - /// public void UpdateDragVisualPosition(object dragVisual, Point position) { if (dragVisual is DragAdorner adorner) @@ -57,31 +33,12 @@ public class WinUIDragVisualProvider : IDragVisualProvider } } - /// public void ReleaseDragVisual(object dragVisual) { if (dragVisual is DragAdorner adorner) { adorner.Hide(); - - // Отложенное удаление после анимации - var timer = new DispatcherTimer - { - Interval = TimeSpan.FromMilliseconds(150) - }; - - timer.Tick += (s, e) => - { - timer.Stop(); - if (adorner.Parent is Panel panel) - { - panel.Children.Remove(adorner); - } - }; - - timer.Start(); + _currentAdorner = null; } - - _currentAdorner = null; } } \ No newline at end of file diff --git a/Lattice.UI.DragDrop/Behaviors/DragSourceBehaviorBase.cs b/Lattice.UI.DragDrop/Behaviors/DragSourceBehaviorBase.cs index b9a77ee..d21c917 100644 --- a/Lattice.UI.DragDrop/Behaviors/DragSourceBehaviorBase.cs +++ b/Lattice.UI.DragDrop/Behaviors/DragSourceBehaviorBase.cs @@ -4,6 +4,8 @@ using Lattice.Core.DragDrop.Services; using Lattice.Core.Geometry; using Microsoft.Extensions.DependencyInjection; using System; +using System.Threading; +using System.Threading.Tasks; namespace Lattice.UI.DragDrop.Behaviors; @@ -103,7 +105,7 @@ public abstract class DragSourceBehaviorBase : IDragSource /// Обрабатывает начало взаимодействия (например, нажатие мыши). /// /// Позиция в координатах элемента. - protected virtual void OnInteractionStarted(Point position) + protected virtual async Task OnInteractionStarted(Point position) { if (_isDragging) return; @@ -115,7 +117,7 @@ public abstract class DragSourceBehaviorBase : IDragSource /// Обрабатывает перемещение во время взаимодействия. /// /// Позиция в координатах элемента. - protected virtual void OnInteractionMoved(Point position) + protected virtual async Task OnInteractionMoved(Point position) { if (_isDragging) return; @@ -123,14 +125,14 @@ public abstract class DragSourceBehaviorBase : IDragSource var distance = CalculateDistance(_dragStartPosition, position); if (distance > DragDropService.DragStartThreshold) { - StartDragOperation(); + await StartDragOperation(); } } /// /// Обрабатывает завершение взаимодействия. /// - protected virtual void OnInteractionEnded() + protected virtual async Task OnInteractionEnded() { // Сброс состояния, если перетаскивание не началось if (!_isDragging) @@ -142,11 +144,11 @@ public abstract class DragSourceBehaviorBase : IDragSource /// /// Обрабатывает отмену взаимодействия. /// - protected virtual void OnInteractionCancelled() + protected virtual async Task OnInteractionCancelled() { if (_isDragging) { - DragDropService.CancelDrag(); + await DragDropService.CancelDragAsync(); } Reset(); } @@ -154,7 +156,7 @@ public abstract class DragSourceBehaviorBase : IDragSource /// /// Начинает операцию перетаскивания. /// - protected virtual void StartDragOperation() + protected virtual async Task StartDragOperation() { if (_isDragging || AssociatedElement == null) return; @@ -163,7 +165,7 @@ public abstract class DragSourceBehaviorBase : IDragSource var screenPosition = ConvertToScreenCoordinates(_dragStartPosition); // Начинаем перетаскивание - _isDragging = DragDropService.StartDrag(this, screenPosition); + _isDragging = await DragDropService.StartDragAsync(this, screenPosition); } /// @@ -195,30 +197,25 @@ public abstract class DragSourceBehaviorBase : IDragSource #region IDragSource Implementation /// - public abstract bool CanStartDrag(out DragInfo? dragInfo); + public abstract Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync(CancellationToken ct = default); /// - public virtual bool StartDrag(DragInfo dragInfo) + public virtual async Task StartDragAsync(DragInfo dragInfo, CancellationToken ct = default) { - // Базовая реализация всегда разрешает начало перетаскивания return true; } /// - public virtual void DragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects) + public virtual async Task DragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects, CancellationToken ct = default) { _isDragging = false; - - // Оповещаем о завершении перетаскивания OnDragCompleted(dragInfo, effects); } /// - public virtual void DragCancelled(DragInfo dragInfo) + public virtual async Task DragCancelledAsync(DragInfo dragInfo, CancellationToken ct = default) { _isDragging = false; - - // Оповещаем об отмене перетаскивания OnDragCancelled(dragInfo); } diff --git a/Lattice.UI.DragDrop/Behaviors/DropTargetBehaviorBase.cs b/Lattice.UI.DragDrop/Behaviors/DropTargetBehaviorBase.cs index a38dad3..19fa0ae 100644 --- a/Lattice.UI.DragDrop/Behaviors/DropTargetBehaviorBase.cs +++ b/Lattice.UI.DragDrop/Behaviors/DropTargetBehaviorBase.cs @@ -4,6 +4,8 @@ using Lattice.Core.DragDrop.Services; using Lattice.Core.Geometry; using Microsoft.Extensions.DependencyInjection; using System; +using System.Threading; +using System.Threading.Tasks; namespace Lattice.UI.DragDrop.Behaviors; @@ -176,15 +178,27 @@ public abstract class DropTargetBehaviorBase : IDropTarget #region IDropTarget Implementation /// - public abstract bool CanAcceptDrop(DropInfo dropInfo); + public abstract Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default); /// - public virtual void DragOver(DropInfo dropInfo) + public virtual async Task DragOverAsync(DropInfo dropInfo, CancellationToken ct = default) { // Базовая реализация устанавливает эффект по умолчанию - if (CanAcceptDrop(dropInfo)) + if (await CanAcceptDropAsync(dropInfo)) { - dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Move; + // Установить эффект по умолчанию, если он разрешен + if (dropInfo.CanAcceptEffect(Core.DragDrop.Enums.DragDropEffects.Move)) + { + dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Move; + } + else if (dropInfo.CanAcceptEffect(Core.DragDrop.Enums.DragDropEffects.Copy)) + { + dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Copy; + } + else if (dropInfo.CanAcceptEffect(Core.DragDrop.Enums.DragDropEffects.Link)) + { + dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Link; + } } else { @@ -193,10 +207,10 @@ public abstract class DropTargetBehaviorBase : IDropTarget } /// - public abstract void Drop(DropInfo dropInfo); + public abstract Task DropAsync(DropInfo dropInfo, CancellationToken ct = default); /// - public virtual void DragLeave() + public virtual async Task DragLeaveAsync(CancellationToken ct = default) { // Базовая реализация не делает ничего }