Доработан проект UI под новый Core
This commit is contained in:
@@ -5,37 +5,77 @@ namespace Lattice.UI.DragDrop.Abstractions;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Хост для отображения визуальных элементов перетаскивания.
|
/// Хост для отображения визуальных элементов перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Интерфейс предоставляет абстракцию для управления визуальными элементами
|
||||||
|
/// перетаскивания в различных UI-фреймворках (WPF, Avalonia, MAUI и т.д.).
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Реализация должна обеспечивать корректное отображение визуальных элементов
|
||||||
|
/// поверх других элементов UI и их своевременное удаление при завершении операций.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public interface IDragDropHost
|
public interface IDragDropHost
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Показывает визуальное представление перетаскивания.
|
/// Показывает визуальное представление перетаскиваемого элемента.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragVisual">Визуальное представление.</param>
|
/// <param name="dragVisual">Визуальное представление элемента для перетаскивания.</param>
|
||||||
/// <param name="position">Начальная позиция.</param>
|
/// <param name="position">Начальная позиция визуального элемента в экранных координатах.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Метод должен отобразить переданный визуальный элемент в указанной позиции.
|
||||||
|
/// Визуальный элемент должен следовать за курсором мыши при обновлении через <see cref="UpdateDragVisualPosition"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Визуальный элемент должен отображаться поверх всех других элементов интерфейса.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
void ShowDragVisual(object dragVisual, Point position);
|
void ShowDragVisual(object dragVisual, Point position);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обновляет позицию визуального представления перетаскивания.
|
/// Обновляет позицию визуального представления перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragVisual">Визуальное представление.</param>
|
/// <param name="dragVisual">Визуальное представление, позиция которого должна быть обновлена.</param>
|
||||||
/// <param name="position">Новая позиция.</param>
|
/// <param name="position">Новая позиция в экранных координатах.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Метод должен обновлять позицию уже отображаемого визуального элемента
|
||||||
|
/// с минимальной задержкой для плавного перемещения.
|
||||||
|
/// </remarks>
|
||||||
void UpdateDragVisualPosition(object dragVisual, Point position);
|
void UpdateDragVisualPosition(object dragVisual, Point position);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Скрывает визуальное представление перетаскивания.
|
/// Скрывает визуальное представление перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragVisual">Визуальное представление.</param>
|
/// <param name="dragVisual">Визуальное представление для скрытия.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// После вызова этого метода визуальный элемент должен быть полностью
|
||||||
|
/// удален из визуального дерева и его ресурсы освобождены.
|
||||||
|
/// </remarks>
|
||||||
void HideDragVisual(object dragVisual);
|
void HideDragVisual(object dragVisual);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Показывает визуальную обратную связь для цели сброса.
|
/// Показывает визуальную обратную связь для цели сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="adorner">Элемент обратной связи.</param>
|
/// <param name="adorner">Элемент обратной связи для отображения.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Метод должен отобразить элемент обратной связи (например, подсветку, рамку или индикатор позиции)
|
||||||
|
/// для визуального указания возможности сброса на целевой элемент.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Элемент обратной связи должен отображаться поверх целевого элемента, но под перетаскиваемым визуальным элементом.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
void ShowDropAdorner(IDropVisualAdorner adorner);
|
void ShowDropAdorner(IDropVisualAdorner adorner);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Скрывает визуальную обратную связь для цели сброса.
|
/// Скрывает визуальную обратную связь для цели сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="adorner">Элемент обратной связи.</param>
|
/// <param name="adorner">Элемент обратной связи для скрытия.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// После вызова этого метода элемент обратной связи должен быть
|
||||||
|
/// удален из визуального дерева и его ресурсы освобождены.
|
||||||
|
/// </remarks>
|
||||||
void HideDropAdorner(IDropVisualAdorner adorner);
|
void HideDropAdorner(IDropVisualAdorner adorner);
|
||||||
}
|
}
|
||||||
@@ -6,26 +6,62 @@ namespace Lattice.UI.DragDrop.Abstractions;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Поставщик визуального представления для перетаскиваемого элемента.
|
/// Поставщик визуального представления для перетаскиваемого элемента.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Интерфейс предоставляет абстракцию для создания и управления визуальными
|
||||||
|
/// представлениями элементов при операции перетаскивания.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Реализации могут предоставлять различные стили визуального представления:
|
||||||
|
/// от простого клонирования оригинального элемента до сложных анимированных представлений.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public interface IDragVisualProvider
|
public interface IDragVisualProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Создает визуальное представление для перетаскивания.
|
/// Создает визуальное представление для перетаскивания на основе информации о перетаскивании.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
/// <param name="dragInfo">Информация о перетаскивании, содержащая данные и метаданные операции.</param>
|
||||||
/// <param name="initialPosition">Начальная позиция в экранных координатах.</param>
|
/// <param name="initialPosition">Начальная позиция в экранных координатах.</param>
|
||||||
/// <returns>Объект, представляющий визуальное отображение.</returns>
|
/// <returns>Объект, представляющий визуальное отображение для перетаскивания.</returns>
|
||||||
object CreateDragVisual(DragInfo dragInfo, Point initialPosition);
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Созданный визуальный элемент должен:
|
||||||
|
/// 1. Отображать репрезентативное представление перетаскиваемых данных
|
||||||
|
/// 2. Иметь прозрачный фон или альфа-канал для плавного отображения
|
||||||
|
/// 3. Быть легковесным для обеспечения плавной анимации
|
||||||
|
/// 4. Поддерживать возможность изменения позиции через <see cref="UpdateDragVisualPosition"/>
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Метод может возвращать null, если визуальное представление не требуется.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
object? CreateDragVisual(DragInfo dragInfo, Point initialPosition);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обновляет позицию визуального представления.
|
/// Обновляет позицию визуального представления перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragVisual">Визуальное представление.</param>
|
/// <param name="dragVisual">Визуальное представление, созданное методом <see cref="CreateDragVisual"/>.</param>
|
||||||
/// <param name="position">Новая позиция.</param>
|
/// <param name="position">Новая позиция в экранных координатах.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Метод должен обновить позицию визуального элемента максимально эффективно,
|
||||||
|
/// так как он вызывается часто во время операции перетаскивания.
|
||||||
|
/// </remarks>
|
||||||
void UpdateDragVisualPosition(object dragVisual, Point position);
|
void UpdateDragVisualPosition(object dragVisual, Point position);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Освобождает ресурсы визуального представления.
|
/// Освобождает ресурсы визуального представления.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragVisual">Визуальное представление.</param>
|
/// <param name="dragVisual">Визуальное представление для освобождения.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Метод должен освободить все ресурсы, связанные с визуальным представлением,
|
||||||
|
/// включая графические ресурсы, подписки на события и временные данные.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Этот метод гарантированно вызывается после завершения операции перетаскивания,
|
||||||
|
/// независимо от её успешности.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
void ReleaseDragVisual(object dragVisual);
|
void ReleaseDragVisual(object dragVisual);
|
||||||
}
|
}
|
||||||
@@ -6,23 +6,54 @@ namespace Lattice.UI.DragDrop.Abstractions;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Визуальный элемент, показывающий обратную связь при наведении на цель сброса.
|
/// Визуальный элемент, показывающий обратную связь при наведении на цель сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Интерфейс предоставляет абстракцию для визуальных индикаторов, которые
|
||||||
|
/// показывают пользователю возможность сброса данных на целевом элементе.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Реализации могут предоставлять различные типы визуальной обратной связи:
|
||||||
|
/// подсветку элемента, отображение индикатора позиции, изменение курсора и т.д.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public interface IDropVisualAdorner
|
public interface IDropVisualAdorner
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Показывает визуальную обратную связь для цели сброса.
|
/// Показывает визуальную обратную связь для цели сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dropInfo">Информация о сбросе.</param>
|
/// <param name="dropInfo">Информация о потенциальном сбросе, включая данные и позицию.</param>
|
||||||
/// <param name="targetBounds">Границы цели.</param>
|
/// <param name="targetBounds">Границы целевого элемента в экранных координатах.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Метод должен отобразить визуальную обратную связь, соответствующую типу данных
|
||||||
|
/// и контексту сброса. Обратная связь должна явно указывать на возможность
|
||||||
|
/// сброса и ожидаемый эффект (копирование, перемещение и т.д.).
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Обратная связь должна учитывать свойства <see cref="DropInfo.ShowVisualFeedback"/>
|
||||||
|
/// и <see cref="DropInfo.VisualFeedbackData"/> для кастомизации отображения.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
void Show(DropInfo dropInfo, Rect targetBounds);
|
void Show(DropInfo dropInfo, Rect targetBounds);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обновляет позицию и состояние визуальной обратной связи.
|
/// Обновляет позицию и состояние визуальной обратной связи.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dropInfo">Информация о сбросе.</param>
|
/// <param name="dropInfo">Текущая информация о сбросе, включая обновленную позицию и состояние.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Метод вызывается при каждом перемещении курсора над целью и должен
|
||||||
|
/// обновлять визуальную обратную связь в соответствии с новой позицией
|
||||||
|
/// и состоянием операции.
|
||||||
|
/// </remarks>
|
||||||
void Update(DropInfo dropInfo);
|
void Update(DropInfo dropInfo);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Скрывает визуальную обратную связь.
|
/// Скрывает визуальную обратную связь.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Метод должен скрыть и очистить все визуальные элементы обратной связи.
|
||||||
|
/// После вызова этого метода ресурсы могут быть освобождены или переиспользованы
|
||||||
|
/// для следующих операций.
|
||||||
|
/// </remarks>
|
||||||
void Hide();
|
void Hide();
|
||||||
}
|
}
|
||||||
@@ -10,9 +10,20 @@ using System.Threading.Tasks;
|
|||||||
namespace Lattice.UI.DragDrop.Behaviors;
|
namespace Lattice.UI.DragDrop.Behaviors;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Базовый класс поведения источника перетаскивания.
|
/// Базовый класс поведения источника перетаскивания для UI элементов.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TElement">Тип UI элемента.</typeparam>
|
/// <typeparam name="TElement">Тип UI элемента, к которому прикрепляется поведение.</typeparam>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот класс предоставляет базовую реализацию поведения перетаскивания для UI элементов.
|
||||||
|
/// Он обрабатывает события мыши/тач, управляет порогом начала перетаскивания и
|
||||||
|
/// интегрируется с сервисом <see cref="IDragDropService"/> из ядра.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Производные классы должны реализовать абстрактные методы для конкретной
|
||||||
|
/// UI-платформы и предоставить логику создания информации о перетаскивании.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
||||||
where TElement : class
|
where TElement : class
|
||||||
{
|
{
|
||||||
@@ -20,10 +31,15 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
private Point _dragStartPosition;
|
private Point _dragStartPosition;
|
||||||
private bool _isDragging;
|
private bool _isDragging;
|
||||||
private TElement? _associatedElement;
|
private TElement? _associatedElement;
|
||||||
|
private CancellationTokenSource? _dragCancellationTokenSource;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает или задает связанный элемент.
|
/// Получает или задает связанный UI элемент.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Элемент UI, к которому прикреплено поведение перетаскивания.
|
||||||
|
/// При изменении значения автоматически выполняется переподключение событий.
|
||||||
|
/// </value>
|
||||||
protected TElement? AssociatedElement
|
protected TElement? AssociatedElement
|
||||||
{
|
{
|
||||||
get => _associatedElement;
|
get => _associatedElement;
|
||||||
@@ -39,8 +55,12 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает сервис перетаскивания.
|
/// Получает сервис перетаскивания из контейнера зависимостей.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Экземпляр <see cref="IDragDropService"/>, используемый для управления операциями перетаскивания.
|
||||||
|
/// При первом обращении выполняется получение сервиса из <see cref="ServiceProvider"/>.
|
||||||
|
/// </value>
|
||||||
protected IDragDropService DragDropService
|
protected IDragDropService DragDropService
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -54,22 +74,34 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает провайдер сервисов.
|
/// Получает провайдер сервисов для разрешения зависимостей.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected IServiceProvider ServiceProvider { get; }
|
protected IServiceProvider ServiceProvider { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает значение, указывающее, выполняется ли в данный момент операция перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
protected bool IsDragging => _isDragging;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Инициализирует новый экземпляр класса <see cref="DragSourceBehaviorBase{TElement}"/>.
|
/// Инициализирует новый экземпляр класса <see cref="DragSourceBehaviorBase{TElement}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serviceProvider">Провайдер сервисов.</param>
|
/// <param name="serviceProvider">Провайдер сервисов для разрешения зависимостей.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, когда <paramref name="serviceProvider"/> равен null.
|
||||||
|
/// </exception>
|
||||||
protected DragSourceBehaviorBase(IServiceProvider serviceProvider)
|
protected DragSourceBehaviorBase(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается при прикреплении к элементу.
|
/// Вызывается при прикреплении поведения к элементу.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Реализация по умолчанию подписывается на события элемента через <see cref="SubscribeToEvents"/>.
|
||||||
|
/// Производные классы могут переопределить этот метод для дополнительной инициализации.
|
||||||
|
/// </remarks>
|
||||||
protected virtual void AttachToElement()
|
protected virtual void AttachToElement()
|
||||||
{
|
{
|
||||||
if (_associatedElement != null)
|
if (_associatedElement != null)
|
||||||
@@ -79,8 +111,12 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается при откреплении от элемента.
|
/// Вызывается при откреплении поведения от элемента.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Реализация по умолчанию отписывается от событий элемента через <see cref="UnsubscribeFromEvents"/>.
|
||||||
|
/// Производные классы могут переопределить этот метод для дополнительной очистки.
|
||||||
|
/// </remarks>
|
||||||
protected virtual void DetachFromElement()
|
protected virtual void DetachFromElement()
|
||||||
{
|
{
|
||||||
if (_associatedElement != null)
|
if (_associatedElement != null)
|
||||||
@@ -90,36 +126,68 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Подписывается на события элемента.
|
/// Подписывается на события элемента, необходимые для отслеживания начала перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="element">Элемент.</param>
|
/// <param name="element">Элемент, к событиям которого нужно подписаться.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Производные классы должны реализовать этот метод для подписки на события конкретной
|
||||||
|
/// UI-платформы (например, MouseDown для WPF, PointerPressed для Avalonia).
|
||||||
|
/// </remarks>
|
||||||
protected abstract void SubscribeToEvents(TElement element);
|
protected abstract void SubscribeToEvents(TElement element);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Отписывается от событий элемента.
|
/// Отписывается от событий элемента.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="element">Элемент.</param>
|
/// <param name="element">Элемент, от событий которого нужно отписаться.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Производные классы должны реализовать этот метод для корректной отписки
|
||||||
|
/// от событий, на которые была выполнена подписка в <see cref="SubscribeToEvents"/>.
|
||||||
|
/// </remarks>
|
||||||
protected abstract void UnsubscribeFromEvents(TElement element);
|
protected abstract void UnsubscribeFromEvents(TElement element);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обрабатывает начало взаимодействия (например, нажатие мыши).
|
/// Обрабатывает начало взаимодействия с элементом (например, нажатие кнопки мыши).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="position">Позиция в координатах элемента.</param>
|
/// <param name="position">Позиция взаимодействия в координатах элемента.</param>
|
||||||
protected virtual async Task OnInteractionStarted(Point position)
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот метод вызывается из обработчиков событий UI-платформы при начале
|
||||||
|
/// взаимодействия, которое может привести к перетаскиванию.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Реализация по умолчанию сохраняет начальную позицию для последующей
|
||||||
|
/// проверки порога перетаскивания.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual Task OnInteractionStarted(Point position)
|
||||||
{
|
{
|
||||||
if (_isDragging)
|
if (_isDragging)
|
||||||
return;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
_dragStartPosition = position;
|
_dragStartPosition = position;
|
||||||
|
_dragCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обрабатывает перемещение во время взаимодействия.
|
/// Обрабатывает перемещение во время взаимодействия с элементом.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="position">Позиция в координатах элемента.</param>
|
/// <param name="position">Текущая позиция взаимодействия в координатах элемента.</param>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот метод вызывается при перемещении курсора/тач-точки во время удержания
|
||||||
|
/// взаимодействия (например, перемещение мыши с нажатой кнопкой).
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Реализация по умолчанию проверяет, превышено ли расстояние от начальной
|
||||||
|
/// точки порога перетаскивания, и если да - начинает операцию перетаскивания.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
protected virtual async Task OnInteractionMoved(Point position)
|
protected virtual async Task OnInteractionMoved(Point position)
|
||||||
{
|
{
|
||||||
if (_isDragging)
|
if (_isDragging || AssociatedElement == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var distance = CalculateDistance(_dragStartPosition, position);
|
var distance = CalculateDistance(_dragStartPosition, position);
|
||||||
@@ -130,20 +198,41 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обрабатывает завершение взаимодействия.
|
/// Обрабатывает завершение взаимодействия с элементом.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual async Task OnInteractionEnded()
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот метод вызывается при завершении взаимодействия (например, отпускании кнопки мыши).
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Реализация по умолчанию сбрасывает состояние поведения, если перетаскивание не было начато.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual Task OnInteractionEnded()
|
||||||
{
|
{
|
||||||
// Сброс состояния, если перетаскивание не началось
|
// Сброс состояния, если перетаскивание не началось
|
||||||
if (!_isDragging)
|
if (!_isDragging)
|
||||||
{
|
{
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обрабатывает отмену взаимодействия.
|
/// Обрабатывает отмену взаимодействия с элементом.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот метод вызывается при отмене взаимодействия (например, нажатии клавиши Escape
|
||||||
|
/// или выходе за пределы допустимой области).
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Реализация по умолчанию отменяет текущую операцию перетаскивания, если она активна,
|
||||||
|
/// и сбрасывает состояние поведения.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
protected virtual async Task OnInteractionCancelled()
|
protected virtual async Task OnInteractionCancelled()
|
||||||
{
|
{
|
||||||
if (_isDragging)
|
if (_isDragging)
|
||||||
@@ -156,28 +245,54 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Начинает операцию перетаскивания.
|
/// Начинает операцию перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <returns>Задача, представляющая асинхронную операцию.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот метод преобразует начальную позицию в экранные координаты и вызывает
|
||||||
|
/// сервис перетаскивания для начала операции.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Операция начинается только если поведение прикреплено к элементу и
|
||||||
|
/// не выполняется другая операция перетаскивания.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
protected virtual async Task StartDragOperation()
|
protected virtual async Task StartDragOperation()
|
||||||
{
|
{
|
||||||
if (_isDragging || AssociatedElement == null)
|
if (_isDragging || AssociatedElement == null || _dragCancellationTokenSource == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Получаем начальную позицию в экранных координатах
|
// Получаем начальную позицию в экранных координатах
|
||||||
var screenPosition = ConvertToScreenCoordinates(_dragStartPosition);
|
var screenPosition = ConvertToScreenCoordinates(_dragStartPosition);
|
||||||
|
|
||||||
// Начинаем перетаскивание
|
// Начинаем перетаскивание
|
||||||
|
try
|
||||||
|
{
|
||||||
_isDragging = await DragDropService.StartDragAsync(this, screenPosition);
|
_isDragging = await DragDropService.StartDragAsync(this, screenPosition);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Операция была отменена
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Преобразует координаты элемента в экранные координаты.
|
/// Преобразует координаты элемента в экранные координаты.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="point">Точка в координатах элемента.</param>
|
/// <param name="point">Точка в координатах элемента.</param>
|
||||||
/// <returns>Точка в экранных координатах.</returns>
|
/// <returns>Точка в экранных координатах.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Производные классы должны реализовать этот метод для преобразования
|
||||||
|
/// координат в соответствии с конкретной UI-платформой.
|
||||||
|
/// </remarks>
|
||||||
protected abstract Point ConvertToScreenCoordinates(Point point);
|
protected abstract Point ConvertToScreenCoordinates(Point point);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вычисляет расстояние между двумя точками.
|
/// Вычисляет расстояние между двумя точками.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="p1">Первая точка.</param>
|
||||||
|
/// <param name="p2">Вторая точка.</param>
|
||||||
|
/// <returns>Расстояние между точками.</returns>
|
||||||
protected virtual double CalculateDistance(Point p1, Point p2)
|
protected virtual double CalculateDistance(Point p1, Point p2)
|
||||||
{
|
{
|
||||||
var dx = p2.X - p1.X;
|
var dx = p2.X - p1.X;
|
||||||
@@ -188,32 +303,33 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Сбрасывает состояние поведения.
|
/// Сбрасывает состояние поведения.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот метод очищает все временные данные и отменяет токены отмены,
|
||||||
|
/// связанные с текущей операцией перетаскивания.
|
||||||
|
/// </remarks>
|
||||||
protected virtual void Reset()
|
protected virtual void Reset()
|
||||||
{
|
{
|
||||||
_isDragging = false;
|
_isDragging = false;
|
||||||
_dragStartPosition = default;
|
_dragStartPosition = default;
|
||||||
|
|
||||||
|
_dragCancellationTokenSource?.Dispose();
|
||||||
|
_dragCancellationTokenSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDragSource Implementation
|
#region IDragSource Implementation
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public abstract Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync(CancellationToken ct = default);
|
public abstract Task<DragInfo?> TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async Task<bool> StartDragAsync(DragInfo dragInfo, CancellationToken ct = default)
|
public async Task OnDragCompletedAsync(DragInfo dragInfo, Lattice.Core.DragDrop.Enums.DragDropEffects effects, CancellationToken cancellationToken = default)
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public virtual async Task DragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects, CancellationToken ct = default)
|
|
||||||
{
|
{
|
||||||
_isDragging = false;
|
_isDragging = false;
|
||||||
OnDragCompleted(dragInfo, effects);
|
OnDragCompleted(dragInfo, effects);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async Task DragCancelledAsync(DragInfo dragInfo, CancellationToken ct = default)
|
public async Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
_isDragging = false;
|
_isDragging = false;
|
||||||
OnDragCancelled(dragInfo);
|
OnDragCancelled(dragInfo);
|
||||||
@@ -224,18 +340,27 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
#region Virtual Methods for Derived Classes
|
#region Virtual Methods for Derived Classes
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается при успешном завершении перетаскивания.
|
/// Вызывается при успешном завершении операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
/// <param name="dragInfo">Информация о перетаскивании, использованная в операции.</param>
|
||||||
/// <param name="effects">Примененные эффекты.</param>
|
/// <param name="effects">Эффекты, примененные при завершении операции.</param>
|
||||||
protected virtual void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects)
|
/// <remarks>
|
||||||
|
/// Производные классы могут переопределить этот метод для выполнения
|
||||||
|
/// дополнительных действий после успешного завершения перетаскивания,
|
||||||
|
/// например, удаления исходного элемента при перемещении.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual void OnDragCompleted(DragInfo dragInfo, Lattice.Core.DragDrop.Enums.DragDropEffects effects)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается при отмене перетаскивания.
|
/// Вызывается при отмене операции перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dragInfo">Информация о перетаскивании.</param>
|
/// <param name="dragInfo">Информация о перетаскивании, использованная в операции.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Производные классы могут переопределить этот метод для выполнения
|
||||||
|
/// действий по восстановлению состояния после отмены перетаскивания.
|
||||||
|
/// </remarks>
|
||||||
protected virtual void OnDragCancelled(DragInfo dragInfo)
|
protected virtual void OnDragCancelled(DragInfo dragInfo)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -243,11 +368,16 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Освобождает ресурсы.
|
/// Открепляет поведение от элемента и освобождает ресурсы.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// После вызова этого метода поведение больше не будет обрабатывать события
|
||||||
|
/// элемента и может быть безопасно удалено.
|
||||||
|
/// </remarks>
|
||||||
public virtual void Detach()
|
public virtual void Detach()
|
||||||
{
|
{
|
||||||
DetachFromElement();
|
DetachFromElement();
|
||||||
_associatedElement = null;
|
_associatedElement = null;
|
||||||
|
Reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,9 +10,20 @@ using System.Threading.Tasks;
|
|||||||
namespace Lattice.UI.DragDrop.Behaviors;
|
namespace Lattice.UI.DragDrop.Behaviors;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Базовый класс поведения цели сброса.
|
/// Базовый класс поведения цели сброса для UI элементов.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TElement">Тип UI элемента.</typeparam>
|
/// <typeparam name="TElement">Тип UI элемента, к которому прикрепляется поведение.</typeparam>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот класс предоставляет базовую реализацию поведения цели сброса для UI элементов.
|
||||||
|
/// Он автоматически регистрирует элемент в сервисе перетаскивания, обновляет его границы
|
||||||
|
/// при изменении размера или позиции и предоставляет методы для обработки событий сброса.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Производные классы должны реализовать абстрактные методы для конкретной
|
||||||
|
/// UI-платформы и предоставить логику проверки и обработки сбрасываемых данных.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
||||||
where TElement : class
|
where TElement : class
|
||||||
{
|
{
|
||||||
@@ -22,8 +33,12 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
private Rect _currentBounds;
|
private Rect _currentBounds;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает или задает связанный элемент.
|
/// Получает или задает связанный UI элемент.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Элемент UI, к которому прикреплено поведение цели сброса.
|
||||||
|
/// При изменении значения автоматически выполняется перерегистрация в сервисе перетаскивания.
|
||||||
|
/// </value>
|
||||||
protected TElement? AssociatedElement
|
protected TElement? AssociatedElement
|
||||||
{
|
{
|
||||||
get => _associatedElement;
|
get => _associatedElement;
|
||||||
@@ -41,16 +56,28 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает или задает приоритет цели сброса.
|
/// Получает или задает приоритет цели сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Цели с более высоким приоритетом проверяются первыми при нахождении курсора
|
||||||
|
/// в области нескольких целей. Значение по умолчанию: 0.
|
||||||
|
/// </value>
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает или задает группу цели сброса.
|
/// Получает или задает группу цели сброса.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Имя группы для группового управления целями сброса. Может использоваться
|
||||||
|
/// для массовой отмены регистрации целей или применения общих настроек.
|
||||||
|
/// </value>
|
||||||
public string? Group { get; set; }
|
public string? Group { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает сервис перетаскивания.
|
/// Получает сервис перетаскивания из контейнера зависимостей.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Экземпляр <see cref="IDragDropService"/>, используемый для регистрации цели сброса.
|
||||||
|
/// При первом обращении выполняется получение сервиса из <see cref="ServiceProvider"/>.
|
||||||
|
/// </value>
|
||||||
protected IDragDropService DragDropService
|
protected IDragDropService DragDropService
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -64,27 +91,47 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает провайдер сервисов.
|
/// Получает провайдер сервисов для разрешения зависимостей.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected IServiceProvider ServiceProvider { get; }
|
protected IServiceProvider ServiceProvider { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает текущие границы элемента в экранных координатах.
|
/// Получает текущие границы элемента в экранных координатах.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Прямоугольник, описывающий границы элемента в экранных координатах.
|
||||||
|
/// Значение автоматически обновляется при изменении размера или позиции элемента.
|
||||||
|
/// </value>
|
||||||
protected Rect CurrentBounds => _currentBounds;
|
protected Rect CurrentBounds => _currentBounds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает уникальный идентификатор регистрации цели в сервисе перетаскивания.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Идентификатор, возвращенный методом <see cref="IDragDropService.RegisterDropTarget"/>,
|
||||||
|
/// или null, если цель не зарегистрирована.
|
||||||
|
/// </value>
|
||||||
|
protected string? RegistrationId => _registrationId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Инициализирует новый экземпляр класса <see cref="DropTargetBehaviorBase{TElement}"/>.
|
/// Инициализирует новый экземпляр класса <see cref="DropTargetBehaviorBase{TElement}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serviceProvider">Провайдер сервисов.</param>
|
/// <param name="serviceProvider">Провайдер сервисов для разрешения зависимостей.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, когда <paramref name="serviceProvider"/> равен null.
|
||||||
|
/// </exception>
|
||||||
protected DropTargetBehaviorBase(IServiceProvider serviceProvider)
|
protected DropTargetBehaviorBase(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается при прикреплении к элементу.
|
/// Вызывается при прикреплении поведения к элементу.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Реализация по умолчанию подписывается на события элемента, обновляет границы
|
||||||
|
/// и регистрирует цель в сервисе перетаскивания.
|
||||||
|
/// </remarks>
|
||||||
protected virtual void AttachToElement()
|
protected virtual void AttachToElement()
|
||||||
{
|
{
|
||||||
if (_associatedElement != null)
|
if (_associatedElement != null)
|
||||||
@@ -96,8 +143,12 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается при откреплении от элемента.
|
/// Вызывается при откреплении поведения от элемента.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Реализация по умолчанию отписывается от событий элемента и отменяет
|
||||||
|
/// регистрацию цели в сервисе перетаскивания.
|
||||||
|
/// </remarks>
|
||||||
protected virtual void DetachFromElement()
|
protected virtual void DetachFromElement()
|
||||||
{
|
{
|
||||||
if (_associatedElement != null)
|
if (_associatedElement != null)
|
||||||
@@ -108,20 +159,32 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Подписывается на события элемента.
|
/// Подписывается на события элемента, необходимые для отслеживания изменений размера и позиции.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="element">Элемент.</param>
|
/// <param name="element">Элемент, к событиям которого нужно подписаться.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Производные классы должны реализовать этот метод для подписки на события конкретной
|
||||||
|
/// UI-платформы (например, SizeChanged, LayoutUpdated для WPF).
|
||||||
|
/// </remarks>
|
||||||
protected abstract void SubscribeToEvents(TElement element);
|
protected abstract void SubscribeToEvents(TElement element);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Отписывается от событий элемента.
|
/// Отписывается от событий элемента.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="element">Элемент.</param>
|
/// <param name="element">Элемент, от событий которого нужно отписаться.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Производные классы должны реализовать этот метод для корректной отписки
|
||||||
|
/// от событий, на которые была выполнена подписка в <see cref="SubscribeToEvents"/>.
|
||||||
|
/// </remarks>
|
||||||
protected abstract void UnsubscribeFromEvents(TElement element);
|
protected abstract void UnsubscribeFromEvents(TElement element);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обновляет границы элемента в экранных координатах.
|
/// Обновляет границы элемента в экранных координатах.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот метод вызывается при изменении размера или позиции элемента для
|
||||||
|
/// обновления области, в которой цель может принимать сбрасываемые данные.
|
||||||
|
/// </remarks>
|
||||||
protected virtual void UpdateBounds()
|
protected virtual void UpdateBounds()
|
||||||
{
|
{
|
||||||
if (_associatedElement != null)
|
if (_associatedElement != null)
|
||||||
@@ -139,13 +202,27 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получает границы элемента в экранных координатах.
|
/// Получает границы элемента в экранных координатах.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="element">Элемент.</param>
|
/// <param name="element">Элемент, границы которого нужно получить.</param>
|
||||||
/// <returns>Границы в экранных координатах.</returns>
|
/// <returns>Границы элемента в экранных координатах.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Производные классы должны реализовать этот метод для получения границ
|
||||||
|
/// элемента в соответствии с конкретной UI-платформой.
|
||||||
|
/// </remarks>
|
||||||
protected abstract Rect GetScreenBounds(TElement element);
|
protected abstract Rect GetScreenBounds(TElement element);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Регистрирует цель в сервисе перетаскивания.
|
/// Регистрирует цель в сервисе перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот метод регистрирует текущий объект как цель сброса в сервисе перетаскивания
|
||||||
|
/// с указанными приоритетом и группой.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Регистрация выполняется только если поведение прикреплено к элементу и
|
||||||
|
/// цель еще не зарегистрирована.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
protected virtual void RegisterToService()
|
protected virtual void RegisterToService()
|
||||||
{
|
{
|
||||||
if (_associatedElement != null && _registrationId == null)
|
if (_associatedElement != null && _registrationId == null)
|
||||||
@@ -158,6 +235,10 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Отменяет регистрацию цели в сервисе перетаскивания.
|
/// Отменяет регистрацию цели в сервисе перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот метод отменяет регистрацию цели сброса, освобождая ресурсы
|
||||||
|
/// в сервисе перетаскивания и предотвращая дальнейшую обработку событий.
|
||||||
|
/// </remarks>
|
||||||
protected virtual void UnregisterFromService()
|
protected virtual void UnregisterFromService()
|
||||||
{
|
{
|
||||||
if (_registrationId != null)
|
if (_registrationId != null)
|
||||||
@@ -170,6 +251,10 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Вызывается при изменении размера или позиции элемента.
|
/// Вызывается при изменении размера или позиции элемента.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Производные классы должны вызывать этот метод из обработчиков событий
|
||||||
|
/// изменения размера или позиции элемента для обновления границ цели.
|
||||||
|
/// </remarks>
|
||||||
protected virtual void OnElementLayoutChanged()
|
protected virtual void OnElementLayoutChanged()
|
||||||
{
|
{
|
||||||
UpdateBounds();
|
UpdateBounds();
|
||||||
@@ -181,7 +266,7 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
public abstract Task<bool> CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default);
|
public abstract Task<bool> CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async Task DragOverAsync(DropInfo dropInfo, CancellationToken ct = default)
|
public virtual async Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// Базовая реализация устанавливает эффект по умолчанию
|
// Базовая реализация устанавливает эффект по умолчанию
|
||||||
if (await CanAcceptDropAsync(dropInfo))
|
if (await CanAcceptDropAsync(dropInfo))
|
||||||
@@ -207,22 +292,58 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public abstract Task DropAsync(DropInfo dropInfo, CancellationToken ct = default);
|
public abstract Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async Task DragLeaveAsync(CancellationToken ct = default)
|
public virtual Task OnDragLeaveAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Базовая реализация не делает ничего
|
// Базовая реализация не выполняет действий при выходе курсора из области цели
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Освобождает ресурсы.
|
/// Открепляет поведение от элемента и освобождает все связанные ресурсы.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот метод выполняет следующие действия:
|
||||||
|
/// </para>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Отписывается от всех событий связанного элемента</item>
|
||||||
|
/// <item>Отменяет регистрацию цели в сервисе перетаскивания</item>
|
||||||
|
/// <item>Освобождает ссылку на связанный элемент</item>
|
||||||
|
/// <item>Очищает все временные данные и состояние</item>
|
||||||
|
/// </list>
|
||||||
|
/// <para>
|
||||||
|
/// После вызова этого метода поведение больше не будет обрабатывать события
|
||||||
|
/// элемента и не будет реагировать на операции перетаскивания. Поведение
|
||||||
|
/// можно безопасно удалить после вызова этого метода.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <strong>Важно:</strong> Этот метод должен быть вызван перед удалением
|
||||||
|
/// элемента из визуального дерева или перед заменой поведения, чтобы
|
||||||
|
/// предотвратить утечки памяти и непредсказуемое поведение системы.
|
||||||
|
/// </para>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// // Пример использования
|
||||||
|
/// var dropBehavior = new MyDropTargetBehavior(serviceProvider);
|
||||||
|
/// dropBehavior.AssociatedElement = myElement;
|
||||||
|
///
|
||||||
|
/// // ... использование поведения ...
|
||||||
|
///
|
||||||
|
/// // Перед удалением элемента или поведения
|
||||||
|
/// dropBehavior.Detach();
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
/// </remarks>
|
||||||
public virtual void Detach()
|
public virtual void Detach()
|
||||||
{
|
{
|
||||||
DetachFromElement();
|
DetachFromElement();
|
||||||
_associatedElement = null;
|
_associatedElement = null;
|
||||||
|
_registrationId = null;
|
||||||
|
_currentBounds = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,194 @@
|
|||||||
using Lattice.UI.DragDrop.Abstractions;
|
using Lattice.Core.DragDrop.Services;
|
||||||
|
using Lattice.UI.DragDrop.Abstractions;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
namespace Lattice.UI.DragDrop.Extensions;
|
namespace Lattice.UI.DragDrop.Extensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Методы расширения для регистрации сервисов перетаскивания.
|
/// Методы расширения для регистрации сервисов перетаскивания в контейнере зависимостей.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Добавляет сервисы перетаскивания.
|
/// Добавляет сервисы перетаскивания в контейнер зависимостей.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="services">Коллекция сервисов для регистрации.</param>
|
||||||
|
/// <returns>Коллекция сервисов с зарегистрированными сервисами перетаскивания.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Этот метод регистрирует основные сервисы перетаскивания, необходимые для работы системы:
|
||||||
|
/// </para>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Основной сервис управления перетаскиванием (<see cref="IDragDropService"/>)</item>
|
||||||
|
/// <item>Абстракции для визуального представления и обратной связи</item>
|
||||||
|
/// <item>Провайдеры по умолчанию с безопасной реализацией</item>
|
||||||
|
/// </list>
|
||||||
|
/// <para>
|
||||||
|
/// Для полноценной работы в конкретной UI-платформе (WPF, Avalonia, MAUI и т.д.)
|
||||||
|
/// необходимо переопределить регистрации платформенными реализациями.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public static IServiceCollection AddLatticeDragDrop(this IServiceCollection services)
|
public static IServiceCollection AddLatticeDragDrop(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
// Регистрируем абстракции, которые будут реализованы в платформенных проектах
|
// Регистрация основного сервиса перетаскивания
|
||||||
services.AddSingleton(typeof(IDragVisualProvider), typeof(DefaultDragVisualProvider));
|
services.TryAddSingleton<IDragDropService, DragDropService>();
|
||||||
services.AddSingleton(typeof(IDropVisualAdorner), typeof(DefaultDropVisualAdorner));
|
|
||||||
services.AddSingleton(typeof(IDragDropHost), typeof(DefaultDragDropHost));
|
// Регистрация UI-специфичных абстракций с реализациями по умолчанию
|
||||||
|
// Эти реализации безопасны (ничего не делают), но могут быть переопределены платформенными реализациями
|
||||||
|
services.TryAddSingleton<IDragVisualProvider, DefaultDragVisualProvider>();
|
||||||
|
services.TryAddSingleton<IDropVisualAdorner, DefaultDropVisualAdorner>();
|
||||||
|
services.TryAddSingleton<IDragDropHost, DefaultDragDropHost>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Реализация по умолчанию для платформ, которые еще не имеют своей реализации.
|
/// Добавляет или заменяет реализацию поставщика визуального представления перетаскивания.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <typeparam name="TProvider">Тип поставщика визуального представления.</typeparam>
|
||||||
|
/// <param name="services">Коллекция сервисов.</param>
|
||||||
|
/// <returns>Коллекция сервисов.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Используйте этот метод для регистрации платформенной реализации поставщика визуального представления.
|
||||||
|
/// </remarks>
|
||||||
|
public static IServiceCollection AddDragVisualProvider<TProvider>(this IServiceCollection services)
|
||||||
|
where TProvider : class, IDragVisualProvider
|
||||||
|
{
|
||||||
|
services.Replace(ServiceDescriptor.Singleton<IDragVisualProvider, TProvider>());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавляет или заменяет реализацию визуальной обратной связи для цели сброса.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAdorner">Тип элемента визуальной обратной связи.</typeparam>
|
||||||
|
/// <param name="services">Коллекция сервисов.</param>
|
||||||
|
/// <returns>Коллекция сервисов.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Используйте этот метод для регистрации платформенной реализации визуальной обратной связи.
|
||||||
|
/// </remarks>
|
||||||
|
public static IServiceCollection AddDropVisualAdorner<TAdorner>(this IServiceCollection services)
|
||||||
|
where TAdorner : class, IDropVisualAdorner
|
||||||
|
{
|
||||||
|
services.Replace(ServiceDescriptor.Singleton<IDropVisualAdorner, TAdorner>());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавляет или заменяет реализацию хоста для отображения визуальных элементов.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="THost">Тип хоста визуальных элементов.</typeparam>
|
||||||
|
/// <param name="services">Коллекция сервисов.</param>
|
||||||
|
/// <returns>Коллекция сервисов.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Используйте этот метод для регистрации платформенной реализации хоста визуальных элементов.
|
||||||
|
/// </remarks>
|
||||||
|
public static IServiceCollection AddDragDropHost<THost>(this IServiceCollection services)
|
||||||
|
where THost : class, IDragDropHost
|
||||||
|
{
|
||||||
|
services.Replace(ServiceDescriptor.Singleton<IDragDropHost, THost>());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Default Implementations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Безопасная реализация поставщика визуального представления по умолчанию.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Эта реализация ничего не делает и используется как заглушка до тех пор,
|
||||||
|
/// пока не будет предоставлена платформенная реализация.
|
||||||
|
/// </remarks>
|
||||||
private class DefaultDragVisualProvider : IDragVisualProvider
|
private class DefaultDragVisualProvider : IDragVisualProvider
|
||||||
{
|
{
|
||||||
public object CreateDragVisual(Core.DragDrop.Models.DragInfo dragInfo, Core.Geometry.Point initialPosition)
|
/// <inheritdoc/>
|
||||||
=> new object();
|
public object? CreateDragVisual(Core.DragDrop.Models.DragInfo dragInfo, Core.Geometry.Point initialPosition)
|
||||||
|
{
|
||||||
public void UpdateDragVisualPosition(object dragVisual, Core.Geometry.Point position) { }
|
// Безопасная реализация: не создает визуальное представление
|
||||||
|
return null;
|
||||||
public void ReleaseDragVisual(object dragVisual) { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UpdateDragVisualPosition(object dragVisual, Core.Geometry.Point position)
|
||||||
|
{
|
||||||
|
// Ничего не делаем, так как визуальное представление не было создано
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void ReleaseDragVisual(object dragVisual)
|
||||||
|
{
|
||||||
|
// Ничего не делаем, так как визуальное представление не было создано
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Безопасная реализация визуальной обратной связи по умолчанию.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Эта реализация ничего не делает и используется как заглушка до тех пор,
|
||||||
|
/// пока не будет предоставлена платформенная реализация.
|
||||||
|
/// </remarks>
|
||||||
private class DefaultDropVisualAdorner : IDropVisualAdorner
|
private class DefaultDropVisualAdorner : IDropVisualAdorner
|
||||||
{
|
{
|
||||||
public void Show(Core.DragDrop.Models.DropInfo dropInfo, Core.Geometry.Rect targetBounds) { }
|
/// <inheritdoc/>
|
||||||
public void Update(Core.DragDrop.Models.DropInfo dropInfo) { }
|
public void Show(Core.DragDrop.Models.DropInfo dropInfo, Core.Geometry.Rect targetBounds)
|
||||||
public void Hide() { }
|
{
|
||||||
|
// Безопасная реализация: не отображает визуальную обратную связь
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Update(Core.DragDrop.Models.DropInfo dropInfo)
|
||||||
|
{
|
||||||
|
// Ничего не делаем, так как обратная связь не отображается
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Hide()
|
||||||
|
{
|
||||||
|
// Ничего не делаем, так как обратная связь не отображается
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Безопасная реализация хоста визуальных элементов по умолчанию.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Эта реализация ничего не делает и используется как заглушка до тех пор,
|
||||||
|
/// пока не будет предоставлена платформенная реализация.
|
||||||
|
/// </remarks>
|
||||||
private class DefaultDragDropHost : IDragDropHost
|
private class DefaultDragDropHost : IDragDropHost
|
||||||
{
|
{
|
||||||
public void ShowDragVisual(object dragVisual, Core.Geometry.Point position) { }
|
/// <inheritdoc/>
|
||||||
public void UpdateDragVisualPosition(object dragVisual, Core.Geometry.Point position) { }
|
public void ShowDragVisual(object dragVisual, Core.Geometry.Point position)
|
||||||
public void HideDragVisual(object dragVisual) { }
|
{
|
||||||
public void ShowDropAdorner(IDropVisualAdorner adorner) { }
|
// Безопасная реализация: не отображает визуальный элемент
|
||||||
public void HideDropAdorner(IDropVisualAdorner adorner) { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UpdateDragVisualPosition(object dragVisual, Core.Geometry.Point position)
|
||||||
|
{
|
||||||
|
// Ничего не делаем, так как визуальный элемент не отображается
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void HideDragVisual(object dragVisual)
|
||||||
|
{
|
||||||
|
// Ничего не делаем, так как визуальный элемент не отображается
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void ShowDropAdorner(IDropVisualAdorner adorner)
|
||||||
|
{
|
||||||
|
// Безопасная реализация: не отображает обратную связь
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void HideDropAdorner(IDropVisualAdorner adorner)
|
||||||
|
{
|
||||||
|
// Ничего не делаем, так как обратная связь не отображается
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -1,136 +1,378 @@
|
|||||||
# Lattice.UI.DragDrop
|
# Lattice.UI.DragDrop
|
||||||
|
|
||||||

|
UI-слой библиотеки перетаскивания (drag-and-drop) для платформ .NET.
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Кроссплатформенные абстракции для системы перетаскивания в Lattice UI Framework.
|
## 📋 Обзор
|
||||||
|
|
||||||
## 📦 О проекте
|
Lattice.UI.DragDrop предоставляет абстракции и базовые реализации для интеграции системы перетаскивания Lattice.Core.DragDrop с различными UI-фреймворками (WPF, Avalonia, MAUI и т.д.).
|
||||||
|
|
||||||
`Lattice.UI.DragDrop` предоставляет платформонезависимые интерфейсы и базовые классы для реализации drag-and-drop функциональности.
|
### ✨ Основные возможности
|
||||||
Этот проект служит основой для конкретных реализаций на различных платформах (WinUI, Uno Platform, MAUI и т.д.).
|
|
||||||
|
|
||||||
## 🎯 Особенности
|
- ✅ **Кросс-платформенные абстракции** - единый API для всех UI-фреймворков
|
||||||
|
- ✅ **Готовые базовые классы** для быстрой реализации поведения перетаскивания
|
||||||
|
- ✅ **Поддержка визуальной обратной связи** через плагинную архитектуру
|
||||||
|
- ✅ **Интеграция с DI-контейнерами** через Microsoft.Extensions.DependencyInjection
|
||||||
|
- ✅ **Безопасные реализации по умолчанию** для упрощения разработки
|
||||||
|
- ✅ **Расширяемость** через наследование и переопределение
|
||||||
|
|
||||||
- **Абстрактные интерфейсы** для источников перетаскивания и целей сброса
|
## 🏗️ Архитектура
|
||||||
- **Базовые классы поведения** для упрощения реализации
|
|
||||||
- **Платформонезависимая архитектура**
|
|
||||||
- **Поддержка сложных сценариев** (переупорядочивание, вложенное перетаскивание)
|
|
||||||
- **Расширяемая система событий**
|
|
||||||
|
|
||||||
## 🔧 Интерфейсы
|
### Основные компоненты
|
||||||
|
|
||||||
### IDragVisualProvider
|
#### 1. **IDragDropHost**
|
||||||
```csharp
|
Абстракция для управления визуальными элементами перетаскивания на уровне платформы.
|
||||||
public interface IDragVisualProvider
|
|
||||||
{
|
|
||||||
object CreateDragVisual(DragInfo dragInfo, Point initialPosition);
|
|
||||||
void UpdateDragVisualPosition(object dragVisual, Point position);
|
|
||||||
void ReleaseDragVisual(object dragVisual);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### IDropVisualAdorner
|
#### 2. **IDragVisualProvider**
|
||||||
```csharp
|
Поставщик визуального представления для перетаскиваемых элементов.
|
||||||
public interface IDropVisualAdorner
|
|
||||||
{
|
|
||||||
void Show(DropInfo dropInfo, Rect targetBounds);
|
|
||||||
void Update(DropInfo dropInfo);
|
|
||||||
void Hide();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📦 Установка
|
#### 3. **IDropVisualAdorner**
|
||||||
|
Элемент визуальной обратной связи при наведении на цель сброса.
|
||||||
|
|
||||||
Добавьте проект как ссылку в ваше решение или установите как NuGet пакет:
|
#### 4. **DragSourceBehaviorBase\<TElement>**
|
||||||
|
Базовый класс для реализации поведения источника перетаскивания.
|
||||||
|
|
||||||
```xml
|
#### 5. **DropTargetBehaviorBase\<TElement>**
|
||||||
<PackageReference Include="Lattice.UI.DragDrop" Version="1.0.0" />
|
Базовый класс для реализации поведения цели сброса.
|
||||||
```
|
|
||||||
|
|
||||||
## 🔗 Зависимости
|
|
||||||
|
|
||||||
- `Lattice.Core.DragDrop` >= 1.0.0
|
|
||||||
- `Lattice.Core.Geometry` >= 1.0.0
|
|
||||||
- `Microsoft.Extensions.DependencyInjection.Abstractions` >= 8.0.0
|
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
### 1. Регистрация сервисов
|
### 1. Установка
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using Lattice.UI.DragDrop.Extensions;
|
// В методе ConfigureServices вашего приложения
|
||||||
|
services.AddLatticeDragDrop();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Регистрация платформенных реализаций
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Для WPF
|
||||||
|
services.AddDragVisualProvider<WpfDragVisualProvider>();
|
||||||
|
services.AddDropVisualAdorner<WpfDropVisualAdorner>();
|
||||||
|
services.AddDragDropHost<WpfDragDropHost>();
|
||||||
|
|
||||||
|
// Для Avalonia
|
||||||
|
services.AddDragVisualProvider<AvaloniaDragVisualProvider>();
|
||||||
|
services.AddDropVisualAdorner<AvaloniaDropVisualAdorner>();
|
||||||
|
services.AddDragDropHost<AvaloniaDragDropHost>();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Создание поведения источника перетаскивания
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MyDragSourceBehavior : DragSourceBehaviorBase<Control>
|
||||||
|
{
|
||||||
|
public MyDragSourceBehavior(IServiceProvider serviceProvider)
|
||||||
|
: base(serviceProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SubscribeToEvents(Control element)
|
||||||
|
{
|
||||||
|
element.MouseDown += OnMouseDown;
|
||||||
|
element.MouseMove += OnMouseMove;
|
||||||
|
element.MouseUp += OnMouseUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UnsubscribeFromEvents(Control element)
|
||||||
|
{
|
||||||
|
element.MouseDown -= OnMouseDown;
|
||||||
|
element.MouseMove -= OnMouseMove;
|
||||||
|
element.MouseUp -= OnMouseUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<DragInfo?> TryStartDragAsync(Point startPosition, CancellationToken ct)
|
||||||
|
{
|
||||||
|
// Проверяем, можно ли начать перетаскивание
|
||||||
|
if (!CanDrag())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Создаем информацию о перетаскивании
|
||||||
|
return new DragInfo(
|
||||||
|
data: GetDragData(),
|
||||||
|
allowedEffects: DragDropEffects.Copy | DragDropEffects.Move,
|
||||||
|
startPosition: startPosition,
|
||||||
|
source: this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... остальная реализация
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Создание поведения цели сброса
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MyDropTargetBehavior : DropTargetBehaviorBase<Panel>
|
||||||
|
{
|
||||||
|
public MyDropTargetBehavior(IServiceProvider serviceProvider)
|
||||||
|
: base(serviceProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SubscribeToEvents(Panel element)
|
||||||
|
{
|
||||||
|
element.SizeChanged += OnSizeChanged;
|
||||||
|
element.LayoutUpdated += OnLayoutUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UnsubscribeFromEvents(Panel element)
|
||||||
|
{
|
||||||
|
element.SizeChanged -= OnSizeChanged;
|
||||||
|
element.LayoutUpdated -= OnLayoutUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<bool> CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct)
|
||||||
|
{
|
||||||
|
// Проверяем тип данных
|
||||||
|
if (dropInfo.Data is not MyDataType data)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Проверяем бизнес-правила
|
||||||
|
return await ValidateDropAsync(data, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnDropAsync(DropInfo dropInfo, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var data = (MyDataType)dropInfo.Data;
|
||||||
|
await ProcessDropAsync(data, ct);
|
||||||
|
dropInfo.MarkAsHandled();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... остальная реализация
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔌 Интеграция с UI-фреймворками
|
||||||
|
|
||||||
|
### WPF
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public static class WpfDragDropExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddWpfDragDrop(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
return services
|
||||||
|
.AddLatticeDragDrop()
|
||||||
|
.AddDragVisualProvider<WpfDragVisualProvider>()
|
||||||
|
.AddDropVisualAdorner<WpfDropVisualAdorner>()
|
||||||
|
.AddDragDropHost<WpfDragDropHost>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avalonia
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public static class AvaloniaDragDropExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddAvaloniaDragDrop(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
return services
|
||||||
|
.AddLatticeDragDrop()
|
||||||
|
.AddDragVisualProvider<AvaloniaDragVisualProvider>()
|
||||||
|
.AddDropVisualAdorner<AvaloniaDropVisualAdorner>()
|
||||||
|
.AddDragDropHost<AvaloniaDragDropHost>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 Визуальная обратная связь
|
||||||
|
|
||||||
|
### Создание кастомного визуального представления
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class CustomDragVisualProvider : IDragVisualProvider
|
||||||
|
{
|
||||||
|
public object CreateDragVisual(DragInfo dragInfo, Point initialPosition)
|
||||||
|
{
|
||||||
|
var border = new Border
|
||||||
|
{
|
||||||
|
Background = new SolidColorBrush(Colors.LightBlue),
|
||||||
|
BorderBrush = new SolidColorBrush(Colors.Blue),
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
CornerRadius = new CornerRadius(4),
|
||||||
|
Padding = new Thickness(8),
|
||||||
|
Child = new TextBlock
|
||||||
|
{
|
||||||
|
Text = $"Dragging: {dragInfo.Data}",
|
||||||
|
Foreground = new SolidColorBrush(Colors.Black)
|
||||||
|
},
|
||||||
|
Opacity = 0.8
|
||||||
|
};
|
||||||
|
|
||||||
|
return border;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateDragVisualPosition(object dragVisual, Point position)
|
||||||
|
{
|
||||||
|
if (dragVisual is FrameworkElement element)
|
||||||
|
{
|
||||||
|
element.SetValue(Canvas.LeftProperty, position.X);
|
||||||
|
element.SetValue(Canvas.TopProperty, position.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReleaseDragVisual(object dragVisual)
|
||||||
|
{
|
||||||
|
if (dragVisual is IDisposable disposable)
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Создание кастомной обратной связи
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class CustomDropVisualAdorner : IDropVisualAdorner
|
||||||
|
{
|
||||||
|
private Border? _adorner;
|
||||||
|
|
||||||
|
public void Show(DropInfo dropInfo, Rect targetBounds)
|
||||||
|
{
|
||||||
|
_adorner = new Border
|
||||||
|
{
|
||||||
|
Background = new SolidColorBrush(Colors.Transparent),
|
||||||
|
BorderBrush = new SolidColorBrush(Colors.Green),
|
||||||
|
BorderThickness = new Thickness(2),
|
||||||
|
CornerRadius = new CornerRadius(4)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Установка позиции и размера
|
||||||
|
Canvas.SetLeft(_adorner, targetBounds.X);
|
||||||
|
Canvas.SetTop(_adorner, targetBounds.Y);
|
||||||
|
_adorner.Width = targetBounds.Width;
|
||||||
|
_adorner.Height = targetBounds.Height;
|
||||||
|
|
||||||
|
// Добавление в визуальное дерево
|
||||||
|
AdornerLayer.GetAdornerLayer(targetElement)?.Add(_adorner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(DropInfo dropInfo)
|
||||||
|
{
|
||||||
|
if (_adorner != null)
|
||||||
|
{
|
||||||
|
// Обновление стиля в зависимости от эффекта
|
||||||
|
var brush = dropInfo.SuggestedEffects.HasFlag(DragDropEffects.Move)
|
||||||
|
? Colors.Green
|
||||||
|
: Colors.Blue;
|
||||||
|
|
||||||
|
_adorner.BorderBrush = new SolidColorBrush(brush);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Hide()
|
||||||
|
{
|
||||||
|
if (_adorner != null)
|
||||||
|
{
|
||||||
|
var layer = AdornerLayer.GetAdornerLayer(_adorner);
|
||||||
|
layer?.Remove(_adorner);
|
||||||
|
_adorner = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Конфигурация
|
||||||
|
|
||||||
|
### Настройка сервисов
|
||||||
|
|
||||||
|
```csharp
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
// Базовая регистрация
|
||||||
services.AddLatticeDragDrop();
|
services.AddLatticeDragDrop();
|
||||||
|
|
||||||
|
// Платформенные реализации
|
||||||
|
services.AddDragVisualProvider<PlatformDragVisualProvider>();
|
||||||
|
services.AddDropVisualAdorner<PlatformDropVisualAdorner>();
|
||||||
|
services.AddDragDropHost<PlatformDragDropHost>();
|
||||||
|
|
||||||
|
// Регистрация поведений
|
||||||
|
services.AddTransient<DragSourceBehaviorBase<UIElement>, MyDragSourceBehavior>();
|
||||||
|
services.AddTransient<DropTargetBehaviorBase<Panel>, MyDropTargetBehavior>();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Создание кастомного поведения
|
### Использование с ViewModel
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using Lattice.UI.DragDrop.Behaviors;
|
public class MainViewModel
|
||||||
|
|
||||||
public class MyDragSource : DragSourceBehaviorBase<MyElement>
|
|
||||||
{
|
{
|
||||||
protected override void SubscribeToEvents(MyElement element)
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public MainViewModel(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
// Подписка на события элемента
|
_serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanStartDrag(out DragInfo? dragInfo)
|
public void AttachDragBehavior(UIElement element)
|
||||||
{
|
{
|
||||||
// Реализация проверки возможности перетаскивания
|
var behavior = _serviceProvider.GetRequiredService<DragSourceBehaviorBase<UIElement>>();
|
||||||
|
behavior.AssociatedElement = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttachDropBehavior(Panel panel)
|
||||||
|
{
|
||||||
|
var behavior = _serviceProvider.GetRequiredService<DropTargetBehaviorBase<Panel>>();
|
||||||
|
behavior.AssociatedElement = panel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📚 API Reference
|
## 📝 Best Practices
|
||||||
|
|
||||||
### Основные типы
|
### 1. **Эффективность визуального представления**
|
||||||
|
- Создавайте легковесные визуальные элементы для плавной анимации
|
||||||
|
- Используйте кэширование для часто используемых представлений
|
||||||
|
- Избегайте сложных преобразований и эффектов
|
||||||
|
|
||||||
| Тип | Описание |
|
### 2. **Обработка событий**
|
||||||
|-----|----------|
|
- Всегда отписывайтесь от событий при откреплении поведения
|
||||||
| `DragSourceBehaviorBase<T>` | Базовый класс для поведения источника |
|
- Используйте слабые ссылки для предотвращения утечек памяти
|
||||||
| `DropTargetBehaviorBase<T>` | Базовый класс для поведения цели |
|
- Обрабатывайте исключения в обработчиках событий
|
||||||
| `IDragVisualProvider` | Поставщик визуального представления |
|
|
||||||
| `IDropVisualAdorner` | Визуальный элемент обратной связи |
|
|
||||||
|
|
||||||
### Расширения DI
|
### 3. **Ресурсы**
|
||||||
|
- Освобождайте графические ресурсы в методах Release/Dispose
|
||||||
|
- Используйте пулы объектов для часто создаваемых элементов
|
||||||
|
- Мониторьте использование памяти при активном перетаскивании
|
||||||
|
|
||||||
- `AddLatticeDragDrop()` - регистрация сервисов перетаскивания
|
### 4. **Пользовательский опыт**
|
||||||
|
- Предоставляйте понятную визуальную обратную связь
|
||||||
|
- Поддерживайте настраиваемые стили и темы
|
||||||
|
- Обеспечивайте плавность анимации даже на слабых устройствах
|
||||||
|
|
||||||
## 🔄 Интеграция с платформенными проектами
|
## 🔄 Миграция
|
||||||
|
|
||||||
Этот проект предназначен для наследования платформенными реализациями:
|
### С версии 1.x на 2.0
|
||||||
|
|
||||||
1. **WinUI**: `Lattice.UI.DragDrop.WinUI`
|
1. **Обновление интерфейсов**:
|
||||||
2. **Uno Platform**: `Lattice.UI.DragDrop.Uno` (планируется)
|
- Методы переименованы в соответствии с Core
|
||||||
3. **MAUI**: `Lattice.UI.DragDrop.Maui` (планируется)
|
- Добавлена поддержка CancellationToken
|
||||||
|
|
||||||
## 🧪 Тестирование
|
2. **Изменения в базовых классах**:
|
||||||
|
- `DragSourceBehaviorBase` теперь реализует новый интерфейс `IDragSource`
|
||||||
|
- `DropTargetBehaviorBase` теперь реализует новый интерфейс `IDropTarget`
|
||||||
|
|
||||||
Проект включает модульные тесты для всех публичных API:
|
3. **Регистрация сервисов**:
|
||||||
|
- Метод `AddLatticeDragDrop` теперь регистрирует только базовые сервисы
|
||||||
```bash
|
- Для платформенных реализаций используйте методы `AddDragVisualProvider`, `AddDropVisualAdorner`, `AddDragDropHost`
|
||||||
dotnet test Lattice.UI.DragDrop.Tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📄 Лицензия
|
## 📄 Лицензия
|
||||||
|
|
||||||
MIT License. Подробности в файле [LICENSE](LICENSE).
|
Библиотека распространяется под лицензией MIT. Подробности см. в файле LICENSE.
|
||||||
|
|
||||||
## 🤝 Участие в разработке
|
## 🤝 Вклад в разработку
|
||||||
|
|
||||||
1. Форкните репозиторий
|
Для интеграции с новой UI-платформой необходимо:
|
||||||
2. Создайте ветку для вашей функции
|
1. Реализовать интерфейсы `IDragDropHost`, `IDragVisualProvider`, `IDropVisualAdorner`
|
||||||
3. Сделайте коммит изменений
|
2. Создать производные классы от `DragSourceBehaviorBase` и `DropTargetBehaviorBase`
|
||||||
4. Отправьте пул-реквест
|
3. Предоставить метод расширения для регистрации всех компонентов
|
||||||
|
|
||||||
## 📞 Поддержка
|
## 🐛 Отчеты об ошибках
|
||||||
|
|
||||||
- Документация: [lattice-framework.github.io](https://lattice-framework.github.io)
|
Для сообщения об ошибках используйте Issues на GitHub. Пожалуйста, указывайте:
|
||||||
- Issues: [GitHub Issues](https://github.com/lattice-framework/ui-dragdrop/issues)
|
- Платформу и версию UI-фреймворка
|
||||||
- Обсуждения: [GitHub Discussions](https://github.com/lattice-framework/ui-dragdrop/discussions)
|
- Шаги для воспроизведения
|
||||||
|
- Ожидаемое и фактическое поведение
|
||||||
|
- Пример кода для демонстрации проблемы
|
||||||
Reference in New Issue
Block a user