Переработаны методы.
This commit is contained in:
@@ -5,57 +5,80 @@
|
|||||||
/// в операции перетаскивания.
|
/// в операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// Объекты, реализующие этот интерфейс, могут инициировать операции перетаскивания
|
/// Объекты, реализующие этот интерфейс, могут инициировать операции перетаскивания
|
||||||
/// и предоставлять данные для передачи другим элементам через механизм drag-and-drop.
|
/// и предоставлять данные для передачи другим элементам через механизм drag-and-drop.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Интерфейс полностью асинхронный и поддерживает отмену операций через CancellationToken.
|
||||||
|
/// Все методы должны быть потокобезопасными и поддерживать вызов из любого потока.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public interface IDragSource
|
public interface IDragSource
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Определяет, может ли объект начать операцию перетаскивания.
|
/// Пытается начать операцию перетаскивания из указанной позиции.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="startPosition">Начальная позиция операции в координатах экрана.</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены операции.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// Кортеж, содержащий флаг возможности начала перетаскивания и информацию о перетаскивании.
|
/// Информация о перетаскивании, если операция может быть начата; в противном случае — null.
|
||||||
|
/// Возвращаемый объект <see cref="Models.DragInfo"/> должен быть полностью инициализирован,
|
||||||
|
/// включая данные, разрешенные эффекты и ссылку на источник.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Этот метод вызывается системой перетаскивания для проверки возможности
|
/// <para>
|
||||||
/// начала операции. Если метод возвращает true, он должен заполнить
|
/// Этот метод вызывается сервисом перетаскивания при попытке начать операцию
|
||||||
/// DragInfo необходимыми данными.
|
/// (обычно при нажатии и перемещении мыши). Метод должен проверить, может ли
|
||||||
|
/// источник начать перетаскивание в текущем контексте.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Реализация должна быть быстрой и не выполнять длительных операций.
|
||||||
|
/// Если подготовка данных требует времени, ее следует выполнить асинхронно
|
||||||
|
/// после подтверждения возможности начала.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
Task<(bool CanStart, Models.DragInfo? DragInfo)> CanStartDragAsync(CancellationToken cancellationToken = default);
|
Task<Models.DragInfo?> TryStartDragAsync(Geometry.Point startPosition, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Начинает операцию перетаскивания.
|
/// Уведомляет источник о завершении операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
/// <param name="dragInfo">Информация о перетаскивании, полученная при начале операции.</param>
|
||||||
/// <returns>
|
|
||||||
/// true, если операция перетаскивания успешно начата; в противном случае — false.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// Этот метод вызывается, когда пользователь начинает перетаскивание элемента.
|
|
||||||
/// Реализация должна подготовить данные для перетаскивания и, возможно,
|
|
||||||
/// создать визуальное представление перетаскиваемого объекта.
|
|
||||||
/// </remarks>
|
|
||||||
Task<bool> StartDragAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Вызывается при завершении операции перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dragInfo">Исходная информация о перетаскивании.</param>
|
|
||||||
/// <param name="effects">Эффекты, которые были применены при сбросе.</param>
|
/// <param name="effects">Эффекты, которые были применены при сбросе.</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены операции.</param>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// Этот метод вызывается после завершения операции перетаскивания
|
/// Этот метод вызывается после завершения операции перетаскивания
|
||||||
/// (успешного или неуспешного). Реализация может выполнить очистку
|
/// (успешного или неуспешного). Реализация может:
|
||||||
/// или обновить состояние на основе результата операции.
|
/// </para>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Выполнить очистку ресурсов, связанных с операцией</item>
|
||||||
|
/// <item>Обновить состояние на основе результата (например, удалить данные при перемещении)</item>
|
||||||
|
/// <item>Отобразить визуальную обратную связь о результате</item>
|
||||||
|
/// </list>
|
||||||
|
/// <para>
|
||||||
|
/// Если операция завершилась с эффектом <see cref="Enums.DragDropEffects.Move"/>,
|
||||||
|
/// источник обычно должен удалить или обновить исходные данные.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
Task DragCompletedAsync(Models.DragInfo dragInfo, Enums.DragDropEffects effects, CancellationToken cancellationToken = default);
|
Task OnDragCompletedAsync(Models.DragInfo dragInfo, Enums.DragDropEffects effects, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается при отмене операции перетаскивания.
|
/// Уведомляет источник об отмене операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragInfo">Исходная информация о перетаскивании.</param>
|
/// <param name="dragInfo">Информация о перетаскивании, полученная при начале операции.</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены операции.</param>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// Этот метод вызывается, когда операция перетаскивания была отменена
|
/// Этот метод вызывается, когда операция перетаскивания была отменена
|
||||||
/// пользователем (например, нажатием клавиши Escape).
|
/// пользователем (например, нажатием клавиши Escape) или системой.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Реализация должна выполнить очистку и восстановить исходное состояние.
|
||||||
|
/// Обычно это включает освобождение ресурсов и сброс визуальных индикаторов.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
Task DragCancelledAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default);
|
Task OnDragCancelledAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,15 @@
|
|||||||
/// в операции перетаскивания.
|
/// в операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// Объекты, реализующие этот интерфейс, могут обрабатывать данные, сброшенные
|
/// Объекты, реализующие этот интерфейс, могут обрабатывать данные, сброшенные
|
||||||
/// пользователем, и предоставлять визуальную обратную связь во время перетаскивания.
|
/// пользователем, и предоставлять визуальную обратную связь во время перетаскивания.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Интерфейс поддерживает асинхронные операции и отмену через CancellationToken.
|
||||||
|
/// Все методы должны быть потокобезопасными и идемпотентными (многократный вызов
|
||||||
|
/// с одинаковыми параметрами должен давать одинаковый результат).
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public interface IDropTarget
|
public interface IDropTarget
|
||||||
{
|
{
|
||||||
@@ -14,42 +21,98 @@ public interface IDropTarget
|
|||||||
/// Определяет, может ли объект принять сбрасываемые данные.
|
/// Определяет, может ли объект принять сбрасываемые данные.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dropInfo">Информация о потенциальном сбросе.</param>
|
/// <param name="dropInfo">Информация о потенциальном сбросе.</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены операции.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// true, если объект может принять данные; в противном случае — false.
|
/// true, если объект может принять данные; в противном случае — false.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// Этот метод вызывается, когда перетаскиваемый объект находится над целью.
|
/// Этот метод вызывается, когда перетаскиваемый объект находится над целью.
|
||||||
/// Реализация должна проверить, совместимы ли данные с целью, и установить
|
/// Реализация должна проверить, совместимы ли данные с целью, и установить
|
||||||
/// предлагаемые эффекты в <paramref name="dropInfo"/>.
|
/// предлагаемые эффекты в свойстве <see cref="Models.DropInfo.SuggestedEffects"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Метод может вызываться многократно при перемещении курсора над целью.
|
||||||
|
/// Реализация должна быть эффективной и избегать длительных операций.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Если метод возвращает false, система не будет вызывать другие методы
|
||||||
|
/// для этой цели до тех пор, пока курсор не покинет ее область.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
Task<bool> CanAcceptDropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default);
|
Task<bool> CanAcceptDropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается, когда перетаскиваемый объект находится над целью.
|
/// Вызывается, когда перетаскиваемый объект перемещается над целью.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dropInfo">Информация о текущем положении перетаскивания.</param>
|
/// <param name="dropInfo">Информация о текущем положении перетаскивания.</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены операции.</param>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// Этот метод вызывается постоянно, пока пользователь перемещает объект над целью.
|
/// Этот метод вызывается постоянно, пока пользователь перемещает объект над целью.
|
||||||
/// Реализация может обновить визуальную обратную связь или изменить предлагаемые эффекты.
|
/// Реализация может:
|
||||||
|
/// </para>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Обновить визуальную обратную связь (подсветка, изменение курсора)</item>
|
||||||
|
/// <item>Вычислить точную позицию сброса (например, между элементами списка)</item>
|
||||||
|
/// <item>Уточнить предлагаемые эффекты на основе текущей позиции</item>
|
||||||
|
/// <item>Прокрутить содержимое, если цель поддерживает прокрутку</item>
|
||||||
|
/// </list>
|
||||||
|
/// <para>
|
||||||
|
/// Метод должен быть оптимизирован для частого вызова. Длительные операции
|
||||||
|
/// должны выполняться асинхронно без блокировки потока.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
Task DragOverAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default);
|
Task OnDragOverAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается, когда пользователь сбрасывает данные на цель.
|
/// Вызывается, когда пользователь сбрасывает данные на цель.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dropInfo">Информация о сбросе.</param>
|
/// <param name="dropInfo">Информация о сбросе, включая данные и позицию.</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены операции.</param>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// Этот метод вызывается, когда пользователь отпускает кнопку мыши над целью.
|
/// Этот метод вызывается, когда пользователь отпускает кнопку мыши над целью.
|
||||||
/// Реализация должна обработать принятие данных и выполнить соответствующее действие.
|
/// Реализация должна обработать принятие данных и выполнить соответствующее действие:
|
||||||
|
/// </para>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Добавить данные в коллекцию (для копирования)</item>
|
||||||
|
/// <item>Переместить данные (при поддержке перемещения)</item>
|
||||||
|
/// <item>Создать ссылку на данные</item>
|
||||||
|
/// <item>Выполнить пользовательскую логику обработки</item>
|
||||||
|
/// </list>
|
||||||
|
/// <para>
|
||||||
|
/// После успешной обработки данных следует вызвать <see cref="Models.DropInfo.MarkAsHandled()"/>,
|
||||||
|
/// чтобы указать системе, что операция обработана и дополнительная обработка не требуется.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Если операция завершилась успешно, система уведомит источник через
|
||||||
|
/// <see cref="IDragSource.OnDragCompletedAsync"/> с соответствующими эффектами.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
Task DropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default);
|
Task OnDropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается, когда перетаскиваемый объект покидает область цели.
|
/// Вызывается, когда перетаскиваемый объект покидает область цели.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Токен отмены операции.</param>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// Этот метод вызывается, когда пользователь перемещает объект за пределы цели.
|
/// Этот метод вызывается, когда пользователь перемещает объект за пределы цели.
|
||||||
/// Реализация должна очистить любую визуальную обратную связь, установленную ранее.
|
/// Реализация должна:
|
||||||
|
/// </para>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Очистить любую визуальную обратную связь, установленную ранее</item>
|
||||||
|
/// <item>Сбросить временное состояние, связанное с операцией</item>
|
||||||
|
/// <item>Освободить ресурсы, выделенные для предварительного просмотра</item>
|
||||||
|
/// </list>
|
||||||
|
/// <para>
|
||||||
|
/// Метод гарантированно вызывается после любого успешного или неуспешного
|
||||||
|
/// вызова <see cref="OnDragOverAsync"/>, если курсор покидает область цели.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
Task DragLeaveAsync(CancellationToken cancellationToken = default);
|
Task OnDragLeaveAsync(CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,9 @@ public static class DragDropEffectsExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Проверяет, содержит ли эффекты указанный эффект.
|
/// Проверяет, содержит ли эффекты указанный эффект.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="effects">Эффекты для проверки.</param>
|
||||||
|
/// <param name="effect">Эффект для поиска.</param>
|
||||||
|
/// <returns>true, если эффект присутствует; в противном случае — false.</returns>
|
||||||
public static bool HasEffect(this DragDropEffects effects, DragDropEffects effect)
|
public static bool HasEffect(this DragDropEffects effects, DragDropEffects effect)
|
||||||
{
|
{
|
||||||
return (effects & effect) == effect;
|
return (effects & effect) == effect;
|
||||||
@@ -62,6 +65,8 @@ public static class DragDropEffectsExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Проверяет, содержат ли эффекты копирование.
|
/// Проверяет, содержат ли эффекты копирование.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="effects">Эффекты для проверки.</param>
|
||||||
|
/// <returns>true, если разрешено копирование; в противном случае — false.</returns>
|
||||||
public static bool CanCopy(this DragDropEffects effects)
|
public static bool CanCopy(this DragDropEffects effects)
|
||||||
{
|
{
|
||||||
return effects.HasEffect(DragDropEffects.Copy);
|
return effects.HasEffect(DragDropEffects.Copy);
|
||||||
@@ -70,6 +75,8 @@ public static class DragDropEffectsExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Проверяет, содержат ли эффекты перемещение.
|
/// Проверяет, содержат ли эффекты перемещение.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="effects">Эффекты для проверки.</param>
|
||||||
|
/// <returns>true, если разрешено перемещение; в противном случае — false.</returns>
|
||||||
public static bool CanMove(this DragDropEffects effects)
|
public static bool CanMove(this DragDropEffects effects)
|
||||||
{
|
{
|
||||||
return effects.HasEffect(DragDropEffects.Move);
|
return effects.HasEffect(DragDropEffects.Move);
|
||||||
@@ -78,6 +85,8 @@ public static class DragDropEffectsExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Проверяет, содержат ли эффекты ссылку.
|
/// Проверяет, содержат ли эффекты ссылку.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="effects">Эффекты для проверки.</param>
|
||||||
|
/// <returns>true, если разрешена ссылка; в противном случае — false.</returns>
|
||||||
public static bool CanLink(this DragDropEffects effects)
|
public static bool CanLink(this DragDropEffects effects)
|
||||||
{
|
{
|
||||||
return effects.HasEffect(DragDropEffects.Link);
|
return effects.HasEffect(DragDropEffects.Link);
|
||||||
@@ -86,6 +95,10 @@ public static class DragDropEffectsExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает наиболее подходящий эффект на основе модификаторов клавиатуры.
|
/// Получает наиболее подходящий эффект на основе модификаторов клавиатуры.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="controlKey">Нажата ли клавиша Control.</param>
|
||||||
|
/// <param name="shiftKey">Нажата ли клавиша Shift.</param>
|
||||||
|
/// <param name="altKey">Нажата ли клавиша Alt.</param>
|
||||||
|
/// <returns>Наиболее подходящий эффект перетаскивания.</returns>
|
||||||
public static DragDropEffects GetEffectFromKeys(bool controlKey, bool shiftKey, bool altKey)
|
public static DragDropEffects GetEffectFromKeys(bool controlKey, bool shiftKey, bool altKey)
|
||||||
{
|
{
|
||||||
if (controlKey && shiftKey)
|
if (controlKey && shiftKey)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
namespace Lattice.Core.DragDrop.Extensions;
|
using Lattice.Core.DragDrop.Enums;
|
||||||
|
using Lattice.Core.Geometry;
|
||||||
|
|
||||||
|
namespace Lattice.Core.DragDrop.Extensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Методы расширения для DropInfo.
|
/// Методы расширения для DropInfo.
|
||||||
@@ -8,6 +11,9 @@ public static class DropInfoExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Проверяет, могут ли данные быть приведены к указанному типу.
|
/// Проверяет, могут ли данные быть приведены к указанному типу.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Тип данных для проверки.</typeparam>
|
||||||
|
/// <param name="dropInfo">Информация о сбросе.</param>
|
||||||
|
/// <returns>true, если данные могут быть приведены к типу T; в противном случае — false.</returns>
|
||||||
public static bool CanAccept<T>(this Models.DropInfo dropInfo)
|
public static bool CanAccept<T>(this Models.DropInfo dropInfo)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
@@ -17,6 +23,9 @@ public static class DropInfoExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Пытается получить данные как указанный тип.
|
/// Пытается получить данные как указанный тип.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Тип, к которому нужно привести данные.</typeparam>
|
||||||
|
/// <param name="dropInfo">Информация о сбросе.</param>
|
||||||
|
/// <returns>Данные как тип T или null.</returns>
|
||||||
public static T? GetDataAs<T>(this Models.DropInfo dropInfo)
|
public static T? GetDataAs<T>(this Models.DropInfo dropInfo)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
@@ -26,6 +35,10 @@ public static class DropInfoExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает данные как указанный тип или выбрасывает исключение.
|
/// Получает данные как указанный тип или выбрасывает исключение.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Тип, к которому нужно привести данные.</typeparam>
|
||||||
|
/// <param name="dropInfo">Информация о сбросе.</param>
|
||||||
|
/// <returns>Данные как тип T.</returns>
|
||||||
|
/// <exception cref="InvalidCastException">Выбрасывается, если данные не могут быть приведены к типу T.</exception>
|
||||||
public static T GetRequiredDataAs<T>(this Models.DropInfo dropInfo)
|
public static T GetRequiredDataAs<T>(this Models.DropInfo dropInfo)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
@@ -40,8 +53,22 @@ public static class DropInfoExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Проверяет, содержится ли позиция в указанных границах.
|
/// Проверяет, содержится ли позиция в указанных границах.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsInBounds(this Models.DropInfo dropInfo, Geometry.Rect bounds)
|
/// <param name="dropInfo">Информация о сбросе.</param>
|
||||||
|
/// <param name="bounds">Границы для проверки.</param>
|
||||||
|
/// <returns>true, если позиция находится в границах; в противном случае — false.</returns>
|
||||||
|
public static bool IsInBounds(this Models.DropInfo dropInfo, Rect bounds)
|
||||||
{
|
{
|
||||||
return bounds.Contains(dropInfo.Position);
|
return bounds.Contains(dropInfo.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет можно ли добавить эффект перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dropInfo"></param>
|
||||||
|
/// <param name="effect"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool CanAcceptEffect(this Models.DropInfo dropInfo, DragDropEffects effect)
|
||||||
|
{
|
||||||
|
return dropInfo.AllowedEffects.HasEffect(effect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
namespace Lattice.Core.DragDrop.Extensions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Методы расширения для регистрации сервисов перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public static class ServiceCollectionExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Добавляет сервис перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceCollection">Коллекция сервисов.</param>
|
|
||||||
/// <returns>Коллекция сервисов.</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// Реализация DI должна быть предоставлена конкретным приложением.
|
|
||||||
/// </remarks>
|
|
||||||
public static object AddDragDropService(this object serviceCollection)
|
|
||||||
{
|
|
||||||
// Реализация регистрации сервиса должна быть в конкретном приложении
|
|
||||||
// Это абстрактный метод для поддержки DI без зависимостей
|
|
||||||
return serviceCollection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Добавляет сервис перетаскивания с конфигурацией.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceCollection">Коллекция сервисов.</param>
|
|
||||||
/// <param name="configure">Действие конфигурации.</param>
|
|
||||||
/// <returns>Коллекция сервисов.</returns>
|
|
||||||
public static object AddDragDropService(
|
|
||||||
this object serviceCollection,
|
|
||||||
Action<DragDropServiceOptions> configure)
|
|
||||||
{
|
|
||||||
var options = new DragDropServiceOptions();
|
|
||||||
configure(options);
|
|
||||||
|
|
||||||
// Реализация регистрации с опциями должна быть в конкретном приложении
|
|
||||||
return serviceCollection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Опции конфигурации сервиса перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public class DragDropServiceOptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Порог начала перетаскивания в пикселях.
|
|
||||||
/// </summary>
|
|
||||||
public double DragStartThreshold { get; set; } = 3.0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Включить ведение журнала операций.
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableLogging { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Включить автоматическую очистку неиспользуемых целей.
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableAutoCleanup { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Интервал автоматической очистки в миллисекундах.
|
|
||||||
/// </summary>
|
|
||||||
public int AutoCleanupInterval { get; set; } = 60000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Включить асинхронную обработку операций.
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableAsyncOperations { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Время ожидания асинхронных операций в миллисекундах.
|
|
||||||
/// </summary>
|
|
||||||
public int AsyncOperationTimeout { get; set; } = 5000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Включить сбор статистики.
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableStatistics { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Включить проверку типов данных.
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableTypeChecking { get; set; } = true;
|
|
||||||
}
|
|
||||||
@@ -30,8 +30,20 @@ namespace Lattice.Core.DragDrop.Models;
|
|||||||
public class DropInfo
|
public class DropInfo
|
||||||
{
|
{
|
||||||
private DragDropEffects _effects = DragDropEffects.None;
|
private DragDropEffects _effects = DragDropEffects.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает позицию сброса относительно цели.
|
||||||
|
/// </summary>
|
||||||
public DropPosition DropPosition { get; set; } = DropPosition.Inside;
|
public DropPosition DropPosition { get; set; } = DropPosition.Inside;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает значение, указывающее, нужно ли показывать визуальную обратную связь.
|
||||||
|
/// </summary>
|
||||||
public bool ShowVisualFeedback { get; set; } = true;
|
public bool ShowVisualFeedback { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает данные для визуальной обратной связи.
|
||||||
|
/// </summary>
|
||||||
public object? VisualFeedbackData { get; set; }
|
public object? VisualFeedbackData { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,13 +1,52 @@
|
|||||||
namespace Lattice.Core.DragDrop.Services;
|
using Lattice.Core.DragDrop.Abstractions;
|
||||||
|
using Lattice.Core.DragDrop.Constants;
|
||||||
|
using Lattice.Core.DragDrop.Enums;
|
||||||
|
using Lattice.Core.DragDrop.Models;
|
||||||
|
using Lattice.Core.Geometry;
|
||||||
|
|
||||||
|
namespace Lattice.Core.DragDrop.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Реализация сервиса управления операциями перетаскивания.
|
/// Центральный сервис управления операциями перетаскивания.
|
||||||
/// Полностью потокобезопасная реализация с поддержкой async/await.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="DragDropService"/> является основным компонентом системы drag-and-drop,
|
||||||
|
/// который координирует взаимодействие между источниками данных (<see cref="Abstractions.IDragSource"/>)
|
||||||
|
/// и целями сброса (<see cref="Abstractions.IDropTarget"/>).
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Основные функции сервиса:
|
||||||
|
/// </para>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Регистрация и управление целями сброса</item>
|
||||||
|
/// <item>Оркестрация жизненного цикла операций перетаскивания</item>
|
||||||
|
/// <item>Обработка событий мыши и клавиатуры</item>
|
||||||
|
/// <item>Распространение информации между компонентами</item>
|
||||||
|
/// <item>Обеспечение потокобезопасности операций</item>
|
||||||
|
/// </list>
|
||||||
|
/// <para>
|
||||||
|
/// Сервис поддерживает полностью асинхронную модель работы, уведомления через события
|
||||||
|
/// и статистику использования. Все операции защищены от параллельного доступа
|
||||||
|
/// и обеспечивают корректную очистку ресурсов.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Для использования сервиса необходимо:
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Зарегистрировать цели сброса с помощью <see cref="RegisterDropTarget"/></item>
|
||||||
|
/// <item>Вызывать методы <see cref="StartDragAsync"/>, <see cref="UpdateDragAsync"/>,
|
||||||
|
/// <see cref="EndDragAsync"/> в ответ на действия пользователя</item>
|
||||||
|
/// <item>Подписаться на события для отслеживания состояния операций</item>
|
||||||
|
/// </list>
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public sealed class DragDropService : IDragDropService
|
public sealed class DragDropService : IDragDropService
|
||||||
{
|
{
|
||||||
#region Nested Types
|
#region Nested Types
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Информация о зарегистрированной цели сброса.
|
||||||
|
/// </summary>
|
||||||
private sealed class DropTargetInfo : IDisposable
|
private sealed class DropTargetInfo : IDisposable
|
||||||
{
|
{
|
||||||
public required Abstractions.IDropTarget Target { get; init; }
|
public required Abstractions.IDropTarget Target { get; init; }
|
||||||
@@ -25,6 +64,9 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Контекст текущей операции перетаскивания.
|
||||||
|
/// </summary>
|
||||||
private sealed class DragOperationContext : IDisposable
|
private sealed class DragOperationContext : IDisposable
|
||||||
{
|
{
|
||||||
public Abstractions.IDragSource? Source { get; set; }
|
public Abstractions.IDragSource? Source { get; set; }
|
||||||
@@ -33,6 +75,7 @@ public sealed class DragDropService : IDragDropService
|
|||||||
public CancellationTokenSource? CancellationTokenSource { get; set; }
|
public CancellationTokenSource? CancellationTokenSource { get; set; }
|
||||||
public DateTime StartTime { get; set; } = DateTime.UtcNow;
|
public DateTime StartTime { get; set; } = DateTime.UtcNow;
|
||||||
public bool ThresholdExceeded { get; set; }
|
public bool ThresholdExceeded { get; set; }
|
||||||
|
public Point LastPosition { get; set; }
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@@ -74,48 +117,60 @@ public sealed class DragDropService : IDragDropService
|
|||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public bool IsDragActive => Volatile.Read(ref _currentDragOperation) != null;
|
public bool IsDragActive => Volatile.Read(ref _currentDragOperation) != null;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Models.DragInfo? CurrentDragInfo => _currentDragOperation?.DragInfo;
|
public Models.DragInfo? CurrentDragInfo => _currentDragOperation?.DragInfo;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Abstractions.IDropTarget? CurrentDropTarget => _currentDragOperation?.CurrentDropTarget;
|
public Abstractions.IDropTarget? CurrentDropTarget => _currentDragOperation?.CurrentDropTarget;
|
||||||
|
|
||||||
public double DragStartThreshold { get; set; } = 3.0;
|
/// <inheritdoc/>
|
||||||
|
public double DragStartThreshold { get; set; } = DragDropConstants.DefaultDragThreshold;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public bool EnableAsyncOperations { get; set; } = true;
|
public bool EnableAsyncOperations { get; set; } = true;
|
||||||
|
|
||||||
public int AsyncOperationTimeout { get; set; } = 5000;
|
/// <inheritdoc/>
|
||||||
|
public int AsyncOperationTimeout { get; set; } = DragDropConstants.DefaultAsyncTimeout;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public event EventHandler<DragStartedEventArgs> DragStarted
|
public event EventHandler<DragStartedEventArgs> DragStarted
|
||||||
{
|
{
|
||||||
add => _dragStarted += value;
|
add => _dragStarted += value;
|
||||||
remove => _dragStarted -= value;
|
remove => _dragStarted -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public event EventHandler<DragUpdatedEventArgs> DragUpdated
|
public event EventHandler<DragUpdatedEventArgs> DragUpdated
|
||||||
{
|
{
|
||||||
add => _dragUpdated += value;
|
add => _dragUpdated += value;
|
||||||
remove => _dragUpdated -= value;
|
remove => _dragUpdated -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public event EventHandler<DropTargetChangedEventArgs> DropTargetChanged
|
public event EventHandler<DropTargetChangedEventArgs> DropTargetChanged
|
||||||
{
|
{
|
||||||
add => _dropTargetChanged += value;
|
add => _dropTargetChanged += value;
|
||||||
remove => _dropTargetChanged -= value;
|
remove => _dropTargetChanged -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public event EventHandler<DragCompletedEventArgs> DragCompleted
|
public event EventHandler<DragCompletedEventArgs> DragCompleted
|
||||||
{
|
{
|
||||||
add => _dragCompleted += value;
|
add => _dragCompleted += value;
|
||||||
remove => _dragCompleted -= value;
|
remove => _dragCompleted -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public event EventHandler<DragCancelledEventArgs> DragCancelled
|
public event EventHandler<DragCancelledEventArgs> DragCancelled
|
||||||
{
|
{
|
||||||
add => _dragCancelled += value;
|
add => _dragCancelled += value;
|
||||||
remove => _dragCancelled -= value;
|
remove => _dragCancelled -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public event EventHandler<DragDropErrorEventArgs> ErrorOccurred
|
public event EventHandler<DragDropErrorEventArgs> ErrorOccurred
|
||||||
{
|
{
|
||||||
add => _errorOccurred += value;
|
add => _errorOccurred += value;
|
||||||
@@ -126,6 +181,17 @@ public sealed class DragDropService : IDragDropService
|
|||||||
|
|
||||||
#region Constructor
|
#region Constructor
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="DragDropService"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Создает сервис с настройками по умолчанию:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Порог начала перетаскивания: <see cref="Constants.DragDropConstants.DefaultDragThreshold"/> пикселей</item>
|
||||||
|
/// <item>Таймаут асинхронных операций: <see cref="Constants.DragDropConstants.DefaultAsyncTimeout"/> миллисекунд</item>
|
||||||
|
/// <item>Включены асинхронные операции: true</item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
public DragDropService()
|
public DragDropService()
|
||||||
{
|
{
|
||||||
// Инициализация таймера очистки (каждые 5 минут)
|
// Инициализация таймера очистки (каждые 5 минут)
|
||||||
@@ -138,6 +204,7 @@ public sealed class DragDropService : IDragDropService
|
|||||||
|
|
||||||
#region Registration Methods
|
#region Registration Methods
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null)
|
public string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -165,6 +232,7 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public bool UpdateDropTargetBounds(string id, Geometry.Rect bounds)
|
public bool UpdateDropTargetBounds(string id, Geometry.Rect bounds)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -193,6 +261,7 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public bool UnregisterDropTarget(string id)
|
public bool UnregisterDropTarget(string id)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -213,6 +282,7 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public void UnregisterDropTargetsInGroup(string group)
|
public void UnregisterDropTargetsInGroup(string group)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -249,7 +319,8 @@ public sealed class DragDropService : IDragDropService
|
|||||||
|
|
||||||
#region Async Operations
|
#region Async Operations
|
||||||
|
|
||||||
public async Task<bool> StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition)
|
/// <inheritdoc/>
|
||||||
|
public async Task<bool> StartDragAsync(IDragSource source, Point startPosition)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||||
@@ -264,60 +335,34 @@ public sealed class DragDropService : IDragDropService
|
|||||||
{
|
{
|
||||||
Interlocked.Increment(ref _totalDragOperations);
|
Interlocked.Increment(ref _totalDragOperations);
|
||||||
|
|
||||||
Models.DragInfo? dragInfo = null;
|
DragInfo? dragInfo;
|
||||||
|
|
||||||
// Проверка возможности начала перетаскивания
|
// Пытаемся начать перетаскивание
|
||||||
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource)
|
if (EnableAsyncOperations)
|
||||||
{
|
{
|
||||||
var result = await ExecuteWithTimeoutAsync(
|
dragInfo = await ExecuteWithTimeoutAsync(
|
||||||
asyncSource.CanStartDragAsync(),
|
source.TryStartDragAsync(startPosition),
|
||||||
"CanStartDragAsync",
|
"TryStartDragAsync",
|
||||||
source);
|
source);
|
||||||
|
|
||||||
if (!result.CanStart || result.DragInfo == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
dragInfo = result.DragInfo;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var startDragResult = await source.CanStartDragAsync();
|
dragInfo = await source.TryStartDragAsync(startPosition);
|
||||||
if (!startDragResult.CanStart || startDragResult.DragInfo == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
dragInfo = startDragResult.DragInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dragInfo == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
var updatedDragInfo = dragInfo.CloneWithPosition(startPosition);
|
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)
|
lock (_dragOperationLock)
|
||||||
{
|
{
|
||||||
_currentDragOperation = new DragOperationContext
|
_currentDragOperation = new DragOperationContext
|
||||||
{
|
{
|
||||||
Source = source,
|
Source = source,
|
||||||
DragInfo = updatedDragInfo,
|
DragInfo = updatedDragInfo,
|
||||||
CancellationTokenSource = new CancellationTokenSource()
|
CancellationTokenSource = new CancellationTokenSource(),
|
||||||
|
LastPosition = startPosition
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +378,8 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateDragAsync(Geometry.Point position)
|
/// <inheritdoc/>
|
||||||
|
public async Task UpdateDragAsync(Point position)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
@@ -359,8 +405,9 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updatedDragInfo = context.DragInfo.CloneWithPosition(position);
|
var updatedDragInfo = context.DragInfo.CloneWithPosition(position);
|
||||||
context.DragInfo = updatedDragInfo;
|
|
||||||
context.DragInfo.Dispose();
|
context.DragInfo.Dispose();
|
||||||
|
context.DragInfo = updatedDragInfo;
|
||||||
|
context.LastPosition = position;
|
||||||
|
|
||||||
// Поиск новой цели сброса
|
// Поиск новой цели сброса
|
||||||
var newDropTarget = await FindDropTargetAsync(position, updatedDragInfo);
|
var newDropTarget = await FindDropTargetAsync(position, updatedDragInfo);
|
||||||
@@ -372,9 +419,8 @@ public sealed class DragDropService : IDragDropService
|
|||||||
{
|
{
|
||||||
await ExecuteTargetOperationAsync(
|
await ExecuteTargetOperationAsync(
|
||||||
context.CurrentDropTarget,
|
context.CurrentDropTarget,
|
||||||
t => t.DragLeaveAsync(),
|
t => t.OnDragLeaveAsync(),
|
||||||
t => t.DragLeaveAsync(),
|
"OnDragLeave");
|
||||||
"DragLeave");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context.CurrentDropTarget = newDropTarget?.Target;
|
context.CurrentDropTarget = newDropTarget?.Target;
|
||||||
@@ -383,14 +429,14 @@ public sealed class DragDropService : IDragDropService
|
|||||||
{
|
{
|
||||||
newDropTarget.UsageCount++;
|
newDropTarget.UsageCount++;
|
||||||
_dropTargetChanged?.Invoke(this, new DropTargetChangedEventArgs(
|
_dropTargetChanged?.Invoke(this, new DropTargetChangedEventArgs(
|
||||||
updatedDragInfo, newDropTarget.Target, newDropTarget.Bounds));
|
updatedDragInfo, position, newDropTarget.Target, newDropTarget.Bounds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Уведомление текущей цели
|
// Уведомление текущей цели
|
||||||
if (context.CurrentDropTarget != null)
|
if (context.CurrentDropTarget != null)
|
||||||
{
|
{
|
||||||
var dropInfo = new Models.DropInfo(
|
var dropInfo = new DropInfo(
|
||||||
updatedDragInfo.Data,
|
updatedDragInfo.Data,
|
||||||
position,
|
position,
|
||||||
updatedDragInfo.AllowedEffects,
|
updatedDragInfo.AllowedEffects,
|
||||||
@@ -398,9 +444,8 @@ public sealed class DragDropService : IDragDropService
|
|||||||
|
|
||||||
await ExecuteTargetOperationAsync(
|
await ExecuteTargetOperationAsync(
|
||||||
context.CurrentDropTarget,
|
context.CurrentDropTarget,
|
||||||
t => t.DragOverAsync(dropInfo),
|
t => t.OnDragOverAsync(dropInfo),
|
||||||
t => t.DragOverAsync(dropInfo),
|
"OnDragOver");
|
||||||
"DragOver");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_dragUpdated?.Invoke(this, new DragUpdatedEventArgs(updatedDragInfo, position));
|
_dragUpdated?.Invoke(this, new DragUpdatedEventArgs(updatedDragInfo, position));
|
||||||
@@ -412,7 +457,8 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Enums.DragDropEffects> EndDragAsync(Geometry.Point position)
|
/// <inheritdoc/>
|
||||||
|
public async Task<DragDropEffects> EndDragAsync(Point position)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
@@ -426,19 +472,19 @@ public sealed class DragDropService : IDragDropService
|
|||||||
if (context == null || context.DragInfo == null || context.Source == null)
|
if (context == null || context.DragInfo == null || context.Source == null)
|
||||||
{
|
{
|
||||||
Reset();
|
Reset();
|
||||||
return Enums.DragDropEffects.None;
|
return DragDropEffects.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var effects = Enums.DragDropEffects.None;
|
var effects = DragDropEffects.None;
|
||||||
var operationTime = DateTime.UtcNow - context.StartTime;
|
var operationTime = DateTime.UtcNow - context.StartTime;
|
||||||
Interlocked.Add(ref _totalOperationTicks, operationTime.Ticks);
|
Interlocked.Add(ref _totalOperationTicks, operationTime.Ticks);
|
||||||
|
|
||||||
// Выполнение сброса
|
// Выполнение сброса
|
||||||
if (context.CurrentDropTarget != null)
|
if (context.CurrentDropTarget != null)
|
||||||
{
|
{
|
||||||
var dropInfo = new Models.DropInfo(
|
var dropInfo = new DropInfo(
|
||||||
context.DragInfo.Data,
|
context.DragInfo.Data,
|
||||||
position,
|
position,
|
||||||
context.DragInfo.AllowedEffects,
|
context.DragInfo.AllowedEffects,
|
||||||
@@ -446,9 +492,8 @@ public sealed class DragDropService : IDragDropService
|
|||||||
|
|
||||||
await ExecuteTargetOperationAsync(
|
await ExecuteTargetOperationAsync(
|
||||||
context.CurrentDropTarget,
|
context.CurrentDropTarget,
|
||||||
t => t.DropAsync(dropInfo),
|
t => t.OnDropAsync(dropInfo),
|
||||||
t => t.DropAsync(dropInfo),
|
"OnDrop");
|
||||||
"Drop");
|
|
||||||
|
|
||||||
if (dropInfo.Handled)
|
if (dropInfo.Handled)
|
||||||
{
|
{
|
||||||
@@ -460,10 +505,8 @@ public sealed class DragDropService : IDragDropService
|
|||||||
// Уведомление источника
|
// Уведомление источника
|
||||||
await ExecuteSourceOperationAsync(
|
await ExecuteSourceOperationAsync(
|
||||||
context.Source,
|
context.Source,
|
||||||
s => s.DragCompletedAsync(context.DragInfo, effects),
|
s => s.OnDragCompletedAsync(context.DragInfo, effects),
|
||||||
s => s.DragCompletedAsync(context.DragInfo, effects),
|
"OnDragCompleted");
|
||||||
"DragCompleted",
|
|
||||||
effects);
|
|
||||||
|
|
||||||
// Событие завершения
|
// Событие завершения
|
||||||
_dragCompleted?.Invoke(this, new DragCompletedEventArgs(
|
_dragCompleted?.Invoke(this, new DragCompletedEventArgs(
|
||||||
@@ -475,7 +518,7 @@ public sealed class DragDropService : IDragDropService
|
|||||||
{
|
{
|
||||||
Interlocked.Increment(ref _errorCount);
|
Interlocked.Increment(ref _errorCount);
|
||||||
HandleError(ex, "EndDragAsync", context);
|
HandleError(ex, "EndDragAsync", context);
|
||||||
return Enums.DragDropEffects.None;
|
return DragDropEffects.None;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -483,6 +526,7 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public async Task CancelDragAsync()
|
public async Task CancelDragAsync()
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -507,11 +551,10 @@ public sealed class DragDropService : IDragDropService
|
|||||||
|
|
||||||
await ExecuteSourceOperationAsync(
|
await ExecuteSourceOperationAsync(
|
||||||
context.Source,
|
context.Source,
|
||||||
s => s.DragCancelledAsync(context.DragInfo),
|
s => s.OnDragCancelledAsync(context.DragInfo),
|
||||||
s => s.DragCancelledAsync(context.DragInfo),
|
"OnDragCancelled");
|
||||||
"DragCancelled");
|
|
||||||
|
|
||||||
_dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo));
|
_dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo, context.LastPosition));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -528,6 +571,7 @@ public sealed class DragDropService : IDragDropService
|
|||||||
|
|
||||||
#region Utility Methods
|
#region Utility Methods
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public void ClearAllDropTargets()
|
public void ClearAllDropTargets()
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -547,6 +591,7 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public DragDropStats GetStats()
|
public DragDropStats GetStats()
|
||||||
{
|
{
|
||||||
return new DragDropStats
|
return new DragDropStats
|
||||||
@@ -609,23 +654,22 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteTargetOperationAsync(
|
private async Task ExecuteTargetOperationAsync(
|
||||||
Abstractions.IDropTarget target,
|
IDropTarget target,
|
||||||
Func<Abstractions.IDropTarget, Task> asyncOperation,
|
Func<IDropTarget, Task> operation,
|
||||||
Action<Abstractions.IDropTarget> syncOperation,
|
|
||||||
string operationName)
|
string operationName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (EnableAsyncOperations && target is Abstractions.IDropTarget asyncTarget)
|
if (EnableAsyncOperations)
|
||||||
{
|
{
|
||||||
await ExecuteWithTimeoutAsync(
|
await ExecuteWithTimeoutAsync(
|
||||||
asyncOperation(asyncTarget),
|
operation(target),
|
||||||
$"{operationName}Async",
|
$"{operationName}Async",
|
||||||
target);
|
target);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
syncOperation(target);
|
await operation(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -636,24 +680,22 @@ public sealed class DragDropService : IDragDropService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteSourceOperationAsync(
|
private async Task ExecuteSourceOperationAsync(
|
||||||
Abstractions.IDragSource source,
|
IDragSource source,
|
||||||
Func<Abstractions.IDragSource, Task> asyncOperation,
|
Func<IDragSource, Task> operation,
|
||||||
Action<Abstractions.IDragSource> syncOperation,
|
string operationName)
|
||||||
string operationName,
|
|
||||||
Enums.DragDropEffects effects = Enums.DragDropEffects.None)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource)
|
if (EnableAsyncOperations)
|
||||||
{
|
{
|
||||||
await ExecuteWithTimeoutAsync(
|
await ExecuteWithTimeoutAsync(
|
||||||
asyncOperation(asyncSource),
|
operation(source),
|
||||||
$"{operationName}Async",
|
$"{operationName}Async",
|
||||||
source);
|
source);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
syncOperation(source);
|
await operation(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using Lattice.Core.DragDrop.Models;
|
|
||||||
|
|
||||||
namespace Lattice.Core.DragDrop.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Аргументы события отмены перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public class DragCancelledEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Информация о перетаскивании.
|
|
||||||
/// </summary>
|
|
||||||
public DragInfo DragInfo { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Инициализирует новый экземпляр класса <see cref="DragCancelledEventArgs"/>.
|
|
||||||
/// </summary>
|
|
||||||
public DragCancelledEventArgs(DragInfo dragInfo)
|
|
||||||
{
|
|
||||||
DragInfo = dragInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using Lattice.Core.DragDrop.Models;
|
|
||||||
using Lattice.Core.Geometry;
|
|
||||||
|
|
||||||
namespace Lattice.Core.DragDrop.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Аргументы события завершения перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public class DragCompletedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Информация о перетаскивании.
|
|
||||||
/// </summary>
|
|
||||||
public DragInfo DragInfo { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Позиция завершения перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public Point DropPosition { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Примененные эффекты перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public Enums.DragDropEffects Effects { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Инициализирует новый экземпляр класса <see cref="DragCompletedEventArgs"/>.
|
|
||||||
/// </summary>
|
|
||||||
public DragCompletedEventArgs(DragInfo dragInfo, Point dropPosition, Enums.DragDropEffects effects)
|
|
||||||
{
|
|
||||||
DragInfo = dragInfo;
|
|
||||||
DropPosition = dropPosition;
|
|
||||||
Effects = effects;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
namespace Lattice.Core.DragDrop.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Аргументы события ошибки в операции перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public class DragDropErrorEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Ошибка, которая произошла.
|
|
||||||
/// </summary>
|
|
||||||
public Exception Exception { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Операция, во время которой произошла ошибка.
|
|
||||||
/// </summary>
|
|
||||||
public string Operation { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Контекст операции.
|
|
||||||
/// </summary>
|
|
||||||
public object? Context { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Инициализирует новый экземпляр класса <see cref="DragDropErrorEventArgs"/>.
|
|
||||||
/// </summary>
|
|
||||||
public DragDropErrorEventArgs(Exception exception, string operation, object? context = null)
|
|
||||||
{
|
|
||||||
Exception = exception;
|
|
||||||
Operation = operation;
|
|
||||||
Context = context;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
237
Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs
Normal file
237
Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
using Lattice.Core.DragDrop.Models;
|
||||||
|
using Lattice.Core.Geometry;
|
||||||
|
|
||||||
|
namespace Lattice.Core.DragDrop.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Предоставляет базовые данные для событий перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот класс содержит общие свойства, которые используются в большинстве событий
|
||||||
|
/// системы перетаскивания. Является базовым классом для специализированных событий.
|
||||||
|
/// </remarks>
|
||||||
|
public abstract class DragEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает информацию о текущей операции перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Объект <see cref="DragInfo"/>, содержащий данные, эффекты и метаданные операции.
|
||||||
|
/// Всегда возвращает актуальную информацию на момент возникновения события.
|
||||||
|
/// </value>
|
||||||
|
public DragInfo DragInfo { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает текущую позицию курсора в координатах экрана.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Точка, представляющая положение курсора мыши в момент события.
|
||||||
|
/// Используется для точного позиционирования и визуальной обратной связи.
|
||||||
|
/// </value>
|
||||||
|
public Point Position { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="DragEventArgs"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
||||||
|
/// <param name="position">Текущая позиция курсора.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, когда <paramref name="dragInfo"/> равен null.
|
||||||
|
/// </exception>
|
||||||
|
protected DragEventArgs(DragInfo dragInfo, Point position)
|
||||||
|
{
|
||||||
|
DragInfo = dragInfo ?? throw new ArgumentNullException(nameof(dragInfo));
|
||||||
|
Position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Предоставляет данные для события начала перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Возникает, когда пользователь начинает операцию перетаскивания.
|
||||||
|
/// Это первое событие в жизненном цикле операции.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class DragStartedEventArgs : DragEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="DragStartedEventArgs"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
||||||
|
/// <param name="position">Начальная позиция перетаскивания.</param>
|
||||||
|
public DragStartedEventArgs(DragInfo dragInfo, Point position)
|
||||||
|
: base(dragInfo, position)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Предоставляет данные для события обновления позиции перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Возникает при каждом перемещении курсора во время операции перетаскивания.
|
||||||
|
/// Может вызываться многократно с высокой частотой, поэтому обработчики
|
||||||
|
/// должны быть оптимизированы для производительности.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class DragUpdatedEventArgs : DragEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="DragUpdatedEventArgs"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
||||||
|
/// <param name="position">Текущая позиция курсора.</param>
|
||||||
|
public DragUpdatedEventArgs(DragInfo dragInfo, Point position)
|
||||||
|
: base(dragInfo, position)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Предоставляет данные для события завершения перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Возникает, когда пользователь завершает операцию перетаскивания
|
||||||
|
/// (отпускает кнопку мыши над целью или вне области сброса).
|
||||||
|
/// Содержит информацию о примененных эффектах и результатах операции.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class DragCompletedEventArgs : DragEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает эффекты, примененные при завершении операции.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Комбинация флагов <see cref="Enums.DragDropEffects"/>, указывающая,
|
||||||
|
/// как были обработаны данные (копирование, перемещение и т.д.).
|
||||||
|
/// </value>
|
||||||
|
public Enums.DragDropEffects Effects { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="DragCompletedEventArgs"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
||||||
|
/// <param name="position">Позиция завершения операции.</param>
|
||||||
|
/// <param name="effects">Примененные эффекты перетаскивания.</param>
|
||||||
|
public DragCompletedEventArgs(DragInfo dragInfo, Point position, Enums.DragDropEffects effects)
|
||||||
|
: base(dragInfo, position)
|
||||||
|
{
|
||||||
|
Effects = effects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Предоставляет данные для события отмены перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Возникает, когда операция перетаскивания была отменена пользователем
|
||||||
|
/// (например, нажатием клавиши Escape) или системой (например, при ошибке).
|
||||||
|
/// После этого события система возвращается в исходное состояние.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class DragCancelledEventArgs : DragEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="DragCancelledEventArgs"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
||||||
|
/// <param name="position">Позиция в момент отмены.</param>
|
||||||
|
public DragCancelledEventArgs(DragInfo dragInfo, Point position)
|
||||||
|
: base(dragInfo, position)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Предоставляет данные для события изменения цели сброса.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Возникает, когда курсор перемещается с одной цели сброса на другую
|
||||||
|
/// или покидает область всех целей. Позволяет обновлять визуальную
|
||||||
|
/// обратную связь при изменении контекста сброса.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class DropTargetChangedEventArgs : DragEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает новую цель сброса, над которой находится курсор.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Объект <see cref="Abstractions.IDropTarget"/>, готовый принять данные,
|
||||||
|
/// или null, если курсор покинул область всех целей.
|
||||||
|
/// </value>
|
||||||
|
public Abstractions.IDropTarget? Target { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает границы новой цели сброса.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Прямоугольник, определяющий область цели в координатах экрана.
|
||||||
|
/// Может использоваться для точного позиционирования визуальной обратной связи.
|
||||||
|
/// </value>
|
||||||
|
public Rect TargetBounds { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="DropTargetChangedEventArgs"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
||||||
|
/// <param name="position">Текущая позиция курсора.</param>
|
||||||
|
/// <param name="target">Новая цель сброса.</param>
|
||||||
|
/// <param name="targetBounds">Границы цели сброса.</param>
|
||||||
|
public DropTargetChangedEventArgs(DragInfo dragInfo, Point position, Abstractions.IDropTarget? target, Rect targetBounds)
|
||||||
|
: base(dragInfo, position)
|
||||||
|
{
|
||||||
|
Target = target;
|
||||||
|
TargetBounds = targetBounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Предоставляет данные для события ошибки в операции перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Возникает при возникновении исключения в любом из компонентов
|
||||||
|
/// системы перетаскивания. Позволяет централизованно обрабатывать ошибки
|
||||||
|
/// и предоставлять пользователю информацию о проблемах.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class DragDropErrorEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает исключение, вызвавшее ошибку.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Объект <see cref="Exception"/>, содержащий информацию об ошибке.
|
||||||
|
/// Может быть любого типа, в зависимости от источника ошибки.
|
||||||
|
/// </value>
|
||||||
|
public Exception Exception { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает название операции, во время которой произошла ошибка.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Строка, идентифицирующая операцию (например, "StartDragAsync",
|
||||||
|
/// "OnDropAsync", "UpdateDropTargetBounds").
|
||||||
|
/// </value>
|
||||||
|
public string Operation { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает контекст, в котором произошла ошибка.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Объект, содержащий дополнительную информацию о контексте ошибки,
|
||||||
|
/// или null, если контекст недоступен.
|
||||||
|
/// </value>
|
||||||
|
public object? Context { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="DragDropErrorEventArgs"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exception">Исключение, вызвавшее ошибку.</param>
|
||||||
|
/// <param name="operation">Название операции.</param>
|
||||||
|
/// <param name="context">Контекст ошибки.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, когда <paramref name="exception"/> или <paramref name="operation"/> равны null.
|
||||||
|
/// </exception>
|
||||||
|
public DragDropErrorEventArgs(Exception exception, string operation, object? context = null)
|
||||||
|
{
|
||||||
|
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
||||||
|
Operation = operation ?? throw new ArgumentNullException(nameof(operation));
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using Lattice.Core.DragDrop.Models;
|
|
||||||
using Lattice.Core.Geometry;
|
|
||||||
|
|
||||||
namespace Lattice.Core.DragDrop.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Аргументы события начала перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public class DragStartedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Информация о перетаскивании.
|
|
||||||
/// </summary>
|
|
||||||
public DragInfo DragInfo { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Начальная позиция перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public Point StartPosition { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Инициализирует новый экземпляр класса <see cref="DragStartedEventArgs"/>.
|
|
||||||
/// </summary>
|
|
||||||
public DragStartedEventArgs(DragInfo dragInfo, Point startPosition)
|
|
||||||
{
|
|
||||||
DragInfo = dragInfo;
|
|
||||||
StartPosition = startPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using Lattice.Core.DragDrop.Models;
|
|
||||||
using Lattice.Core.Geometry;
|
|
||||||
|
|
||||||
namespace Lattice.Core.DragDrop.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Аргументы события обновления перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public class DragUpdatedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Информация о перетаскивании.
|
|
||||||
/// </summary>
|
|
||||||
public DragInfo DragInfo { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Текущая позиция перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public Point Position { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Инициализирует новый экземпляр класса <see cref="DragUpdatedEventArgs"/>.
|
|
||||||
/// </summary>
|
|
||||||
public DragUpdatedEventArgs(DragInfo dragInfo, Point position)
|
|
||||||
{
|
|
||||||
DragInfo = dragInfo;
|
|
||||||
Position = position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using Lattice.Core.DragDrop.Models;
|
|
||||||
using Lattice.Core.Geometry;
|
|
||||||
|
|
||||||
namespace Lattice.Core.DragDrop.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Аргументы события изменения цели сброса.
|
|
||||||
/// </summary>
|
|
||||||
public class DropTargetChangedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Информация о перетаскивании.
|
|
||||||
/// </summary>
|
|
||||||
public DragInfo DragInfo { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Новая цель сброса.
|
|
||||||
/// </summary>
|
|
||||||
public Abstractions.IDropTarget Target { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Границы цели.
|
|
||||||
/// </summary>
|
|
||||||
public Rect TargetBounds { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Инициализирует новый экземпляр класса <see cref="DropTargetChangedEventArgs"/>.
|
|
||||||
/// </summary>
|
|
||||||
public DropTargetChangedEventArgs(DragInfo dragInfo, Abstractions.IDropTarget target, Rect targetBounds)
|
|
||||||
{
|
|
||||||
DragInfo = dragInfo;
|
|
||||||
Target = target;
|
|
||||||
TargetBounds = targetBounds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,33 +8,51 @@ public interface IDragDropService : IDisposable
|
|||||||
#region Свойства
|
#region Свойства
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Активна ли операция перетаскивания.
|
/// Получает значение, указывающее, активна ли операция перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>true, если операция перетаскивания активна; в противном случае — false.</value>
|
||||||
bool IsDragActive { get; }
|
bool IsDragActive { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Информация о текущей операции.
|
/// Получает информацию о текущей операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Объект <see cref="Models.DragInfo"/>, содержащий данные текущей операции,
|
||||||
|
/// или null, если операция не активна.
|
||||||
|
/// </value>
|
||||||
Models.DragInfo? CurrentDragInfo { get; }
|
Models.DragInfo? CurrentDragInfo { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Текущая цель сброса.
|
/// Получает текущую цель сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Объект <see cref="Abstractions.IDropTarget"/>, над которым находится курсор,
|
||||||
|
/// или null, если курсор не над зарегистрированной целью.
|
||||||
|
/// </value>
|
||||||
Abstractions.IDropTarget? CurrentDropTarget { get; }
|
Abstractions.IDropTarget? CurrentDropTarget { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Порог начала перетаскивания в пикселях.
|
/// Получает или задает порог начала перетаскивания в пикселях.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Минимальное расстояние, которое должен пройти курсор мыши, чтобы начать операцию перетаскивания.
|
||||||
|
/// Значение по умолчанию: <see cref="Constants.DragDropConstants.DefaultDragThreshold"/>.
|
||||||
|
/// </value>
|
||||||
double DragStartThreshold { get; set; }
|
double DragStartThreshold { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Включены ли асинхронные операции.
|
/// Получает или задает значение, указывающее, включены ли асинхронные операции.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>true, если асинхронные операции включены; в противном случае — false.</value>
|
||||||
bool EnableAsyncOperations { get; set; }
|
bool EnableAsyncOperations { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Максимальное время ожидания асинхронной операции (мс).
|
/// Получает или задает максимальное время ожидания асинхронной операции в миллисекундах.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Время ожидания в миллисекундах. Значение 0 или меньше означает отсутствие таймаута.
|
||||||
|
/// Значение по умолчанию: <see cref="Constants.DragDropConstants.DefaultAsyncTimeout"/>.
|
||||||
|
/// </value>
|
||||||
int AsyncOperationTimeout { get; set; }
|
int AsyncOperationTimeout { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -42,32 +60,32 @@ public interface IDragDropService : IDisposable
|
|||||||
#region События
|
#region События
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Событие начала операции перетаскивания.
|
/// Происходит при начале операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<DragStartedEventArgs> DragStarted;
|
event EventHandler<DragStartedEventArgs> DragStarted;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Событие обновления позиции перетаскивания.
|
/// Происходит при обновлении позиции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<DragUpdatedEventArgs> DragUpdated;
|
event EventHandler<DragUpdatedEventArgs> DragUpdated;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Событие изменения цели сброса.
|
/// Происходит при изменении цели сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<DropTargetChangedEventArgs> DropTargetChanged;
|
event EventHandler<DropTargetChangedEventArgs> DropTargetChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Событие завершения операции перетаскивания.
|
/// Происходит при завершении операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<DragCompletedEventArgs> DragCompleted;
|
event EventHandler<DragCompletedEventArgs> DragCompleted;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Событие отмены операции перетаскивания.
|
/// Происходит при отмене операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<DragCancelledEventArgs> DragCancelled;
|
event EventHandler<DragCancelledEventArgs> DragCancelled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Событие ошибки в операции перетаскивания.
|
/// Происходит при возникновении ошибки в операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<DragDropErrorEventArgs> ErrorOccurred;
|
event EventHandler<DragDropErrorEventArgs> ErrorOccurred;
|
||||||
|
|
||||||
@@ -76,23 +94,39 @@ public interface IDragDropService : IDisposable
|
|||||||
#region Регистрация целей сброса
|
#region Регистрация целей сброса
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Регистрирует цель сброса.
|
/// Регистрирует цель сброса в системе.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="target">Цель сброса для регистрации.</param>
|
||||||
|
/// <param name="bounds">Границы области цели в координатах экрана.</param>
|
||||||
|
/// <param name="priority">Приоритет цели (высшие значения обрабатываются первыми).</param>
|
||||||
|
/// <param name="group">Имя группы для групповой отмены регистрации.</param>
|
||||||
|
/// <returns>Уникальный идентификатор зарегистрированной цели.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Выбрасывается, когда <paramref name="target"/> равен null.</exception>
|
||||||
|
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
|
||||||
string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null);
|
string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обновляет границы цели сброса.
|
/// Обновляет границы цели сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="id">Идентификатор цели сброса.</param>
|
||||||
|
/// <param name="bounds">Новые границы области цели.</param>
|
||||||
|
/// <returns>true, если границы успешно обновлены; в противном случае — false.</returns>
|
||||||
|
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
|
||||||
bool UpdateDropTargetBounds(string id, Geometry.Rect bounds);
|
bool UpdateDropTargetBounds(string id, Geometry.Rect bounds);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Отменяет регистрацию цели сброса.
|
/// Отменяет регистрацию цели сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="id">Идентификатор цели сброса.</param>
|
||||||
|
/// <returns>true, если цель успешно удалена; в противном случае — false.</returns>
|
||||||
|
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
|
||||||
bool UnregisterDropTarget(string id);
|
bool UnregisterDropTarget(string id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Отменяет регистрацию всех целей в группе.
|
/// Отменяет регистрацию всех целей сброса в указанной группе.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="group">Имя группы для удаления.</param>
|
||||||
|
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
|
||||||
void UnregisterDropTargetsInGroup(string group);
|
void UnregisterDropTargetsInGroup(string group);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -100,23 +134,54 @@ public interface IDragDropService : IDisposable
|
|||||||
#region Асинхронные операции
|
#region Асинхронные операции
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Начинает операцию перетаскивания (асинхронно).
|
/// Начинает операцию перетаскивания из указанной позиции.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="source">Источник данных для перетаскивания.</param>
|
||||||
|
/// <param name="startPosition">Начальная позиция операции в координатах экрана.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Задача, представляющая асинхронную операцию. Результат содержит true, если операция успешно начата;
|
||||||
|
/// в противном случае — false.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Выбрасывается, когда <paramref name="source"/> равен null.</exception>
|
||||||
|
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот метод следует вызывать в ответ на событие нажатия кнопки мыши или начала жеста перетаскивания.
|
||||||
|
/// </remarks>
|
||||||
Task<bool> StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition);
|
Task<bool> StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обновляет позицию перетаскивания (асинхронно).
|
/// Обновляет позицию текущей операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="position">Новая позиция курсора в координатах экрана.</param>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
|
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот метод следует вызывать при каждом перемещении мыши во время операции перетаскивания.
|
||||||
|
/// </remarks>
|
||||||
Task UpdateDragAsync(Geometry.Point position);
|
Task UpdateDragAsync(Geometry.Point position);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Завершает операцию перетаскивания (асинхронно).
|
/// Завершает текущую операцию перетаскивания в указанной позиции.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="position">Позиция завершения операции в координатах экрана.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Задача, представляющая асинхронную операцию. Результат содержит эффекты, примененные при завершении операции.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот метод следует вызывать при отпускании кнопки мыши или завершении жеста перетаскивания.
|
||||||
|
/// </remarks>
|
||||||
Task<Enums.DragDropEffects> EndDragAsync(Geometry.Point position);
|
Task<Enums.DragDropEffects> EndDragAsync(Geometry.Point position);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Отменяет операцию перетаскивания (асинхронно).
|
/// Отменяет текущую операцию перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
|
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот метод следует вызывать при отмене операции пользователем (например, нажатием клавиши Escape)
|
||||||
|
/// или при возникновении ошибки.
|
||||||
|
/// </remarks>
|
||||||
Task CancelDragAsync();
|
Task CancelDragAsync();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -124,27 +189,52 @@ public interface IDragDropService : IDisposable
|
|||||||
#region Утилиты
|
#region Утилиты
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Очищает все зарегистрированные цели.
|
/// Очищает все зарегистрированные цели сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Выбрасывается, если сервис был удален.</exception>
|
||||||
void ClearAllDropTargets();
|
void ClearAllDropTargets();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает статистику использования.
|
/// Получает статистику использования системы перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <returns>Объект <see cref="DragDropStats"/> со статистикой использования.</returns>
|
||||||
DragDropStats GetStats();
|
DragDropStats GetStats();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Статистика использования Drag & Drop.
|
/// Содержит статистику использования системы перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DragDropStats
|
public class DragDropStats
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает общее количество операций перетаскивания.
|
||||||
|
/// </summary>
|
||||||
public int TotalDragOperations { get; set; }
|
public int TotalDragOperations { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает количество успешных сбросов.
|
||||||
|
/// </summary>
|
||||||
public int SuccessfulDrops { get; set; }
|
public int SuccessfulDrops { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает количество отмененных операций.
|
||||||
|
/// </summary>
|
||||||
public int CancelledOperations { get; set; }
|
public int CancelledOperations { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает количество ошибок.
|
||||||
|
/// </summary>
|
||||||
public int ErrorCount { get; set; }
|
public int ErrorCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает количество зарегистрированных целей сброса.
|
||||||
|
/// </summary>
|
||||||
public int RegisteredTargets { get; set; }
|
public int RegisteredTargets { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает среднее время операции перетаскивания.
|
||||||
|
/// </summary>
|
||||||
public TimeSpan AverageOperationTime { get; set; }
|
public TimeSpan AverageOperationTime { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
using Lattice.Core.DragDrop.Abstractions;
|
|
||||||
using Lattice.Core.DragDrop.Enums;
|
|
||||||
using Lattice.Core.DragDrop.Models;
|
|
||||||
|
|
||||||
namespace Lattice.Core.DragDrop.Utilities;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Предоставляет утилитарные методы и фабричные методы для работы с системой перетаскивания с поддержкой async.
|
|
||||||
/// </summary>
|
|
||||||
public static class AsyncDragDropUtilities
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Создает асинхронную реализацию источника перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
public static IDragSource CreateAsyncDragSource(
|
|
||||||
Func<Task<object>> dataProviderAsync,
|
|
||||||
Func<Task<bool>>? canDragAsync = null,
|
|
||||||
Func<DragInfo, DragDropEffects, Task>? onCompletedAsync = null,
|
|
||||||
Func<DragInfo, Task>? onCancelledAsync = null)
|
|
||||||
{
|
|
||||||
return new AsyncDragSourceWrapper(dataProviderAsync, canDragAsync, onCompletedAsync, onCancelledAsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Создает асинхронную реализацию цели сброса.
|
|
||||||
/// </summary>
|
|
||||||
public static IDropTarget CreateAsyncDropTarget(
|
|
||||||
Func<DropInfo, Task<bool>>? canAcceptAsync = null,
|
|
||||||
Func<DropInfo, Task>? onDragOverAsync = null,
|
|
||||||
Func<DropInfo, Task>? onDropAsync = null,
|
|
||||||
Func<Task>? onDragLeaveAsync = null)
|
|
||||||
{
|
|
||||||
return new AsyncDropTargetWrapper(canAcceptAsync, onDragOverAsync, onDropAsync, onDragLeaveAsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#region Factory Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Создает информацию о перетаскивании.
|
|
||||||
/// </summary>
|
|
||||||
public static Models.DragInfo CreateDragInfo(
|
|
||||||
object data,
|
|
||||||
Geometry.Point startPosition,
|
|
||||||
Enums.DragDropEffects allowedEffects = Enums.DragDropEffects.Copy | Enums.DragDropEffects.Move,
|
|
||||||
object? source = null,
|
|
||||||
Dictionary<string, object>? parameters = null)
|
|
||||||
{
|
|
||||||
var dragInfo = new Models.DragInfo(data, allowedEffects, startPosition, source);
|
|
||||||
|
|
||||||
if (parameters != null)
|
|
||||||
{
|
|
||||||
foreach (var param in parameters)
|
|
||||||
{
|
|
||||||
dragInfo.SetParameter(param.Key, param.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dragInfo;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Обертки-реализации
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Обертка для создания асинхронного источника перетаскивания.
|
|
||||||
/// </summary>
|
|
||||||
private sealed class AsyncDragSourceWrapper : IDragSource
|
|
||||||
{
|
|
||||||
private readonly Func<Task<object>> _dataProviderAsync;
|
|
||||||
private readonly Func<Task<bool>>? _canDragAsync;
|
|
||||||
private readonly Func<DragInfo, DragDropEffects, Task>? _onCompletedAsync;
|
|
||||||
private readonly Func<DragInfo, Task>? _onCancelledAsync;
|
|
||||||
|
|
||||||
public AsyncDragSourceWrapper(
|
|
||||||
Func<Task<object>> dataProviderAsync,
|
|
||||||
Func<Task<bool>>? canDragAsync = null,
|
|
||||||
Func<DragInfo, DragDropEffects, Task>? onCompletedAsync = null,
|
|
||||||
Func<DragInfo, Task>? onCancelledAsync = null)
|
|
||||||
{
|
|
||||||
_dataProviderAsync = dataProviderAsync ?? throw new ArgumentNullException(nameof(dataProviderAsync));
|
|
||||||
_canDragAsync = canDragAsync;
|
|
||||||
_onCompletedAsync = onCompletedAsync;
|
|
||||||
_onCancelledAsync = onCancelledAsync;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync(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<bool> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Обертка для создания асинхронной цели сброса.
|
|
||||||
/// </summary>
|
|
||||||
private sealed class AsyncDropTargetWrapper : IDropTarget
|
|
||||||
{
|
|
||||||
private readonly Func<DropInfo, Task<bool>>? _canAcceptAsync;
|
|
||||||
private readonly Func<DropInfo, Task>? _onDragOverAsync;
|
|
||||||
private readonly Func<DropInfo, Task>? _onDropAsync;
|
|
||||||
private readonly Func<Task>? _onDragLeaveAsync;
|
|
||||||
|
|
||||||
public AsyncDropTargetWrapper(
|
|
||||||
Func<DropInfo, Task<bool>>? canAcceptAsync = null,
|
|
||||||
Func<DropInfo, Task>? onDragOverAsync = null,
|
|
||||||
Func<DropInfo, Task>? onDropAsync = null,
|
|
||||||
Func<Task>? onDragLeaveAsync = null)
|
|
||||||
{
|
|
||||||
_canAcceptAsync = canAcceptAsync;
|
|
||||||
_onDragOverAsync = onDragOverAsync;
|
|
||||||
_onDropAsync = onDropAsync;
|
|
||||||
_onDragLeaveAsync = onDragLeaveAsync;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> CanAcceptDropAsync(DropInfo dropInfo, 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
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,547 +1,313 @@
|
|||||||
# Lattice.UI.DragDrop.WinUI
|
# Lattice.Core.DragDrop
|
||||||
|
|
||||||

|
Библиотека для реализации drag-and-drop (перетаскивания) в приложениях на .NET.
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Полнофункциональная реализация системы перетаскивания для WinUI 3 в составе Lattice UI Framework.
|
## 📋 Обзор
|
||||||
|
|
||||||
## 🎉 Демо
|
Lattice.Core.DragDrop предоставляет полнофункциональную, асинхронную и потокобезопасную систему для реализации операций перетаскивания в пользовательских интерфейсах. Библиотека построена на принципах разделения ответственности и поддерживает сложные сценарии перетаскивания с минимальными усилиями со стороны разработчика.
|
||||||
|
|
||||||

|
### ✨ Основные возможности
|
||||||
|
|
||||||
*Перетаскивание элементов между контейнерами и переупорядочивание списка*
|
- ✅ **Полностью асинхронный API** - все операции поддерживают async/await
|
||||||
|
- ✅ **Потокобезопасность** - безопасная работа в многопоточных средах
|
||||||
|
- ✅ **Расширяемая архитектура** - легко добавлять новые типы источников и целей
|
||||||
|
- ✅ **Подробные события** - полный контроль над жизненным циклом операций
|
||||||
|
- ✅ **Статистика и мониторинг** - встроенный сбор метрик использования
|
||||||
|
- ✅ **Поддержка CancellationToken** - корректная отмена длительных операций
|
||||||
|
- ✅ **Независимость от UI-фреймворков** - может использоваться с любым представлением
|
||||||
|
|
||||||
## 📦 Особенности
|
## 🏗️ Архитектура
|
||||||
|
|
||||||
✅ **Готовое решение для WinUI 3** - работает из коробки
|
### Основные компоненты
|
||||||
✅ **Attached Behaviors** - легко подключается к любым UIElement
|
|
||||||
✅ **Визуальная обратная связь** - анимации и подсветка
|
#### 1. **IDragSource**
|
||||||
✅ **Переупорядочивание элементов** - drag-and-drop в списках
|
Интерфейс для объектов, которые могут быть источником данных при перетаскивании. Определяет:
|
||||||
✅ **Кастомизация стилей** - полный контроль над внешним видом
|
- Возможность начала перетаскивания
|
||||||
✅ **Поддержка сложных сценариев** - вложенные элементы, зоны сброса
|
- Подготовку данных для передачи
|
||||||
✅ **Производительность** - оптимизировано для плавной работы
|
- Реакцию на завершение или отмену операции
|
||||||
|
|
||||||
|
#### 2. **IDropTarget**
|
||||||
|
Интерфейс для объектов, которые могут принимать сброшенные данные. Определяет:
|
||||||
|
- Проверку совместимости данных
|
||||||
|
- Визуальную обратную связь при наведении
|
||||||
|
- Обработку сброшенных данных
|
||||||
|
|
||||||
|
#### 3. **DragDropService**
|
||||||
|
Центральный сервис, координирующий все операции. Отвечает за:
|
||||||
|
- Регистрацию и управление целями сброса
|
||||||
|
- Оркестрацию жизненного цикла операций
|
||||||
|
- Распространение событий между компонентами
|
||||||
|
- Сбор статистики и обработку ошибок
|
||||||
|
|
||||||
|
#### 4. **Модели данных**
|
||||||
|
- **DragInfo** - информация о начале перетаскивания
|
||||||
|
- **DropInfo** - информация о потенциальном сбросе
|
||||||
|
- **DragDropEffects** - перечисление возможных эффектов
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
### 1. Установка
|
### 1. Установка
|
||||||
|
|
||||||
Добавьте пакет через NuGet:
|
```csharp
|
||||||
|
// Пример регистрации в DI-контейнере
|
||||||
```powershell
|
services.AddSingleton<IDragDropService, DragDropService>();
|
||||||
Install-Package Lattice.UI.DragDrop.WinUI
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Или через Package Manager:
|
### 2. Создание источника перетаскивания
|
||||||
|
|
||||||
```xml
|
|
||||||
<PackageReference Include="Lattice.UI.DragDrop.WinUI" Version="1.0.0" />
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Инициализация в приложении
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using Lattice.UI.DragDrop.WinUI.Extensions;
|
public class ItemDragSource : IDragSource
|
||||||
using Lattice.UI.DragDrop.WinUI.Helpers;
|
|
||||||
|
|
||||||
public MainWindow()
|
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
private readonly Item _item;
|
||||||
|
|
||||||
// Инициализация ресурсов
|
public ItemDragSource(Item item)
|
||||||
ResourceHelper.InitializeDragDropResources();
|
|
||||||
|
|
||||||
// Настройка примеров перетаскивания
|
|
||||||
SetupDragDropExamples();
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
{
|
||||||
Background = new SolidColorBrush(Colors.White),
|
_item = item;
|
||||||
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);
|
public async Task<DragInfo?> TryStartDragAsync(Point startPosition, CancellationToken ct)
|
||||||
reorderContainer.Children.Add(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<ControlTemplate>();
|
|
||||||
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
|
|
||||||
<!-- App.xaml или Generic.xaml -->
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="ms-appx:///Lattice.UI.DragDrop.WinUI/Themes/Generic.xaml" />
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
|
|
||||||
<!-- Переопределение стилей -->
|
|
||||||
<Style x:Key="CustomDragAdornerStyle" TargetType="dragDrop:DragAdorner">
|
|
||||||
<Setter Property="Background" Value="Red" />
|
|
||||||
<Setter Property="Opacity" Value="0.9" />
|
|
||||||
</Style>
|
|
||||||
</ResourceDictionary>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Визуальные состояния
|
|
||||||
|
|
||||||
Элементы поддерживают следующие визуальные состояния:
|
|
||||||
|
|
||||||
- `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()
|
|
||||||
{
|
{
|
||||||
// Делаем панель перетаскиваемой
|
// Проверяем, можно ли начать перетаскивание
|
||||||
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(),
|
await _repository.DeleteAsync(_item.Id, ct);
|
||||||
Title = Title
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Устанавливаем визуальную обратную связь
|
|
||||||
this.ApplyDragStyle();
|
|
||||||
this.EnableDragVisualFeedback();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class DockArea : ContentControl
|
public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken ct)
|
||||||
{
|
|
||||||
public DockArea()
|
|
||||||
{
|
{
|
||||||
// Область докинга принимает панели
|
// Очистка ресурсов при отмене
|
||||||
this.MakeDropTarget(typeof(DockPaneDragData));
|
return Task.CompletedTask;
|
||||||
|
|
||||||
// Кастомный обработчик
|
|
||||||
this.SetDropHandler(new DockDropHandler());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📊 Производительность
|
### 3. Создание цели сброса
|
||||||
|
|
||||||
### Оптимизации
|
|
||||||
|
|
||||||
1. **Минимальные перерисовки** - обновление только при необходимости
|
|
||||||
2. **Кэширование визуальных элементов** - повторное использование
|
|
||||||
3. **Эффективные алгоритмы поиска** - быстрый поиск целей сброса
|
|
||||||
4. **Асинхронная обработка** - не блокирует UI поток
|
|
||||||
|
|
||||||
### Рекомендации
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// ✅ Правильно
|
public class ContainerDropTarget : IDropTarget
|
||||||
element.MakeDragSource(data);
|
|
||||||
|
|
||||||
// ❌ Избегать
|
|
||||||
element.PointerPressed += (s, e) => { /* сложная логика */ };
|
|
||||||
element.PointerMoved += (s, e) => { /* частые обновления */ };
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🧪 Тестирование
|
|
||||||
|
|
||||||
### Модульные тесты
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
[TestClass]
|
|
||||||
public class DragDropTests
|
|
||||||
{
|
{
|
||||||
[TestMethod]
|
private readonly ObservableCollection<Item> _items;
|
||||||
public void MakeDragSource_EnablesDragging()
|
|
||||||
{
|
|
||||||
var element = new Border();
|
|
||||||
element.MakeDragSource("test");
|
|
||||||
|
|
||||||
Assert.IsTrue(element.IsDragSource());
|
public async Task<bool> CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct)
|
||||||
|
{
|
||||||
|
// Проверяем тип данных
|
||||||
|
if (dropInfo.Data is not Item item)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Проверяем бизнес-правила
|
||||||
|
return await _validator.CanAddItemAsync(item, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
public async Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct)
|
||||||
public void MakeDropTarget_AcceptsCorrectTypes()
|
|
||||||
{
|
{
|
||||||
var element = new Border();
|
// Обновляем визуальную обратную связь
|
||||||
element.MakeDropTarget(typeof(string), typeof(int));
|
dropInfo.SuggestedEffects = DragDropEffects.Move;
|
||||||
|
dropInfo.ShowVisualFeedback = true;
|
||||||
|
}
|
||||||
|
|
||||||
Assert.IsTrue(element.IsDropTarget());
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### UI тесты
|
### 4. Регистрация и использование сервиса
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
[TestClass]
|
public class MainViewModel
|
||||||
public class DragDropUITests
|
|
||||||
{
|
{
|
||||||
[UITestMethod]
|
private readonly IDragDropService _dragDropService;
|
||||||
public async Task DragAndDrop_BetweenElements()
|
|
||||||
|
public MainViewModel(IDragDropService dragDropService)
|
||||||
{
|
{
|
||||||
await UITestHelper.Run(async window =>
|
_dragDropService = dragDropService;
|
||||||
{
|
|
||||||
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));
|
_targetId = _dragDropService.RegisterDropTarget(
|
||||||
|
target: new ContainerDropTarget(_items),
|
||||||
|
bounds: new Rect(0, 0, 400, 300),
|
||||||
|
priority: 0,
|
||||||
|
group: "main-container"
|
||||||
|
);
|
||||||
|
|
||||||
// Симуляция перетаскивания
|
// Подписка на события
|
||||||
await SimulateDrag(source, target);
|
_dragDropService.DragStarted += OnDragStarted;
|
||||||
|
_dragDropService.DragCompleted += OnDragCompleted;
|
||||||
|
_dragDropService.ErrorOccurred += OnError;
|
||||||
|
}
|
||||||
|
|
||||||
// Проверка результатов
|
// Обработка мышиных событий
|
||||||
Assert.IsTrue(dropOccurred);
|
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
|
```csharp
|
||||||
// Включение подробного логирования
|
// Получение статистики использования
|
||||||
#if DEBUG
|
var stats = _dragDropService.GetStats();
|
||||||
DragDropDebugger.EnableLogging = true;
|
|
||||||
DragDropDebugger.LogLevel = LogLevel.Verbose;
|
Console.WriteLine($"Всего операций: {stats.TotalDragOperations}");
|
||||||
#endif
|
Console.WriteLine($"Успешных сбросов: {stats.SuccessfulDrops}");
|
||||||
|
Console.WriteLine($"Отменено операций: {stats.CancelledOperations}");
|
||||||
|
Console.WriteLine($"Среднее время операции: {stats.AverageOperationTime}");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Визуальные подсказки
|
## ⚙️ Конфигурация
|
||||||
|
|
||||||
|
### Параметры сервиса
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Показать границы целей сброса
|
// Настройка через свойства сервиса
|
||||||
DebugDropTargets.ShowBounds = true;
|
_dragDropService.DragStartThreshold = 5.0; // Порог в пикселях
|
||||||
|
_dragDropService.EnableAsyncOperations = true;
|
||||||
// Показать траекторию перетаскивания
|
_dragDropService.AsyncOperationTimeout = 3000; // 3 секунды
|
||||||
DebugDragTrail.Enabled = true;
|
|
||||||
DebugDragTrail.Color = Colors.Red;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📈 Производительность в production
|
### Константы по умолчанию
|
||||||
|
|
||||||
### Мониторинг
|
Все значения по умолчанию определены в классе `DragDropConstants`:
|
||||||
|
- `DefaultDragThreshold`: 3.0 пикселей
|
||||||
|
- `DefaultAsyncTimeout`: 5000 миллисекунд
|
||||||
|
- `TargetLifetimeMinutes`: 10 минут
|
||||||
|
|
||||||
|
## 🔧 Расширенные сценарии
|
||||||
|
|
||||||
|
### Группировка целей сброса
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Сбор метрик
|
// Регистрация группы целей
|
||||||
var metrics = DragDropPerformanceCollector.Collect();
|
_dragDropService.RegisterDropTarget(target1, bounds1, group: "panel");
|
||||||
Console.WriteLine($"Drag operations: {metrics.DragCount}");
|
_dragDropService.RegisterDropTarget(target2, bounds2, group: "panel");
|
||||||
Console.WriteLine($"Average drag time: {metrics.AverageDragTimeMs}ms");
|
|
||||||
Console.WriteLine($"Drop success rate: {metrics.DropSuccessRate:P}");
|
// Массовая отмена регистрации
|
||||||
|
_dragDropService.UnregisterDropTargetsInGroup("panel");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Оптимизация для больших списков
|
### Обработка ошибок
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Виртуализация для списков
|
private void OnError(object sender, DragDropErrorEventArgs e)
|
||||||
var virtualizingList = new ListView
|
|
||||||
{
|
{
|
||||||
ItemsSource = largeCollection,
|
_logger.LogError(e.Exception,
|
||||||
VirtualizingStackPanel.VirtualizationMode = VirtualizationMode.Recycling
|
"Ошибка в операции {Operation}",
|
||||||
};
|
e.Operation);
|
||||||
|
|
||||||
// Оптимизация перетаскивания
|
// Уведомление пользователя
|
||||||
virtualizingList.MakeDropTarget(typeof(DataItem));
|
_notificationService.ShowError("Ошибка при перетаскивании");
|
||||||
virtualizingList.SetDragDropOptimization(true);
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🤝 Интеграция с другими компонентами Lattice
|
### Кастомизация эффектов
|
||||||
|
|
||||||
### Toolbox
|
|
||||||
```csharp
|
```csharp
|
||||||
toolboxItem.MakeDragSource(new ToolboxItem
|
// Использование расширений для работы с эффектами
|
||||||
|
if (effects.CanCopy())
|
||||||
{
|
{
|
||||||
Type = typeof(Button),
|
// Логика для копирования
|
||||||
Icon = "🔘"
|
}
|
||||||
});
|
|
||||||
|
if (effects.CanMove())
|
||||||
|
{
|
||||||
|
// Логика для перемещения
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определение эффекта по модификаторам клавиш
|
||||||
|
var effect = DragDropEffectsExtensions.GetEffectFromKeys(
|
||||||
|
controlKey: Keyboard.IsControlDown,
|
||||||
|
shiftKey: Keyboard.IsShiftDown,
|
||||||
|
altKey: Keyboard.IsAltDown
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Property Grid
|
## 📝 Best Practices
|
||||||
```csharp
|
|
||||||
propertyItem.MakeDragSource(new PropertyValue
|
|
||||||
{
|
|
||||||
Name = "Background",
|
|
||||||
Value = Colors.Blue
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Layout System
|
1. **Производительность**
|
||||||
```csharp
|
- Методы `CanAcceptDropAsync` и `OnDragOverAsync` вызываются часто, оптимизируйте их
|
||||||
layoutPanel.MakeDropTarget(typeof(UIElement));
|
- Избегайте синхронных операций в обработчиках
|
||||||
layoutPanel.SetDropPositionHandler((element, position) =>
|
- Используйте кэширование при проверке типов данных
|
||||||
{
|
|
||||||
return CalculateDropZone(position);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📚 Дополнительные ресурсы
|
2. **Безопасность**
|
||||||
|
- Всегда проверяйте тип данных в `CanAcceptDropAsync`
|
||||||
|
- Валидируйте бизнес-правила перед обработкой сброса
|
||||||
|
- Используйте CancellationToken для отмены длительных операций
|
||||||
|
|
||||||
### Документация
|
3. **Пользовательский опыт**
|
||||||
- [Полная документация API](https://lattice-framework.github.io/ui-dragdrop/api/)
|
- Предоставляйте визуальную обратную связь через `DropInfo.ShowVisualFeedback`
|
||||||
- [Примеры использования](https://lattice-framework.github.io/ui-dragdrop/examples/)
|
- Используйте `DropInfo.VisualFeedbackData` для кастомизации отображения
|
||||||
- [Руководство по стилизации](https://lattice-framework.github.io/ui-dragdrop/styling/)
|
- Обрабатывайте отмену операций для очистки временных данных
|
||||||
|
|
||||||
### Видео туториалы
|
## 🔄 Миграция
|
||||||
- [Быстрый старт](https://youtube.com/playlist?list=...) - 15 минут
|
|
||||||
- [Продвинутые техники](https://youtube.com/playlist?list=...) - 45 минут
|
|
||||||
- [Интеграция с Docking](https://youtube.com/playlist?list=...) - 30 минут
|
|
||||||
|
|
||||||
### Сообщество
|
### С версии 1.x на 2.0
|
||||||
- [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) - технические вопросы
|
|
||||||
|
|
||||||
## 🐛 Отчет об ошибках
|
1. **Интерфейсы переименованы:**
|
||||||
|
- `CanStartDragAsync` → `TryStartDragAsync`
|
||||||
|
- `DragCompletedAsync` → `OnDragCompletedAsync`
|
||||||
|
- `DragCancelledAsync` → `OnDragCancelledAsync`
|
||||||
|
|
||||||
Нашли ошибку? [Создайте issue](https://github.com/lattice-framework/ui-dragdrop/issues) с подробным описанием:
|
2. **Классы EventArgs объединены:**
|
||||||
|
- Все события наследуются от `DragEventArgs`
|
||||||
|
- Упрощена иерархия классов событий
|
||||||
|
|
||||||
1. Шаги для воспроизведения
|
3. **Удалены устаревшие компоненты:**
|
||||||
2. Ожидаемое поведение
|
- `AsyncDragDropUtilities` удален
|
||||||
3. Фактическое поведение
|
- `ServiceCollectionExtensions` удален
|
||||||
4. Скриншоты или видео
|
|
||||||
5. Версии: Windows, WinUI, Lattice
|
|
||||||
|
|
||||||
## 🚀 Roadmap
|
|
||||||
|
|
||||||
### Версия 1.1 (Q2 2024)
|
|
||||||
- [ ] Поддержка touch-жестов
|
|
||||||
- [ ] Анимации с физикой
|
|
||||||
- [ ] Расширенная визуализация
|
|
||||||
|
|
||||||
### Версия 1.2 (Q3 2024)
|
|
||||||
- [ ] Интеграция с Windows Shell
|
|
||||||
- [ ] Поддержка виртуальных данных
|
|
||||||
- [ ] Улучшенная доступность
|
|
||||||
|
|
||||||
### Версия 2.0 (Q4 2024)
|
|
||||||
- [ ] Кроссплатформенная поддержка (Uno Platform)
|
|
||||||
- [ ] WebAssembly поддержка
|
|
||||||
- [ ] Расширенный набор контролов
|
|
||||||
|
|
||||||
## 📄 Лицензия
|
## 📄 Лицензия
|
||||||
|
|
||||||
MIT License. Подробности в файле [LICENSE](LICENSE).
|
Библиотека распространяется под лицензией MIT. Подробности см. в файле LICENSE.
|
||||||
|
|
||||||
## 👥 Авторы
|
## 🤝 Вклад в разработку
|
||||||
|
|
||||||
- **Команда Lattice Framework** - [@lattice-framework](https://github.com/lattice-framework)
|
Мы приветствуем вклад в развитие библиотеки. Перед отправкой pull request ознакомьтесь с руководством по контрибьютингу.
|
||||||
- **Главный разработчик** - [Ваше имя](https://github.com/yourusername)
|
|
||||||
|
|
||||||
## 🙏 Благодарности
|
## 🐛 Отчеты об ошибках
|
||||||
|
|
||||||
Спасибо сообществу WinUI и всем контрибьюторам, которые помогают улучшать проект!
|
Для сообщения об ошибках используйте Issues на GitHub. Пожалуйста, указывайте:
|
||||||
|
- Версию библиотеки
|
||||||
---
|
- Шаги для воспроизведения
|
||||||
|
- Ожидаемое и фактическое поведение
|
||||||
<div align="center">
|
- Пример кода для демонстрации проблемы
|
||||||
<p>
|
|
||||||
<strong>Lattice.UI.DragDrop.WinUI</strong> - часть <a href="https://github.com/lattice-framework">Lattice UI Framework</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/lattice-framework/ui-dragdrop">GitHub</a> •
|
|
||||||
<a href="https://lattice-framework.github.io">Документация</a> •
|
|
||||||
<a href="https://discord.gg/lattice">Discord</a> •
|
|
||||||
<a href="https://twitter.com/latticefw">Twitter</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
Reference in New Issue
Block a user