From be1215426288812697b385d22122db138e3678ff Mon Sep 17 00:00:00 2001 From: FrigaT Date: Sun, 25 Jan 2026 02:37:16 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BD=D1=8B=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=D1=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Abstractions/IDragSource.cs | 81 +- .../Abstractions/IDropTarget.cs | 81 +- .../Enums/DragDropEffects.cs | 13 + .../Extensions/DropInfoExtensions.cs | 31 +- .../Extensions/ServiceCollectionExtensions.cs | 85 -- Lattice.Core.DragDrop/Models/DropInfo.cs | 12 + .../Services/DragDropService.cs | 210 +++-- .../EventArgs/DragCancelledEventArgs.cs | 22 - .../EventArgs/DragCompletedEventArgs.cs | 35 - .../EventArgs/DragDropErrorEventArgs.cs | 32 - .../Services/EventArgs/DragEventArgs.cs | 237 ++++++ .../EventArgs/DragStartedEventArgs.cs | 29 - .../EventArgs/DragUpdatedEventArgs.cs | 29 - .../EventArgs/DropTargetChangedEventArgs.cs | 35 - .../Services/IDragDropService.cs | 132 +++- .../Utilities/AsyncDragDropUtilities.cs | 225 ------ Lattice.UI.DragDrop.WinUI/README.md | 724 ++++++------------ 17 files changed, 897 insertions(+), 1116 deletions(-) delete mode 100644 Lattice.Core.DragDrop/Extensions/ServiceCollectionExtensions.cs delete mode 100644 Lattice.Core.DragDrop/Services/EventArgs/DragCancelledEventArgs.cs delete mode 100644 Lattice.Core.DragDrop/Services/EventArgs/DragCompletedEventArgs.cs delete mode 100644 Lattice.Core.DragDrop/Services/EventArgs/DragDropErrorEventArgs.cs create mode 100644 Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs delete mode 100644 Lattice.Core.DragDrop/Services/EventArgs/DragStartedEventArgs.cs delete mode 100644 Lattice.Core.DragDrop/Services/EventArgs/DragUpdatedEventArgs.cs delete mode 100644 Lattice.Core.DragDrop/Services/EventArgs/DropTargetChangedEventArgs.cs delete mode 100644 Lattice.Core.DragDrop/Utilities/AsyncDragDropUtilities.cs diff --git a/Lattice.Core.DragDrop/Abstractions/IDragSource.cs b/Lattice.Core.DragDrop/Abstractions/IDragSource.cs index 65822e9..989b274 100644 --- a/Lattice.Core.DragDrop/Abstractions/IDragSource.cs +++ b/Lattice.Core.DragDrop/Abstractions/IDragSource.cs @@ -5,57 +5,80 @@ /// в операции перетаскивания. /// /// +/// /// Объекты, реализующие этот интерфейс, могут инициировать операции перетаскивания /// и предоставлять данные для передачи другим элементам через механизм drag-and-drop. +/// +/// +/// Интерфейс полностью асинхронный и поддерживает отмену операций через CancellationToken. +/// Все методы должны быть потокобезопасными и поддерживать вызов из любого потока. +/// /// public interface IDragSource { /// - /// Определяет, может ли объект начать операцию перетаскивания. + /// Пытается начать операцию перетаскивания из указанной позиции. /// + /// Начальная позиция операции в координатах экрана. + /// Токен отмены операции. /// - /// Кортеж, содержащий флаг возможности начала перетаскивания и информацию о перетаскивании. + /// Информация о перетаскивании, если операция может быть начата; в противном случае — null. + /// Возвращаемый объект должен быть полностью инициализирован, + /// включая данные, разрешенные эффекты и ссылку на источник. /// /// - /// Этот метод вызывается системой перетаскивания для проверки возможности - /// начала операции. Если метод возвращает true, он должен заполнить - /// DragInfo необходимыми данными. + /// + /// Этот метод вызывается сервисом перетаскивания при попытке начать операцию + /// (обычно при нажатии и перемещении мыши). Метод должен проверить, может ли + /// источник начать перетаскивание в текущем контексте. + /// + /// + /// Реализация должна быть быстрой и не выполнять длительных операций. + /// Если подготовка данных требует времени, ее следует выполнить асинхронно + /// после подтверждения возможности начала. + /// /// - Task<(bool CanStart, Models.DragInfo? DragInfo)> CanStartDragAsync(CancellationToken cancellationToken = default); + Task TryStartDragAsync(Geometry.Point startPosition, CancellationToken cancellationToken = default); /// - /// Начинает операцию перетаскивания. + /// Уведомляет источник о завершении операции перетаскивания. /// - /// Информация о перетаскивании. - /// - /// true, если операция перетаскивания успешно начата; в противном случае — false. - /// - /// - /// Этот метод вызывается, когда пользователь начинает перетаскивание элемента. - /// Реализация должна подготовить данные для перетаскивания и, возможно, - /// создать визуальное представление перетаскиваемого объекта. - /// - Task StartDragAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default); - - /// - /// Вызывается при завершении операции перетаскивания. - /// - /// Исходная информация о перетаскивании. + /// Информация о перетаскивании, полученная при начале операции. /// Эффекты, которые были применены при сбросе. + /// Токен отмены операции. + /// Задача, представляющая асинхронную операцию. /// + /// /// Этот метод вызывается после завершения операции перетаскивания - /// (успешного или неуспешного). Реализация может выполнить очистку - /// или обновить состояние на основе результата операции. + /// (успешного или неуспешного). Реализация может: + /// + /// + /// Выполнить очистку ресурсов, связанных с операцией + /// Обновить состояние на основе результата (например, удалить данные при перемещении) + /// Отобразить визуальную обратную связь о результате + /// + /// + /// Если операция завершилась с эффектом , + /// источник обычно должен удалить или обновить исходные данные. + /// /// - Task DragCompletedAsync(Models.DragInfo dragInfo, Enums.DragDropEffects effects, CancellationToken cancellationToken = default); + Task OnDragCompletedAsync(Models.DragInfo dragInfo, Enums.DragDropEffects effects, CancellationToken cancellationToken = default); /// - /// Вызывается при отмене операции перетаскивания. + /// Уведомляет источник об отмене операции перетаскивания. /// - /// Исходная информация о перетаскивании. + /// Информация о перетаскивании, полученная при начале операции. + /// Токен отмены операции. + /// Задача, представляющая асинхронную операцию. /// + /// /// Этот метод вызывается, когда операция перетаскивания была отменена - /// пользователем (например, нажатием клавиши Escape). + /// пользователем (например, нажатием клавиши Escape) или системой. + /// + /// + /// Реализация должна выполнить очистку и восстановить исходное состояние. + /// Обычно это включает освобождение ресурсов и сброс визуальных индикаторов. + /// /// - Task DragCancelledAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default); + Task OnDragCancelledAsync(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 b3eb5f5..5a15f2d 100644 --- a/Lattice.Core.DragDrop/Abstractions/IDropTarget.cs +++ b/Lattice.Core.DragDrop/Abstractions/IDropTarget.cs @@ -5,8 +5,15 @@ /// в операции перетаскивания. /// /// +/// /// Объекты, реализующие этот интерфейс, могут обрабатывать данные, сброшенные /// пользователем, и предоставлять визуальную обратную связь во время перетаскивания. +/// +/// +/// Интерфейс поддерживает асинхронные операции и отмену через CancellationToken. +/// Все методы должны быть потокобезопасными и идемпотентными (многократный вызов +/// с одинаковыми параметрами должен давать одинаковый результат). +/// /// public interface IDropTarget { @@ -14,42 +21,98 @@ public interface IDropTarget /// Определяет, может ли объект принять сбрасываемые данные. /// /// Информация о потенциальном сбросе. + /// Токен отмены операции. /// /// true, если объект может принять данные; в противном случае — false. /// /// + /// /// Этот метод вызывается, когда перетаскиваемый объект находится над целью. /// Реализация должна проверить, совместимы ли данные с целью, и установить - /// предлагаемые эффекты в . + /// предлагаемые эффекты в свойстве . + /// + /// + /// Метод может вызываться многократно при перемещении курсора над целью. + /// Реализация должна быть эффективной и избегать длительных операций. + /// + /// + /// Если метод возвращает false, система не будет вызывать другие методы + /// для этой цели до тех пор, пока курсор не покинет ее область. + /// /// Task CanAcceptDropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); /// - /// Вызывается, когда перетаскиваемый объект находится над целью. + /// Вызывается, когда перетаскиваемый объект перемещается над целью. /// /// Информация о текущем положении перетаскивания. + /// Токен отмены операции. + /// Задача, представляющая асинхронную операцию. /// + /// /// Этот метод вызывается постоянно, пока пользователь перемещает объект над целью. - /// Реализация может обновить визуальную обратную связь или изменить предлагаемые эффекты. + /// Реализация может: + /// + /// + /// Обновить визуальную обратную связь (подсветка, изменение курсора) + /// Вычислить точную позицию сброса (например, между элементами списка) + /// Уточнить предлагаемые эффекты на основе текущей позиции + /// Прокрутить содержимое, если цель поддерживает прокрутку + /// + /// + /// Метод должен быть оптимизирован для частого вызова. Длительные операции + /// должны выполняться асинхронно без блокировки потока. + /// /// - Task DragOverAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); + Task OnDragOverAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); /// /// Вызывается, когда пользователь сбрасывает данные на цель. /// - /// Информация о сбросе. + /// Информация о сбросе, включая данные и позицию. + /// Токен отмены операции. + /// Задача, представляющая асинхронную операцию. /// + /// /// Этот метод вызывается, когда пользователь отпускает кнопку мыши над целью. - /// Реализация должна обработать принятие данных и выполнить соответствующее действие. + /// Реализация должна обработать принятие данных и выполнить соответствующее действие: + /// + /// + /// Добавить данные в коллекцию (для копирования) + /// Переместить данные (при поддержке перемещения) + /// Создать ссылку на данные + /// Выполнить пользовательскую логику обработки + /// + /// + /// После успешной обработки данных следует вызвать , + /// чтобы указать системе, что операция обработана и дополнительная обработка не требуется. + /// + /// + /// Если операция завершилась успешно, система уведомит источник через + /// с соответствующими эффектами. + /// /// - Task DropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); + Task OnDropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); /// /// Вызывается, когда перетаскиваемый объект покидает область цели. /// + /// Токен отмены операции. + /// Задача, представляющая асинхронную операцию. /// + /// /// Этот метод вызывается, когда пользователь перемещает объект за пределы цели. - /// Реализация должна очистить любую визуальную обратную связь, установленную ранее. + /// Реализация должна: + /// + /// + /// Очистить любую визуальную обратную связь, установленную ранее + /// Сбросить временное состояние, связанное с операцией + /// Освободить ресурсы, выделенные для предварительного просмотра + /// + /// + /// Метод гарантированно вызывается после любого успешного или неуспешного + /// вызова , если курсор покидает область цели. + /// /// - Task DragLeaveAsync(CancellationToken cancellationToken = default); + Task OnDragLeaveAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Enums/DragDropEffects.cs b/Lattice.Core.DragDrop/Enums/DragDropEffects.cs index 9fcc970..705232a 100644 --- a/Lattice.Core.DragDrop/Enums/DragDropEffects.cs +++ b/Lattice.Core.DragDrop/Enums/DragDropEffects.cs @@ -54,6 +54,9 @@ public static class DragDropEffectsExtensions /// /// Проверяет, содержит ли эффекты указанный эффект. /// + /// Эффекты для проверки. + /// Эффект для поиска. + /// true, если эффект присутствует; в противном случае — false. public static bool HasEffect(this DragDropEffects effects, DragDropEffects effect) { return (effects & effect) == effect; @@ -62,6 +65,8 @@ public static class DragDropEffectsExtensions /// /// Проверяет, содержат ли эффекты копирование. /// + /// Эффекты для проверки. + /// true, если разрешено копирование; в противном случае — false. public static bool CanCopy(this DragDropEffects effects) { return effects.HasEffect(DragDropEffects.Copy); @@ -70,6 +75,8 @@ public static class DragDropEffectsExtensions /// /// Проверяет, содержат ли эффекты перемещение. /// + /// Эффекты для проверки. + /// true, если разрешено перемещение; в противном случае — false. public static bool CanMove(this DragDropEffects effects) { return effects.HasEffect(DragDropEffects.Move); @@ -78,6 +85,8 @@ public static class DragDropEffectsExtensions /// /// Проверяет, содержат ли эффекты ссылку. /// + /// Эффекты для проверки. + /// true, если разрешена ссылка; в противном случае — false. public static bool CanLink(this DragDropEffects effects) { return effects.HasEffect(DragDropEffects.Link); @@ -86,6 +95,10 @@ public static class DragDropEffectsExtensions /// /// Получает наиболее подходящий эффект на основе модификаторов клавиатуры. /// + /// Нажата ли клавиша Control. + /// Нажата ли клавиша Shift. + /// Нажата ли клавиша Alt. + /// Наиболее подходящий эффект перетаскивания. public static DragDropEffects GetEffectFromKeys(bool controlKey, bool shiftKey, bool altKey) { if (controlKey && shiftKey) diff --git a/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs b/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs index f362db6..a6e9c55 100644 --- a/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs +++ b/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs @@ -1,4 +1,7 @@ -namespace Lattice.Core.DragDrop.Extensions; +using Lattice.Core.DragDrop.Enums; +using Lattice.Core.Geometry; + +namespace Lattice.Core.DragDrop.Extensions; /// /// Методы расширения для DropInfo. @@ -8,6 +11,9 @@ public static class DropInfoExtensions /// /// Проверяет, могут ли данные быть приведены к указанному типу. /// + /// Тип данных для проверки. + /// Информация о сбросе. + /// true, если данные могут быть приведены к типу T; в противном случае — false. public static bool CanAccept(this Models.DropInfo dropInfo) where T : class { @@ -17,6 +23,9 @@ public static class DropInfoExtensions /// /// Пытается получить данные как указанный тип. /// + /// Тип, к которому нужно привести данные. + /// Информация о сбросе. + /// Данные как тип T или null. public static T? GetDataAs(this Models.DropInfo dropInfo) where T : class { @@ -26,6 +35,10 @@ public static class DropInfoExtensions /// /// Получает данные как указанный тип или выбрасывает исключение. /// + /// Тип, к которому нужно привести данные. + /// Информация о сбросе. + /// Данные как тип T. + /// Выбрасывается, если данные не могут быть приведены к типу T. public static T GetRequiredDataAs(this Models.DropInfo dropInfo) where T : class { @@ -40,8 +53,22 @@ public static class DropInfoExtensions /// /// Проверяет, содержится ли позиция в указанных границах. /// - public static bool IsInBounds(this Models.DropInfo dropInfo, Geometry.Rect bounds) + /// Информация о сбросе. + /// Границы для проверки. + /// true, если позиция находится в границах; в противном случае — false. + public static bool IsInBounds(this Models.DropInfo dropInfo, Rect bounds) { return bounds.Contains(dropInfo.Position); } + + /// + /// Проверяет можно ли добавить эффект перетаскивания. + /// + /// + /// + /// + public static bool CanAcceptEffect(this Models.DropInfo dropInfo, DragDropEffects effect) + { + return dropInfo.AllowedEffects.HasEffect(effect); + } } \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Extensions/ServiceCollectionExtensions.cs b/Lattice.Core.DragDrop/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index d6401ce..0000000 --- a/Lattice.Core.DragDrop/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,85 +0,0 @@ -namespace Lattice.Core.DragDrop.Extensions; - -/// -/// Методы расширения для регистрации сервисов перетаскивания. -/// -public static class ServiceCollectionExtensions -{ - /// - /// Добавляет сервис перетаскивания. - /// - /// Коллекция сервисов. - /// Коллекция сервисов. - /// - /// Реализация DI должна быть предоставлена конкретным приложением. - /// - public static object AddDragDropService(this object serviceCollection) - { - // Реализация регистрации сервиса должна быть в конкретном приложении - // Это абстрактный метод для поддержки DI без зависимостей - return serviceCollection; - } - - /// - /// Добавляет сервис перетаскивания с конфигурацией. - /// - /// Коллекция сервисов. - /// Действие конфигурации. - /// Коллекция сервисов. - public static object AddDragDropService( - this object serviceCollection, - Action configure) - { - var options = new DragDropServiceOptions(); - configure(options); - - // Реализация регистрации с опциями должна быть в конкретном приложении - return serviceCollection; - } -} - -/// -/// Опции конфигурации сервиса перетаскивания. -/// -public class DragDropServiceOptions -{ - /// - /// Порог начала перетаскивания в пикселях. - /// - public double DragStartThreshold { get; set; } = 3.0; - - /// - /// Включить ведение журнала операций. - /// - public bool EnableLogging { get; set; } = false; - - /// - /// Включить автоматическую очистку неиспользуемых целей. - /// - public bool EnableAutoCleanup { get; set; } = true; - - /// - /// Интервал автоматической очистки в миллисекундах. - /// - public int AutoCleanupInterval { get; set; } = 60000; - - /// - /// Включить асинхронную обработку операций. - /// - public bool EnableAsyncOperations { get; set; } = true; - - /// - /// Время ожидания асинхронных операций в миллисекундах. - /// - public int AsyncOperationTimeout { get; set; } = 5000; - - /// - /// Включить сбор статистики. - /// - public bool EnableStatistics { get; set; } = true; - - /// - /// Включить проверку типов данных. - /// - public bool EnableTypeChecking { get; set; } = true; -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Models/DropInfo.cs b/Lattice.Core.DragDrop/Models/DropInfo.cs index 1ee05fb..66dd2f6 100644 --- a/Lattice.Core.DragDrop/Models/DropInfo.cs +++ b/Lattice.Core.DragDrop/Models/DropInfo.cs @@ -30,8 +30,20 @@ namespace Lattice.Core.DragDrop.Models; public class DropInfo { private DragDropEffects _effects = DragDropEffects.None; + + /// + /// Получает или задает позицию сброса относительно цели. + /// public DropPosition DropPosition { get; set; } = DropPosition.Inside; + + /// + /// Получает или задает значение, указывающее, нужно ли показывать визуальную обратную связь. + /// public bool ShowVisualFeedback { get; set; } = true; + + /// + /// Получает или задает данные для визуальной обратной связи. + /// public object? VisualFeedbackData { get; set; } /// diff --git a/Lattice.Core.DragDrop/Services/DragDropService.cs b/Lattice.Core.DragDrop/Services/DragDropService.cs index f0a9b99..fab0243 100644 --- a/Lattice.Core.DragDrop/Services/DragDropService.cs +++ b/Lattice.Core.DragDrop/Services/DragDropService.cs @@ -1,13 +1,52 @@ -namespace Lattice.Core.DragDrop.Services; +using Lattice.Core.DragDrop.Abstractions; +using Lattice.Core.DragDrop.Constants; +using Lattice.Core.DragDrop.Enums; +using Lattice.Core.DragDrop.Models; +using Lattice.Core.Geometry; + +namespace Lattice.Core.DragDrop.Services; /// -/// Реализация сервиса управления операциями перетаскивания. -/// Полностью потокобезопасная реализация с поддержкой async/await. +/// Центральный сервис управления операциями перетаскивания. /// +/// +/// +/// является основным компонентом системы drag-and-drop, +/// который координирует взаимодействие между источниками данных () +/// и целями сброса (). +/// +/// +/// Основные функции сервиса: +/// +/// +/// Регистрация и управление целями сброса +/// Оркестрация жизненного цикла операций перетаскивания +/// Обработка событий мыши и клавиатуры +/// Распространение информации между компонентами +/// Обеспечение потокобезопасности операций +/// +/// +/// Сервис поддерживает полностью асинхронную модель работы, уведомления через события +/// и статистику использования. Все операции защищены от параллельного доступа +/// и обеспечивают корректную очистку ресурсов. +/// +/// +/// Для использования сервиса необходимо: +/// +/// Зарегистрировать цели сброса с помощью +/// Вызывать методы , , +/// в ответ на действия пользователя +/// Подписаться на события для отслеживания состояния операций +/// +/// +/// public sealed class DragDropService : IDragDropService { #region Nested Types + /// + /// Информация о зарегистрированной цели сброса. + /// private sealed class DropTargetInfo : IDisposable { public required Abstractions.IDropTarget Target { get; init; } @@ -25,6 +64,9 @@ public sealed class DragDropService : IDragDropService } } + /// + /// Контекст текущей операции перетаскивания. + /// private sealed class DragOperationContext : IDisposable { public Abstractions.IDragSource? Source { get; set; } @@ -33,6 +75,7 @@ public sealed class DragDropService : IDragDropService public CancellationTokenSource? CancellationTokenSource { get; set; } public DateTime StartTime { get; set; } = DateTime.UtcNow; public bool ThresholdExceeded { get; set; } + public Point LastPosition { get; set; } public void Dispose() { @@ -74,48 +117,60 @@ public sealed class DragDropService : IDragDropService #region Properties + /// public bool IsDragActive => Volatile.Read(ref _currentDragOperation) != null; + /// public Models.DragInfo? CurrentDragInfo => _currentDragOperation?.DragInfo; + /// public Abstractions.IDropTarget? CurrentDropTarget => _currentDragOperation?.CurrentDropTarget; - public double DragStartThreshold { get; set; } = 3.0; + /// + public double DragStartThreshold { get; set; } = DragDropConstants.DefaultDragThreshold; + /// public bool EnableAsyncOperations { get; set; } = true; - public int AsyncOperationTimeout { get; set; } = 5000; + /// + public int AsyncOperationTimeout { get; set; } = DragDropConstants.DefaultAsyncTimeout; + /// public event EventHandler DragStarted { add => _dragStarted += value; remove => _dragStarted -= value; } + /// public event EventHandler DragUpdated { add => _dragUpdated += value; remove => _dragUpdated -= value; } + /// public event EventHandler DropTargetChanged { add => _dropTargetChanged += value; remove => _dropTargetChanged -= value; } + /// public event EventHandler DragCompleted { add => _dragCompleted += value; remove => _dragCompleted -= value; } + /// public event EventHandler DragCancelled { add => _dragCancelled += value; remove => _dragCancelled -= value; } + /// public event EventHandler ErrorOccurred { add => _errorOccurred += value; @@ -126,6 +181,17 @@ public sealed class DragDropService : IDragDropService #region Constructor + /// + /// Инициализирует новый экземпляр класса . + /// + /// + /// Создает сервис с настройками по умолчанию: + /// + /// Порог начала перетаскивания: пикселей + /// Таймаут асинхронных операций: миллисекунд + /// Включены асинхронные операции: true + /// + /// public DragDropService() { // Инициализация таймера очистки (каждые 5 минут) @@ -138,6 +204,7 @@ public sealed class DragDropService : IDragDropService #region Registration Methods + /// public string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null) { ThrowIfDisposed(); @@ -165,6 +232,7 @@ public sealed class DragDropService : IDragDropService } } + /// public bool UpdateDropTargetBounds(string id, Geometry.Rect bounds) { ThrowIfDisposed(); @@ -193,6 +261,7 @@ public sealed class DragDropService : IDragDropService } } + /// public bool UnregisterDropTarget(string id) { ThrowIfDisposed(); @@ -213,6 +282,7 @@ public sealed class DragDropService : IDragDropService } } + /// public void UnregisterDropTargetsInGroup(string group) { ThrowIfDisposed(); @@ -249,7 +319,8 @@ public sealed class DragDropService : IDragDropService #region Async Operations - public async Task StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition) + /// + public async Task StartDragAsync(IDragSource source, Point startPosition) { ThrowIfDisposed(); if (source == null) throw new ArgumentNullException(nameof(source)); @@ -264,60 +335,34 @@ public sealed class DragDropService : IDragDropService { Interlocked.Increment(ref _totalDragOperations); - Models.DragInfo? dragInfo = null; + DragInfo? dragInfo; - // Проверка возможности начала перетаскивания - if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource) + // Пытаемся начать перетаскивание + if (EnableAsyncOperations) { - var result = await ExecuteWithTimeoutAsync( - asyncSource.CanStartDragAsync(), - "CanStartDragAsync", + dragInfo = await ExecuteWithTimeoutAsync( + source.TryStartDragAsync(startPosition), + "TryStartDragAsync", source); - - if (!result.CanStart || result.DragInfo == null) - return false; - - dragInfo = result.DragInfo; } else { - var startDragResult = await source.CanStartDragAsync(); - if (!startDragResult.CanStart || startDragResult.DragInfo == null) - return false; - - dragInfo = startDragResult.DragInfo; + dragInfo = await source.TryStartDragAsync(startPosition); } + if (dragInfo == null) + return false; + var updatedDragInfo = dragInfo.CloneWithPosition(startPosition); - // Начало перетаскивания - bool started; - - if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource2) - { - started = await ExecuteWithTimeoutAsync( - asyncSource2.StartDragAsync(updatedDragInfo), - "StartDragAsync", - source); - } - else - { - started = await source.StartDragAsync(updatedDragInfo); - } - - if (!started) - { - updatedDragInfo.Dispose(); - return false; - } - lock (_dragOperationLock) { _currentDragOperation = new DragOperationContext { Source = source, DragInfo = updatedDragInfo, - CancellationTokenSource = new CancellationTokenSource() + CancellationTokenSource = new CancellationTokenSource(), + LastPosition = startPosition }; } @@ -333,7 +378,8 @@ public sealed class DragDropService : IDragDropService } } - public async Task UpdateDragAsync(Geometry.Point position) + /// + public async Task UpdateDragAsync(Point position) { ThrowIfDisposed(); @@ -359,8 +405,9 @@ public sealed class DragDropService : IDragDropService } var updatedDragInfo = context.DragInfo.CloneWithPosition(position); - context.DragInfo = updatedDragInfo; context.DragInfo.Dispose(); + context.DragInfo = updatedDragInfo; + context.LastPosition = position; // Поиск новой цели сброса var newDropTarget = await FindDropTargetAsync(position, updatedDragInfo); @@ -372,9 +419,8 @@ public sealed class DragDropService : IDragDropService { await ExecuteTargetOperationAsync( context.CurrentDropTarget, - t => t.DragLeaveAsync(), - t => t.DragLeaveAsync(), - "DragLeave"); + t => t.OnDragLeaveAsync(), + "OnDragLeave"); } context.CurrentDropTarget = newDropTarget?.Target; @@ -383,14 +429,14 @@ public sealed class DragDropService : IDragDropService { newDropTarget.UsageCount++; _dropTargetChanged?.Invoke(this, new DropTargetChangedEventArgs( - updatedDragInfo, newDropTarget.Target, newDropTarget.Bounds)); + updatedDragInfo, position, newDropTarget.Target, newDropTarget.Bounds)); } } // Уведомление текущей цели if (context.CurrentDropTarget != null) { - var dropInfo = new Models.DropInfo( + var dropInfo = new DropInfo( updatedDragInfo.Data, position, updatedDragInfo.AllowedEffects, @@ -398,9 +444,8 @@ public sealed class DragDropService : IDragDropService await ExecuteTargetOperationAsync( context.CurrentDropTarget, - t => t.DragOverAsync(dropInfo), - t => t.DragOverAsync(dropInfo), - "DragOver"); + t => t.OnDragOverAsync(dropInfo), + "OnDragOver"); } _dragUpdated?.Invoke(this, new DragUpdatedEventArgs(updatedDragInfo, position)); @@ -412,7 +457,8 @@ public sealed class DragDropService : IDragDropService } } - public async Task EndDragAsync(Geometry.Point position) + /// + public async Task EndDragAsync(Point position) { ThrowIfDisposed(); @@ -426,19 +472,19 @@ public sealed class DragDropService : IDragDropService if (context == null || context.DragInfo == null || context.Source == null) { Reset(); - return Enums.DragDropEffects.None; + return DragDropEffects.None; } try { - var effects = Enums.DragDropEffects.None; + var effects = DragDropEffects.None; var operationTime = DateTime.UtcNow - context.StartTime; Interlocked.Add(ref _totalOperationTicks, operationTime.Ticks); // Выполнение сброса if (context.CurrentDropTarget != null) { - var dropInfo = new Models.DropInfo( + var dropInfo = new DropInfo( context.DragInfo.Data, position, context.DragInfo.AllowedEffects, @@ -446,9 +492,8 @@ public sealed class DragDropService : IDragDropService await ExecuteTargetOperationAsync( context.CurrentDropTarget, - t => t.DropAsync(dropInfo), - t => t.DropAsync(dropInfo), - "Drop"); + t => t.OnDropAsync(dropInfo), + "OnDrop"); if (dropInfo.Handled) { @@ -460,10 +505,8 @@ public sealed class DragDropService : IDragDropService // Уведомление источника await ExecuteSourceOperationAsync( context.Source, - s => s.DragCompletedAsync(context.DragInfo, effects), - s => s.DragCompletedAsync(context.DragInfo, effects), - "DragCompleted", - effects); + s => s.OnDragCompletedAsync(context.DragInfo, effects), + "OnDragCompleted"); // Событие завершения _dragCompleted?.Invoke(this, new DragCompletedEventArgs( @@ -475,7 +518,7 @@ public sealed class DragDropService : IDragDropService { Interlocked.Increment(ref _errorCount); HandleError(ex, "EndDragAsync", context); - return Enums.DragDropEffects.None; + return DragDropEffects.None; } finally { @@ -483,6 +526,7 @@ public sealed class DragDropService : IDragDropService } } + /// public async Task CancelDragAsync() { ThrowIfDisposed(); @@ -507,11 +551,10 @@ public sealed class DragDropService : IDragDropService await ExecuteSourceOperationAsync( context.Source, - s => s.DragCancelledAsync(context.DragInfo), - s => s.DragCancelledAsync(context.DragInfo), - "DragCancelled"); + s => s.OnDragCancelledAsync(context.DragInfo), + "OnDragCancelled"); - _dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo)); + _dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo, context.LastPosition)); } catch (Exception ex) { @@ -528,6 +571,7 @@ public sealed class DragDropService : IDragDropService #region Utility Methods + /// public void ClearAllDropTargets() { ThrowIfDisposed(); @@ -547,6 +591,7 @@ public sealed class DragDropService : IDragDropService } } + /// public DragDropStats GetStats() { return new DragDropStats @@ -609,23 +654,22 @@ public sealed class DragDropService : IDragDropService } private async Task ExecuteTargetOperationAsync( - Abstractions.IDropTarget target, - Func asyncOperation, - Action syncOperation, + IDropTarget target, + Func operation, string operationName) { try { - if (EnableAsyncOperations && target is Abstractions.IDropTarget asyncTarget) + if (EnableAsyncOperations) { await ExecuteWithTimeoutAsync( - asyncOperation(asyncTarget), + operation(target), $"{operationName}Async", target); } else { - syncOperation(target); + await operation(target); } } catch (Exception ex) @@ -636,24 +680,22 @@ public sealed class DragDropService : IDragDropService } private async Task ExecuteSourceOperationAsync( - Abstractions.IDragSource source, - Func asyncOperation, - Action syncOperation, - string operationName, - Enums.DragDropEffects effects = Enums.DragDropEffects.None) + IDragSource source, + Func operation, + string operationName) { try { - if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource) + if (EnableAsyncOperations) { await ExecuteWithTimeoutAsync( - asyncOperation(asyncSource), + operation(source), $"{operationName}Async", source); } else { - syncOperation(source); + await operation(source); } } catch (Exception ex) diff --git a/Lattice.Core.DragDrop/Services/EventArgs/DragCancelledEventArgs.cs b/Lattice.Core.DragDrop/Services/EventArgs/DragCancelledEventArgs.cs deleted file mode 100644 index 495d904..0000000 --- a/Lattice.Core.DragDrop/Services/EventArgs/DragCancelledEventArgs.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Lattice.Core.DragDrop.Models; - -namespace Lattice.Core.DragDrop.Services; - -/// -/// Аргументы события отмены перетаскивания. -/// -public class DragCancelledEventArgs : EventArgs -{ - /// - /// Информация о перетаскивании. - /// - public DragInfo DragInfo { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - public DragCancelledEventArgs(DragInfo dragInfo) - { - DragInfo = dragInfo; - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Services/EventArgs/DragCompletedEventArgs.cs b/Lattice.Core.DragDrop/Services/EventArgs/DragCompletedEventArgs.cs deleted file mode 100644 index bc1a747..0000000 --- a/Lattice.Core.DragDrop/Services/EventArgs/DragCompletedEventArgs.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Lattice.Core.DragDrop.Models; -using Lattice.Core.Geometry; - -namespace Lattice.Core.DragDrop.Services; - -/// -/// Аргументы события завершения перетаскивания. -/// -public class DragCompletedEventArgs : EventArgs -{ - /// - /// Информация о перетаскивании. - /// - public DragInfo DragInfo { get; } - - /// - /// Позиция завершения перетаскивания. - /// - public Point DropPosition { get; } - - /// - /// Примененные эффекты перетаскивания. - /// - public Enums.DragDropEffects Effects { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - public DragCompletedEventArgs(DragInfo dragInfo, Point dropPosition, Enums.DragDropEffects effects) - { - DragInfo = dragInfo; - DropPosition = dropPosition; - Effects = effects; - } -} diff --git a/Lattice.Core.DragDrop/Services/EventArgs/DragDropErrorEventArgs.cs b/Lattice.Core.DragDrop/Services/EventArgs/DragDropErrorEventArgs.cs deleted file mode 100644 index 4a7ce23..0000000 --- a/Lattice.Core.DragDrop/Services/EventArgs/DragDropErrorEventArgs.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Lattice.Core.DragDrop.Services; - -/// -/// Аргументы события ошибки в операции перетаскивания. -/// -public class DragDropErrorEventArgs : EventArgs -{ - /// - /// Ошибка, которая произошла. - /// - public Exception Exception { get; } - - /// - /// Операция, во время которой произошла ошибка. - /// - public string Operation { get; } - - /// - /// Контекст операции. - /// - public object? Context { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - public DragDropErrorEventArgs(Exception exception, string operation, object? context = null) - { - Exception = exception; - Operation = operation; - Context = context; - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs b/Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs new file mode 100644 index 0000000..1138456 --- /dev/null +++ b/Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs @@ -0,0 +1,237 @@ +using Lattice.Core.DragDrop.Models; +using Lattice.Core.Geometry; + +namespace Lattice.Core.DragDrop.Services; + +/// +/// Предоставляет базовые данные для событий перетаскивания. +/// +/// +/// Этот класс содержит общие свойства, которые используются в большинстве событий +/// системы перетаскивания. Является базовым классом для специализированных событий. +/// +public abstract class DragEventArgs : EventArgs +{ + /// + /// Получает информацию о текущей операции перетаскивания. + /// + /// + /// Объект , содержащий данные, эффекты и метаданные операции. + /// Всегда возвращает актуальную информацию на момент возникновения события. + /// + public DragInfo DragInfo { get; } + + /// + /// Получает текущую позицию курсора в координатах экрана. + /// + /// + /// Точка, представляющая положение курсора мыши в момент события. + /// Используется для точного позиционирования и визуальной обратной связи. + /// + public Point Position { get; } + + /// + /// Инициализирует новый экземпляр класса . + /// + /// Информация о перетаскивании. + /// Текущая позиция курсора. + /// + /// Выбрасывается, когда равен null. + /// + protected DragEventArgs(DragInfo dragInfo, Point position) + { + DragInfo = dragInfo ?? throw new ArgumentNullException(nameof(dragInfo)); + Position = position; + } +} + +/// +/// Предоставляет данные для события начала перетаскивания. +/// +/// +/// Возникает, когда пользователь начинает операцию перетаскивания. +/// Это первое событие в жизненном цикле операции. +/// +public sealed class DragStartedEventArgs : DragEventArgs +{ + /// + /// Инициализирует новый экземпляр класса . + /// + /// Информация о перетаскивании. + /// Начальная позиция перетаскивания. + public DragStartedEventArgs(DragInfo dragInfo, Point position) + : base(dragInfo, position) + { + } +} + +/// +/// Предоставляет данные для события обновления позиции перетаскивания. +/// +/// +/// Возникает при каждом перемещении курсора во время операции перетаскивания. +/// Может вызываться многократно с высокой частотой, поэтому обработчики +/// должны быть оптимизированы для производительности. +/// +public sealed class DragUpdatedEventArgs : DragEventArgs +{ + /// + /// Инициализирует новый экземпляр класса . + /// + /// Информация о перетаскивании. + /// Текущая позиция курсора. + public DragUpdatedEventArgs(DragInfo dragInfo, Point position) + : base(dragInfo, position) + { + } +} + +/// +/// Предоставляет данные для события завершения перетаскивания. +/// +/// +/// Возникает, когда пользователь завершает операцию перетаскивания +/// (отпускает кнопку мыши над целью или вне области сброса). +/// Содержит информацию о примененных эффектах и результатах операции. +/// +public sealed class DragCompletedEventArgs : DragEventArgs +{ + /// + /// Получает эффекты, примененные при завершении операции. + /// + /// + /// Комбинация флагов , указывающая, + /// как были обработаны данные (копирование, перемещение и т.д.). + /// + public Enums.DragDropEffects Effects { get; } + + /// + /// Инициализирует новый экземпляр класса . + /// + /// Информация о перетаскивании. + /// Позиция завершения операции. + /// Примененные эффекты перетаскивания. + public DragCompletedEventArgs(DragInfo dragInfo, Point position, Enums.DragDropEffects effects) + : base(dragInfo, position) + { + Effects = effects; + } +} + +/// +/// Предоставляет данные для события отмены перетаскивания. +/// +/// +/// Возникает, когда операция перетаскивания была отменена пользователем +/// (например, нажатием клавиши Escape) или системой (например, при ошибке). +/// После этого события система возвращается в исходное состояние. +/// +public sealed class DragCancelledEventArgs : DragEventArgs +{ + /// + /// Инициализирует новый экземпляр класса . + /// + /// Информация о перетаскивании. + /// Позиция в момент отмены. + public DragCancelledEventArgs(DragInfo dragInfo, Point position) + : base(dragInfo, position) + { + } +} + +/// +/// Предоставляет данные для события изменения цели сброса. +/// +/// +/// Возникает, когда курсор перемещается с одной цели сброса на другую +/// или покидает область всех целей. Позволяет обновлять визуальную +/// обратную связь при изменении контекста сброса. +/// +public sealed class DropTargetChangedEventArgs : DragEventArgs +{ + /// + /// Получает новую цель сброса, над которой находится курсор. + /// + /// + /// Объект , готовый принять данные, + /// или null, если курсор покинул область всех целей. + /// + public Abstractions.IDropTarget? Target { get; } + + /// + /// Получает границы новой цели сброса. + /// + /// + /// Прямоугольник, определяющий область цели в координатах экрана. + /// Может использоваться для точного позиционирования визуальной обратной связи. + /// + public Rect TargetBounds { get; } + + /// + /// Инициализирует новый экземпляр класса . + /// + /// Информация о перетаскивании. + /// Текущая позиция курсора. + /// Новая цель сброса. + /// Границы цели сброса. + public DropTargetChangedEventArgs(DragInfo dragInfo, Point position, Abstractions.IDropTarget? target, Rect targetBounds) + : base(dragInfo, position) + { + Target = target; + TargetBounds = targetBounds; + } +} + +/// +/// Предоставляет данные для события ошибки в операции перетаскивания. +/// +/// +/// Возникает при возникновении исключения в любом из компонентов +/// системы перетаскивания. Позволяет централизованно обрабатывать ошибки +/// и предоставлять пользователю информацию о проблемах. +/// +public sealed class DragDropErrorEventArgs : EventArgs +{ + /// + /// Получает исключение, вызвавшее ошибку. + /// + /// + /// Объект , содержащий информацию об ошибке. + /// Может быть любого типа, в зависимости от источника ошибки. + /// + public Exception Exception { get; } + + /// + /// Получает название операции, во время которой произошла ошибка. + /// + /// + /// Строка, идентифицирующая операцию (например, "StartDragAsync", + /// "OnDropAsync", "UpdateDropTargetBounds"). + /// + public string Operation { get; } + + /// + /// Получает контекст, в котором произошла ошибка. + /// + /// + /// Объект, содержащий дополнительную информацию о контексте ошибки, + /// или null, если контекст недоступен. + /// + public object? Context { get; } + + /// + /// Инициализирует новый экземпляр класса . + /// + /// Исключение, вызвавшее ошибку. + /// Название операции. + /// Контекст ошибки. + /// + /// Выбрасывается, когда или равны null. + /// + public DragDropErrorEventArgs(Exception exception, string operation, object? context = null) + { + Exception = exception ?? throw new ArgumentNullException(nameof(exception)); + Operation = operation ?? throw new ArgumentNullException(nameof(operation)); + Context = context; + } +} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Services/EventArgs/DragStartedEventArgs.cs b/Lattice.Core.DragDrop/Services/EventArgs/DragStartedEventArgs.cs deleted file mode 100644 index 85386b7..0000000 --- a/Lattice.Core.DragDrop/Services/EventArgs/DragStartedEventArgs.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Lattice.Core.DragDrop.Models; -using Lattice.Core.Geometry; - -namespace Lattice.Core.DragDrop.Services; - -/// -/// Аргументы события начала перетаскивания. -/// -public class DragStartedEventArgs : EventArgs -{ - /// - /// Информация о перетаскивании. - /// - public DragInfo DragInfo { get; } - - /// - /// Начальная позиция перетаскивания. - /// - public Point StartPosition { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - public DragStartedEventArgs(DragInfo dragInfo, Point startPosition) - { - DragInfo = dragInfo; - StartPosition = startPosition; - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Services/EventArgs/DragUpdatedEventArgs.cs b/Lattice.Core.DragDrop/Services/EventArgs/DragUpdatedEventArgs.cs deleted file mode 100644 index 504c505..0000000 --- a/Lattice.Core.DragDrop/Services/EventArgs/DragUpdatedEventArgs.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Lattice.Core.DragDrop.Models; -using Lattice.Core.Geometry; - -namespace Lattice.Core.DragDrop.Services; - -/// -/// Аргументы события обновления перетаскивания. -/// -public class DragUpdatedEventArgs : EventArgs -{ - /// - /// Информация о перетаскивании. - /// - public DragInfo DragInfo { get; } - - /// - /// Текущая позиция перетаскивания. - /// - public Point Position { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - public DragUpdatedEventArgs(DragInfo dragInfo, Point position) - { - DragInfo = dragInfo; - Position = position; - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Services/EventArgs/DropTargetChangedEventArgs.cs b/Lattice.Core.DragDrop/Services/EventArgs/DropTargetChangedEventArgs.cs deleted file mode 100644 index 9003fe9..0000000 --- a/Lattice.Core.DragDrop/Services/EventArgs/DropTargetChangedEventArgs.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Lattice.Core.DragDrop.Models; -using Lattice.Core.Geometry; - -namespace Lattice.Core.DragDrop.Services; - -/// -/// Аргументы события изменения цели сброса. -/// -public class DropTargetChangedEventArgs : EventArgs -{ - /// - /// Информация о перетаскивании. - /// - public DragInfo DragInfo { get; } - - /// - /// Новая цель сброса. - /// - public Abstractions.IDropTarget Target { get; } - - /// - /// Границы цели. - /// - public Rect TargetBounds { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - public DropTargetChangedEventArgs(DragInfo dragInfo, Abstractions.IDropTarget target, Rect targetBounds) - { - DragInfo = dragInfo; - Target = target; - TargetBounds = targetBounds; - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Services/IDragDropService.cs b/Lattice.Core.DragDrop/Services/IDragDropService.cs index e8220d9..2440076 100644 --- a/Lattice.Core.DragDrop/Services/IDragDropService.cs +++ b/Lattice.Core.DragDrop/Services/IDragDropService.cs @@ -8,33 +8,51 @@ public interface IDragDropService : IDisposable #region Свойства /// - /// Активна ли операция перетаскивания. + /// Получает значение, указывающее, активна ли операция перетаскивания. /// + /// true, если операция перетаскивания активна; в противном случае — false. bool IsDragActive { get; } /// - /// Информация о текущей операции. + /// Получает информацию о текущей операции перетаскивания. /// + /// + /// Объект , содержащий данные текущей операции, + /// или null, если операция не активна. + /// Models.DragInfo? CurrentDragInfo { get; } /// - /// Текущая цель сброса. + /// Получает текущую цель сброса. /// + /// + /// Объект , над которым находится курсор, + /// или null, если курсор не над зарегистрированной целью. + /// Abstractions.IDropTarget? CurrentDropTarget { get; } /// - /// Порог начала перетаскивания в пикселях. + /// Получает или задает порог начала перетаскивания в пикселях. /// + /// + /// Минимальное расстояние, которое должен пройти курсор мыши, чтобы начать операцию перетаскивания. + /// Значение по умолчанию: . + /// double DragStartThreshold { get; set; } /// - /// Включены ли асинхронные операции. + /// Получает или задает значение, указывающее, включены ли асинхронные операции. /// + /// true, если асинхронные операции включены; в противном случае — false. bool EnableAsyncOperations { get; set; } /// - /// Максимальное время ожидания асинхронной операции (мс). + /// Получает или задает максимальное время ожидания асинхронной операции в миллисекундах. /// + /// + /// Время ожидания в миллисекундах. Значение 0 или меньше означает отсутствие таймаута. + /// Значение по умолчанию: . + /// int AsyncOperationTimeout { get; set; } #endregion @@ -42,32 +60,32 @@ public interface IDragDropService : IDisposable #region События /// - /// Событие начала операции перетаскивания. + /// Происходит при начале операции перетаскивания. /// event EventHandler DragStarted; /// - /// Событие обновления позиции перетаскивания. + /// Происходит при обновлении позиции перетаскивания. /// event EventHandler DragUpdated; /// - /// Событие изменения цели сброса. + /// Происходит при изменении цели сброса. /// event EventHandler DropTargetChanged; /// - /// Событие завершения операции перетаскивания. + /// Происходит при завершении операции перетаскивания. /// event EventHandler DragCompleted; /// - /// Событие отмены операции перетаскивания. + /// Происходит при отмене операции перетаскивания. /// event EventHandler DragCancelled; /// - /// Событие ошибки в операции перетаскивания. + /// Происходит при возникновении ошибки в операции перетаскивания. /// event EventHandler ErrorOccurred; @@ -76,23 +94,39 @@ public interface IDragDropService : IDisposable #region Регистрация целей сброса /// - /// Регистрирует цель сброса. + /// Регистрирует цель сброса в системе. /// + /// Цель сброса для регистрации. + /// Границы области цели в координатах экрана. + /// Приоритет цели (высшие значения обрабатываются первыми). + /// Имя группы для групповой отмены регистрации. + /// Уникальный идентификатор зарегистрированной цели. + /// Выбрасывается, когда равен null. + /// Выбрасывается, если сервис был удален. string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null); /// /// Обновляет границы цели сброса. /// + /// Идентификатор цели сброса. + /// Новые границы области цели. + /// true, если границы успешно обновлены; в противном случае — false. + /// Выбрасывается, если сервис был удален. bool UpdateDropTargetBounds(string id, Geometry.Rect bounds); /// /// Отменяет регистрацию цели сброса. /// + /// Идентификатор цели сброса. + /// true, если цель успешно удалена; в противном случае — false. + /// Выбрасывается, если сервис был удален. bool UnregisterDropTarget(string id); /// - /// Отменяет регистрацию всех целей в группе. + /// Отменяет регистрацию всех целей сброса в указанной группе. /// + /// Имя группы для удаления. + /// Выбрасывается, если сервис был удален. void UnregisterDropTargetsInGroup(string group); #endregion @@ -100,23 +134,54 @@ public interface IDragDropService : IDisposable #region Асинхронные операции /// - /// Начинает операцию перетаскивания (асинхронно). + /// Начинает операцию перетаскивания из указанной позиции. /// + /// Источник данных для перетаскивания. + /// Начальная позиция операции в координатах экрана. + /// + /// Задача, представляющая асинхронную операцию. Результат содержит true, если операция успешно начата; + /// в противном случае — false. + /// + /// Выбрасывается, когда равен null. + /// Выбрасывается, если сервис был удален. + /// + /// Этот метод следует вызывать в ответ на событие нажатия кнопки мыши или начала жеста перетаскивания. + /// Task StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition); /// - /// Обновляет позицию перетаскивания (асинхронно). + /// Обновляет позицию текущей операции перетаскивания. /// + /// Новая позиция курсора в координатах экрана. + /// Задача, представляющая асинхронную операцию. + /// Выбрасывается, если сервис был удален. + /// + /// Этот метод следует вызывать при каждом перемещении мыши во время операции перетаскивания. + /// Task UpdateDragAsync(Geometry.Point position); /// - /// Завершает операцию перетаскивания (асинхронно). + /// Завершает текущую операцию перетаскивания в указанной позиции. /// + /// Позиция завершения операции в координатах экрана. + /// + /// Задача, представляющая асинхронную операцию. Результат содержит эффекты, примененные при завершении операции. + /// + /// Выбрасывается, если сервис был удален. + /// + /// Этот метод следует вызывать при отпускании кнопки мыши или завершении жеста перетаскивания. + /// Task EndDragAsync(Geometry.Point position); /// - /// Отменяет операцию перетаскивания (асинхронно). + /// Отменяет текущую операцию перетаскивания. /// + /// Задача, представляющая асинхронную операцию. + /// Выбрасывается, если сервис был удален. + /// + /// Этот метод следует вызывать при отмене операции пользователем (например, нажатием клавиши Escape) + /// или при возникновении ошибки. + /// Task CancelDragAsync(); #endregion @@ -124,27 +189,52 @@ public interface IDragDropService : IDisposable #region Утилиты /// - /// Очищает все зарегистрированные цели. + /// Очищает все зарегистрированные цели сброса. /// + /// Выбрасывается, если сервис был удален. void ClearAllDropTargets(); /// - /// Получает статистику использования. + /// Получает статистику использования системы перетаскивания. /// + /// Объект со статистикой использования. DragDropStats GetStats(); #endregion } /// -/// Статистика использования Drag & Drop. +/// Содержит статистику использования системы перетаскивания. /// public class DragDropStats { + /// + /// Получает или задает общее количество операций перетаскивания. + /// public int TotalDragOperations { get; set; } + + /// + /// Получает или задает количество успешных сбросов. + /// public int SuccessfulDrops { get; set; } + + /// + /// Получает или задает количество отмененных операций. + /// public int CancelledOperations { get; set; } + + /// + /// Получает или задает количество ошибок. + /// public int ErrorCount { get; set; } + + /// + /// Получает или задает количество зарегистрированных целей сброса. + /// public int RegisteredTargets { get; set; } + + /// + /// Получает или задает среднее время операции перетаскивания. + /// public TimeSpan AverageOperationTime { get; set; } } \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Utilities/AsyncDragDropUtilities.cs b/Lattice.Core.DragDrop/Utilities/AsyncDragDropUtilities.cs deleted file mode 100644 index 0e31e45..0000000 --- a/Lattice.Core.DragDrop/Utilities/AsyncDragDropUtilities.cs +++ /dev/null @@ -1,225 +0,0 @@ -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Enums; -using Lattice.Core.DragDrop.Models; - -namespace Lattice.Core.DragDrop.Utilities; - -/// -/// Предоставляет утилитарные методы и фабричные методы для работы с системой перетаскивания с поддержкой async. -/// -public static class AsyncDragDropUtilities -{ - /// - /// Создает асинхронную реализацию источника перетаскивания. - /// - public static IDragSource CreateAsyncDragSource( - Func> dataProviderAsync, - Func>? canDragAsync = null, - Func? onCompletedAsync = null, - Func? onCancelledAsync = null) - { - return new AsyncDragSourceWrapper(dataProviderAsync, canDragAsync, onCompletedAsync, onCancelledAsync); - } - - /// - /// Создает асинхронную реализацию цели сброса. - /// - public static IDropTarget CreateAsyncDropTarget( - Func>? canAcceptAsync = null, - Func? onDragOverAsync = null, - Func? onDropAsync = null, - Func? onDragLeaveAsync = null) - { - return new AsyncDropTargetWrapper(canAcceptAsync, onDragOverAsync, onDropAsync, onDragLeaveAsync); - } - - - #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; - } - #endregion - - #region Обертки-реализации - - /// - /// Обертка для создания асинхронного источника перетаскивания. - /// - private sealed class AsyncDragSourceWrapper : IDragSource - { - private readonly Func> _dataProviderAsync; - private readonly Func>? _canDragAsync; - private readonly Func? _onCompletedAsync; - private readonly Func? _onCancelledAsync; - - public AsyncDragSourceWrapper( - Func> dataProviderAsync, - Func>? canDragAsync = null, - Func? onCompletedAsync = null, - Func? onCancelledAsync = null) - { - _dataProviderAsync = dataProviderAsync ?? throw new ArgumentNullException(nameof(dataProviderAsync)); - _canDragAsync = canDragAsync; - _onCompletedAsync = onCompletedAsync; - _onCancelledAsync = onCancelledAsync; - } - - public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync(CancellationToken cancellationToken = default) - { - try - { - // Проверяем, может ли начаться перетаскивание - var canDrag = _canDragAsync != null ? await _canDragAsync().ConfigureAwait(false) : true; - if (!canDrag) - return (false, null); - - // Получаем данные - var data = await _dataProviderAsync().ConfigureAwait(false); - if (data == null) - return (false, null); - - // Создаем информацию о перетаскивании - var dragInfo = CreateDragInfo( - data, - Geometry.Point.Zero, - DragDropEffects.Copy | DragDropEffects.Move, - this); - - return (true, dragInfo); - } - catch - { - return (false, null); - } - } - - public Task StartDragAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) - { - // Базовая реализация всегда разрешает начало - return Task.FromResult(true); - } - - public async Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken cancellationToken = default) - { - if (_onCompletedAsync != null) - { - await _onCompletedAsync(dragInfo, effects).ConfigureAwait(false); - } - } - - public async Task DragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) - { - if (_onCancelledAsync != null) - { - await _onCancelledAsync(dragInfo).ConfigureAwait(false); - } - } - } - - /// - /// Обертка для создания асинхронной цели сброса. - /// - private sealed class AsyncDropTargetWrapper : IDropTarget - { - private readonly Func>? _canAcceptAsync; - private readonly Func? _onDragOverAsync; - private readonly Func? _onDropAsync; - private readonly Func? _onDragLeaveAsync; - - public AsyncDropTargetWrapper( - Func>? canAcceptAsync = null, - Func? onDragOverAsync = null, - Func? onDropAsync = null, - Func? onDragLeaveAsync = null) - { - _canAcceptAsync = canAcceptAsync; - _onDragOverAsync = onDragOverAsync; - _onDropAsync = onDropAsync; - _onDragLeaveAsync = onDragLeaveAsync; - } - - public async Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - try - { - if (_canAcceptAsync != null) - { - return await _canAcceptAsync(dropInfo).ConfigureAwait(false); - } - return true; // По умолчанию принимаем все - } - catch - { - return false; // При ошибке не принимаем - } - } - - public async Task DragOverAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - try - { - if (_onDragOverAsync != null) - { - await _onDragOverAsync(dropInfo).ConfigureAwait(false); - } - } - catch - { - // Игнорируем ошибки в обработчике - } - } - - public async Task DropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - try - { - if (_onDropAsync != null) - { - await _onDropAsync(dropInfo).ConfigureAwait(false); - } - } - catch - { - // Игнорируем ошибки в обработчике - } - } - - public async Task DragLeaveAsync(CancellationToken cancellationToken = default) - { - try - { - if (_onDragLeaveAsync != null) - { - await _onDragLeaveAsync().ConfigureAwait(false); - } - } - catch - { - // Игнорируем ошибки в обработчике - } - } - } - - #endregion - -} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/README.md b/Lattice.UI.DragDrop.WinUI/README.md index 0941339..25661af 100644 --- a/Lattice.UI.DragDrop.WinUI/README.md +++ b/Lattice.UI.DragDrop.WinUI/README.md @@ -1,547 +1,313 @@ -# Lattice.UI.DragDrop.WinUI +# Lattice.Core.DragDrop -![Lattice Framework](https://img.shields.io/badge/Lattice-UI%20Framework-blueviolet) -![WinUI 3](https://img.shields.io/badge/WinUI-3.0%2B-blue) -![Windows 10+](https://img.shields.io/badge/Windows-10%2B-success) -![Version](https://img.shields.io/badge/version-1.0.0-green) -![License](https://img.shields.io/badge/license-MIT-blue) +Библиотека для реализации drag-and-drop (перетаскивания) в приложениях на .NET. -Полнофункциональная реализация системы перетаскивания для WinUI 3 в составе Lattice UI Framework. +## 📋 Обзор -## 🎉 Демо +Lattice.Core.DragDrop предоставляет полнофункциональную, асинхронную и потокобезопасную систему для реализации операций перетаскивания в пользовательских интерфейсах. Библиотека построена на принципах разделения ответственности и поддерживает сложные сценарии перетаскивания с минимальными усилиями со стороны разработчика. -![Drag & Drop Demo](https://raw.githubusercontent.com/lattice-framework/ui-dragdrop/main/docs/demo.gif) +### ✨ Основные возможности -*Перетаскивание элементов между контейнерами и переупорядочивание списка* +- ✅ **Полностью асинхронный API** - все операции поддерживают async/await +- ✅ **Потокобезопасность** - безопасная работа в многопоточных средах +- ✅ **Расширяемая архитектура** - легко добавлять новые типы источников и целей +- ✅ **Подробные события** - полный контроль над жизненным циклом операций +- ✅ **Статистика и мониторинг** - встроенный сбор метрик использования +- ✅ **Поддержка CancellationToken** - корректная отмена длительных операций +- ✅ **Независимость от UI-фреймворков** - может использоваться с любым представлением -## 📦 Особенности +## 🏗️ Архитектура -✅ **Готовое решение для WinUI 3** - работает из коробки -✅ **Attached Behaviors** - легко подключается к любым UIElement -✅ **Визуальная обратная связь** - анимации и подсветка -✅ **Переупорядочивание элементов** - drag-and-drop в списках -✅ **Кастомизация стилей** - полный контроль над внешним видом -✅ **Поддержка сложных сценариев** - вложенные элементы, зоны сброса -✅ **Производительность** - оптимизировано для плавной работы +### Основные компоненты + +#### 1. **IDragSource** +Интерфейс для объектов, которые могут быть источником данных при перетаскивании. Определяет: +- Возможность начала перетаскивания +- Подготовку данных для передачи +- Реакцию на завершение или отмену операции + +#### 2. **IDropTarget** +Интерфейс для объектов, которые могут принимать сброшенные данные. Определяет: +- Проверку совместимости данных +- Визуальную обратную связь при наведении +- Обработку сброшенных данных + +#### 3. **DragDropService** +Центральный сервис, координирующий все операции. Отвечает за: +- Регистрацию и управление целями сброса +- Оркестрацию жизненного цикла операций +- Распространение событий между компонентами +- Сбор статистики и обработку ошибок + +#### 4. **Модели данных** +- **DragInfo** - информация о начале перетаскивания +- **DropInfo** - информация о потенциальном сбросе +- **DragDropEffects** - перечисление возможных эффектов ## 🚀 Быстрый старт ### 1. Установка -Добавьте пакет через NuGet: - -```powershell -Install-Package Lattice.UI.DragDrop.WinUI +```csharp +// Пример регистрации в DI-контейнере +services.AddSingleton(); ``` -Или через Package Manager: - -```xml - -``` - -### 2. Инициализация в приложении +### 2. Создание источника перетаскивания ```csharp -using Lattice.UI.DragDrop.WinUI.Extensions; -using Lattice.UI.DragDrop.WinUI.Helpers; - -public MainWindow() +public class ItemDragSource : IDragSource { - InitializeComponent(); - - // Инициализация ресурсов - ResourceHelper.InitializeDragDropResources(); - - // Настройка примеров перетаскивания - SetupDragDropExamples(); -} + private readonly Item _item; -private void SetupDragDropExamples() -{ - // Пример 1: Перетаскивание текста - var textBlock = new TextBlock { Text = "Перетащи меня" }; - textBlock.MakeDragSource("Пример данных"); - - // Пример 2: Цель сброса - var border = new Border { Background = new SolidColorBrush(Colors.LightGray) }; - border.MakeDropTarget(typeof(string)); - - // Пример 3: Стилизация - var button = new Button { Content = "Кнопка" }; - button.ApplyDragStyle(); - button.EnableDragVisualFeedback(); -} -``` - -## 🎨 Использование - -### Базовое перетаскивание - -```csharp -// Создаем перетаскиваемый элемент -var dragSource = new Border -{ - Background = new SolidColorBrush(Colors.LightBlue), - Child = new TextBlock { Text = "Drag me" } -}; - -// Делаем элемент перетаскиваемым -dragSource.MakeDragSource(dragSource); // Можно передать любые данные - -// Создаем цель сброса -var dropTarget = new Border -{ - Background = new SolidColorBrush(Colors.LightGreen) -}; - -// Делаем элемент целью сброса -dropTarget.MakeDropTarget(typeof(Border)); -``` - -### Переупорядочивание элементов в списке - -```csharp -// Создаем контейнер с поддержкой переупорядочивания -var reorderContainer = new StackPanel(); -reorderContainer.MakeDropTarget(typeof(UIElement)); - -// Добавляем перетаскиваемые элементы -for (int i = 0; i < 5; i++) -{ - var item = new Border + public ItemDragSource(Item item) { - Background = new SolidColorBrush(Colors.White), - BorderBrush = new SolidColorBrush(Colors.Gray), - BorderThickness = new Thickness(1), - Margin = new Thickness(0, 0, 0, 5), - Child = new TextBlock { Text = $"Item {i + 1}" } - }; - - item.MakeDragSource(item); - reorderContainer.Children.Add(item); -} -``` + _item = item; + } -### Кастомизация визуальной обратной связи - -```csharp -// Создаем кастомный стиль -var customStyle = new Style(typeof(Control)); -customStyle.Setters.Add(new Setter(Control.BackgroundProperty, - new SolidColorBrush(Colors.Yellow))); -customStyle.Setters.Add(new Setter(Control.BorderBrushProperty, - new SolidColorBrush(Colors.Red))); - -// Применяем стиль к элементу -var element = new Button { Content = "Custom Style" }; -element.SetDropFeedbackStyle(customStyle); -element.MakeDragSource("data"); -``` - -## 📁 Структура API - -### Behaviors (Поведения) - -| Класс | Описание | -|-------|----------| -| `WinUIDragSourceBehavior` | Прикрепляемое поведение для источников | -| `WinUIDropTargetBehavior` | Прикрепляемое поведение для целей | - -### Controls (Контролы) - -| Контрол | Назначение | -|---------|------------| -| `DragAdorner` | Визуальное представление перетаскивания | -| `DropPreviewAdorner` | Предпросмотр области сброса | -| `DragDropOverlay` | Оверлей для визуальных элементов | - -### Services (Сервисы) - -| Сервис | Назначение | -|--------|------------| -| `WinUIDragVisualProvider` | Создание визуальных элементов | -| `DragDropConfigurationService` | Централизованная настройка | - -### Extensions (Расширения) - -```csharp -// Основные методы расширения -element.MakeDragSource(data); // Сделать перетаскиваемым -element.MakeDropTarget(types); // Сделать целью сброса -control.ApplyDragStyle(); // Применить стиль перетаскивания -control.SetDragVisualState("Dragging"); // Установить визуальное состояние -``` - -## 🎯 Примеры использования - -### Пример 1: Файловый менеджер - -```csharp -// Перетаскивание файлов -var fileItem = new ListViewItem { Content = "Document.pdf" }; -fileItem.MakeDragSource(new FileData { Path = "C:\\Files\\Document.pdf" }); - -// Папка - цель сброса -var folderItem = new ListViewItem { Content = "Downloads" }; -folderItem.MakeDropTarget(typeof(FileData)); -folderItem.SetDropHandler(new FileDropHandler()); -``` - -### Пример 2: Конструктор UI - -```csharp -// Панель инструментов -var toolbox = new StackPanel(); -toolbox.MakeChildrenDraggable(element => new ControlTemplate -{ - Type = element.GetType(), - Name = element.Name -}); - -// Область дизайна -var designArea = new Canvas(); -designArea.MakeDropTarget(typeof(ControlTemplate)); - -// Обработчик сброса -designArea.Drop += (sender, e) => -{ - var template = e.DataView.GetData(); - var control = Activator.CreateInstance(template.Type); - Canvas.SetLeft(control, e.GetPosition(designArea).X); - Canvas.SetTop(control, e.GetPosition(designArea).Y); - designArea.Children.Add(control); -}; -``` - -### Пример 3: Календарь с событиями - -```csharp -// Событие календаря -var calendarEvent = new Border -{ - Background = new SolidColorBrush(Colors.CornflowerBlue), - Child = new TextBlock { Text = "Meeting at 10:00" } -}; - -calendarEvent.MakeDragSource(new CalendarEvent -{ - Id = 1, - Title = "Meeting", - StartTime = DateTime.Now -}); - -// Ячейка календаря -var timeSlot = new Border -{ - Background = new SolidColorBrush(Colors.White), - BorderBrush = new SolidColorBrush(Colors.LightGray) -}; - -timeSlot.MakeDropTarget(typeof(CalendarEvent)); -``` - -## 🎨 Темы и стилизация - -### Встроенные стили - -Проект включает готовые стили в папке `Themes/`: - -- `DragAdorner.xaml` - стиль для визуального элемента перетаскивания -- `DropPreviewAdorner.xaml` - стиль для предпросмотра сброса -- `DragDropStyles.xaml` - общие стили для элементов - -### Кастомизация через ресурсы - -```xml - - - - - - - - - -``` - -### Визуальные состояния - -Элементы поддерживают следующие визуальные состояния: - -- `Normal` - обычное состояние -- `Dragging` - элемент перетаскивается -- `DragOver` - над элементом перетаскивают объект - -```csharp -// Переключение состояний -control.SetDragVisualState("Dragging", true); // С анимацией -control.SetDragVisualState("Normal", false); // Без анимации -``` - -## 🔧 Интеграция с Docking System - -Система идеально интегрируется с Lattice Docking: - -```csharp -using Lattice.UI.DragDrop.WinUI.Extensions; -using Lattice.UI.Docking.WinUI; - -public class DockPane : ContentControl -{ - public DockPane() + public async Task TryStartDragAsync(Point startPosition, CancellationToken ct) { - // Делаем панель перетаскиваемой - this.MakeDragSource(new DockPaneDragData + // Проверяем, можно ли начать перетаскивание + if (!_item.CanBeDragged) + return null; + + // Создаем информацию о перетаскивании + return new DragInfo( + data: _item, + allowedEffects: DragDropEffects.Copy | DragDropEffects.Move, + startPosition: startPosition, + source: this + ); + } + + public async Task OnDragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken ct) + { + if (effects == DragDropEffects.Move) { - Pane = this, - ContentType = Content?.GetType(), - Title = Title - }); - - // Устанавливаем визуальную обратную связь - this.ApplyDragStyle(); - this.EnableDragVisualFeedback(); + // Удаляем элемент при перемещении + await _repository.DeleteAsync(_item.Id, ct); + } } -} -public class DockArea : ContentControl -{ - public DockArea() + public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken ct) { - // Область докинга принимает панели - this.MakeDropTarget(typeof(DockPaneDragData)); - - // Кастомный обработчик - this.SetDropHandler(new DockDropHandler()); + // Очистка ресурсов при отмене + return Task.CompletedTask; } } ``` -## 📊 Производительность - -### Оптимизации - -1. **Минимальные перерисовки** - обновление только при необходимости -2. **Кэширование визуальных элементов** - повторное использование -3. **Эффективные алгоритмы поиска** - быстрый поиск целей сброса -4. **Асинхронная обработка** - не блокирует UI поток - -### Рекомендации +### 3. Создание цели сброса ```csharp -// ✅ Правильно -element.MakeDragSource(data); +public class ContainerDropTarget : IDropTarget +{ + private readonly ObservableCollection _items; -// ❌ Избегать -element.PointerPressed += (s, e) => { /* сложная логика */ }; -element.PointerMoved += (s, e) => { /* частые обновления */ }; + public async Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct) + { + // Проверяем тип данных + if (dropInfo.Data is not Item item) + return false; + + // Проверяем бизнес-правила + return await _validator.CanAddItemAsync(item, ct); + } + + public async Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct) + { + // Обновляем визуальную обратную связь + dropInfo.SuggestedEffects = DragDropEffects.Move; + dropInfo.ShowVisualFeedback = true; + } + + public async Task OnDropAsync(DropInfo dropInfo, CancellationToken ct) + { + var item = (Item)dropInfo.Data; + await _items.AddAsync(item, ct); + + // Помечаем как обработанное + dropInfo.MarkAsHandled(); + } + + public Task OnDragLeaveAsync(CancellationToken ct) + { + // Очищаем визуальную обратную связь + return Task.CompletedTask; + } +} ``` -## 🧪 Тестирование - -### Модульные тесты +### 4. Регистрация и использование сервиса ```csharp -[TestClass] -public class DragDropTests +public class MainViewModel { - [TestMethod] - public void MakeDragSource_EnablesDragging() + private readonly IDragDropService _dragDropService; + + public MainViewModel(IDragDropService dragDropService) { - var element = new Border(); - element.MakeDragSource("test"); + _dragDropService = dragDropService; - Assert.IsTrue(element.IsDragSource()); + // Регистрация цели сброса + _targetId = _dragDropService.RegisterDropTarget( + target: new ContainerDropTarget(_items), + bounds: new Rect(0, 0, 400, 300), + priority: 0, + group: "main-container" + ); + + // Подписка на события + _dragDropService.DragStarted += OnDragStarted; + _dragDropService.DragCompleted += OnDragCompleted; + _dragDropService.ErrorOccurred += OnError; } + + // Обработка мышиных событий + public async Task OnMouseDown(Point position) + { + var source = new ItemDragSource(selectedItem); + await _dragDropService.StartDragAsync(source, position); + } + + public async Task OnMouseMove(Point position) + { + await _dragDropService.UpdateDragAsync(position); + } + + public async Task OnMouseUp(Point position) + { + var effects = await _dragDropService.EndDragAsync(position); + // Обработка результатов + } +} +``` + +## 📊 Статистика и мониторинг + +```csharp +// Получение статистики использования +var stats = _dragDropService.GetStats(); + +Console.WriteLine($"Всего операций: {stats.TotalDragOperations}"); +Console.WriteLine($"Успешных сбросов: {stats.SuccessfulDrops}"); +Console.WriteLine($"Отменено операций: {stats.CancelledOperations}"); +Console.WriteLine($"Среднее время операции: {stats.AverageOperationTime}"); +``` + +## ⚙️ Конфигурация + +### Параметры сервиса + +```csharp +// Настройка через свойства сервиса +_dragDropService.DragStartThreshold = 5.0; // Порог в пикселях +_dragDropService.EnableAsyncOperations = true; +_dragDropService.AsyncOperationTimeout = 3000; // 3 секунды +``` + +### Константы по умолчанию + +Все значения по умолчанию определены в классе `DragDropConstants`: +- `DefaultDragThreshold`: 3.0 пикселей +- `DefaultAsyncTimeout`: 5000 миллисекунд +- `TargetLifetimeMinutes`: 10 минут + +## 🔧 Расширенные сценарии + +### Группировка целей сброса + +```csharp +// Регистрация группы целей +_dragDropService.RegisterDropTarget(target1, bounds1, group: "panel"); +_dragDropService.RegisterDropTarget(target2, bounds2, group: "panel"); + +// Массовая отмена регистрации +_dragDropService.UnregisterDropTargetsInGroup("panel"); +``` + +### Обработка ошибок + +```csharp +private void OnError(object sender, DragDropErrorEventArgs e) +{ + _logger.LogError(e.Exception, + "Ошибка в операции {Operation}", + e.Operation); - [TestMethod] - public void MakeDropTarget_AcceptsCorrectTypes() - { - var element = new Border(); - element.MakeDropTarget(typeof(string), typeof(int)); - - Assert.IsTrue(element.IsDropTarget()); - } + // Уведомление пользователя + _notificationService.ShowError("Ошибка при перетаскивании"); } ``` -### UI тесты +### Кастомизация эффектов ```csharp -[TestClass] -public class DragDropUITests +// Использование расширений для работы с эффектами +if (effects.CanCopy()) { - [UITestMethod] - public async Task DragAndDrop_BetweenElements() - { - await UITestHelper.Run(async window => - { - var source = new Border { Background = new SolidColorBrush(Colors.Blue) }; - var target = new Border { Background = new SolidColorBrush(Colors.Green) }; - - source.MakeDragSource("data"); - target.MakeDropTarget(typeof(string)); - - // Симуляция перетаскивания - await SimulateDrag(source, target); - - // Проверка результатов - Assert.IsTrue(dropOccurred); - }); - } + // Логика для копирования } -``` -## 🔍 Отладка - -### Включение логов - -```csharp -// Включение подробного логирования -#if DEBUG -DragDropDebugger.EnableLogging = true; -DragDropDebugger.LogLevel = LogLevel.Verbose; -#endif -``` - -### Визуальные подсказки - -```csharp -// Показать границы целей сброса -DebugDropTargets.ShowBounds = true; - -// Показать траекторию перетаскивания -DebugDragTrail.Enabled = true; -DebugDragTrail.Color = Colors.Red; -``` - -## 📈 Производительность в production - -### Мониторинг - -```csharp -// Сбор метрик -var metrics = DragDropPerformanceCollector.Collect(); -Console.WriteLine($"Drag operations: {metrics.DragCount}"); -Console.WriteLine($"Average drag time: {metrics.AverageDragTimeMs}ms"); -Console.WriteLine($"Drop success rate: {metrics.DropSuccessRate:P}"); -``` - -### Оптимизация для больших списков - -```csharp -// Виртуализация для списков -var virtualizingList = new ListView +if (effects.CanMove()) { - ItemsSource = largeCollection, - VirtualizingStackPanel.VirtualizationMode = VirtualizationMode.Recycling -}; + // Логика для перемещения +} -// Оптимизация перетаскивания -virtualizingList.MakeDropTarget(typeof(DataItem)); -virtualizingList.SetDragDropOptimization(true); +// Определение эффекта по модификаторам клавиш +var effect = DragDropEffectsExtensions.GetEffectFromKeys( + controlKey: Keyboard.IsControlDown, + shiftKey: Keyboard.IsShiftDown, + altKey: Keyboard.IsAltDown +); ``` -## 🤝 Интеграция с другими компонентами Lattice +## 📝 Best Practices -### Toolbox -```csharp -toolboxItem.MakeDragSource(new ToolboxItem -{ - Type = typeof(Button), - Icon = "🔘" -}); -``` +1. **Производительность** + - Методы `CanAcceptDropAsync` и `OnDragOverAsync` вызываются часто, оптимизируйте их + - Избегайте синхронных операций в обработчиках + - Используйте кэширование при проверке типов данных -### Property Grid -```csharp -propertyItem.MakeDragSource(new PropertyValue -{ - Name = "Background", - Value = Colors.Blue -}); -``` +2. **Безопасность** + - Всегда проверяйте тип данных в `CanAcceptDropAsync` + - Валидируйте бизнес-правила перед обработкой сброса + - Используйте CancellationToken для отмены длительных операций -### Layout System -```csharp -layoutPanel.MakeDropTarget(typeof(UIElement)); -layoutPanel.SetDropPositionHandler((element, position) => -{ - return CalculateDropZone(position); -}); -``` +3. **Пользовательский опыт** + - Предоставляйте визуальную обратную связь через `DropInfo.ShowVisualFeedback` + - Используйте `DropInfo.VisualFeedbackData` для кастомизации отображения + - Обрабатывайте отмену операций для очистки временных данных -## 📚 Дополнительные ресурсы +## 🔄 Миграция -### Документация -- [Полная документация API](https://lattice-framework.github.io/ui-dragdrop/api/) -- [Примеры использования](https://lattice-framework.github.io/ui-dragdrop/examples/) -- [Руководство по стилизации](https://lattice-framework.github.io/ui-dragdrop/styling/) +### С версии 1.x на 2.0 -### Видео туториалы -- [Быстрый старт](https://youtube.com/playlist?list=...) - 15 минут -- [Продвинутые техники](https://youtube.com/playlist?list=...) - 45 минут -- [Интеграция с Docking](https://youtube.com/playlist?list=...) - 30 минут +1. **Интерфейсы переименованы:** + - `CanStartDragAsync` → `TryStartDragAsync` + - `DragCompletedAsync` → `OnDragCompletedAsync` + - `DragCancelledAsync` → `OnDragCancelledAsync` -### Сообщество -- [GitHub Discussions](https://github.com/lattice-framework/ui-dragdrop/discussions) - вопросы и обсуждения -- [Discord](https://discord.gg/lattice) - живое общение -- [Stack Overflow](https://stackoverflow.com/questions/tagged/lattice-ui-dragdrop) - технические вопросы +2. **Классы EventArgs объединены:** + - Все события наследуются от `DragEventArgs` + - Упрощена иерархия классов событий -## 🐛 Отчет об ошибках - -Нашли ошибку? [Создайте issue](https://github.com/lattice-framework/ui-dragdrop/issues) с подробным описанием: - -1. Шаги для воспроизведения -2. Ожидаемое поведение -3. Фактическое поведение -4. Скриншоты или видео -5. Версии: Windows, WinUI, Lattice - -## 🚀 Roadmap - -### Версия 1.1 (Q2 2024) -- [ ] Поддержка touch-жестов -- [ ] Анимации с физикой -- [ ] Расширенная визуализация - -### Версия 1.2 (Q3 2024) -- [ ] Интеграция с Windows Shell -- [ ] Поддержка виртуальных данных -- [ ] Улучшенная доступность - -### Версия 2.0 (Q4 2024) -- [ ] Кроссплатформенная поддержка (Uno Platform) -- [ ] WebAssembly поддержка -- [ ] Расширенный набор контролов +3. **Удалены устаревшие компоненты:** + - `AsyncDragDropUtilities` удален + - `ServiceCollectionExtensions` удален ## 📄 Лицензия -MIT License. Подробности в файле [LICENSE](LICENSE). +Библиотека распространяется под лицензией MIT. Подробности см. в файле LICENSE. -## 👥 Авторы +## 🤝 Вклад в разработку -- **Команда Lattice Framework** - [@lattice-framework](https://github.com/lattice-framework) -- **Главный разработчик** - [Ваше имя](https://github.com/yourusername) +Мы приветствуем вклад в развитие библиотеки. Перед отправкой pull request ознакомьтесь с руководством по контрибьютингу. -## 🙏 Благодарности +## 🐛 Отчеты об ошибках -Спасибо сообществу WinUI и всем контрибьюторам, которые помогают улучшать проект! - ---- - -
-

- Lattice.UI.DragDrop.WinUI - часть Lattice UI Framework -

-

- GitHub • - Документация • - Discord • - Twitter -

-
\ No newline at end of file +Для сообщения об ошибках используйте Issues на GitHub. Пожалуйста, указывайте: +- Версию библиотеки +- Шаги для воспроизведения +- Ожидаемое и фактическое поведение +- Пример кода для демонстрации проблемы \ No newline at end of file