Доработан проект UI под новый Core

This commit is contained in:
2026-01-25 02:52:07 +03:00
parent be12154262
commit 2bd7d3c474
7 changed files with 923 additions and 181 deletions

View File

@@ -10,9 +10,20 @@ using System.Threading.Tasks;
namespace Lattice.UI.DragDrop.Behaviors;
/// <summary>
/// Базовый класс поведения цели сброса.
/// Базовый класс поведения цели сброса для UI элементов.
/// </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
where TElement : class
{
@@ -22,8 +33,12 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
private Rect _currentBounds;
/// <summary>
/// Получает или задает связанный элемент.
/// Получает или задает связанный UI элемент.
/// </summary>
/// <value>
/// Элемент UI, к которому прикреплено поведение цели сброса.
/// При изменении значения автоматически выполняется перерегистрация в сервисе перетаскивания.
/// </value>
protected TElement? AssociatedElement
{
get => _associatedElement;
@@ -41,16 +56,28 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
/// <summary>
/// Получает или задает приоритет цели сброса.
/// </summary>
/// <value>
/// Цели с более высоким приоритетом проверяются первыми при нахождении курсора
/// в области нескольких целей. Значение по умолчанию: 0.
/// </value>
public int Priority { get; set; }
/// <summary>
/// Получает или задает группу цели сброса.
/// </summary>
/// <value>
/// Имя группы для группового управления целями сброса. Может использоваться
/// для массовой отмены регистрации целей или применения общих настроек.
/// </value>
public string? Group { get; set; }
/// <summary>
/// Получает сервис перетаскивания.
/// Получает сервис перетаскивания из контейнера зависимостей.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDragDropService"/>, используемый для регистрации цели сброса.
/// При первом обращении выполняется получение сервиса из <see cref="ServiceProvider"/>.
/// </value>
protected IDragDropService DragDropService
{
get
@@ -64,27 +91,47 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
}
/// <summary>
/// Получает провайдер сервисов.
/// Получает провайдер сервисов для разрешения зависимостей.
/// </summary>
protected IServiceProvider ServiceProvider { get; }
/// <summary>
/// Получает текущие границы элемента в экранных координатах.
/// </summary>
/// <value>
/// Прямоугольник, описывающий границы элемента в экранных координатах.
/// Значение автоматически обновляется при изменении размера или позиции элемента.
/// </value>
protected Rect CurrentBounds => _currentBounds;
/// <summary>
/// Получает уникальный идентификатор регистрации цели в сервисе перетаскивания.
/// </summary>
/// <value>
/// Идентификатор, возвращенный методом <see cref="IDragDropService.RegisterDropTarget"/>,
/// или null, если цель не зарегистрирована.
/// </value>
protected string? RegistrationId => _registrationId;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DropTargetBehaviorBase{TElement}"/>.
/// </summary>
/// <param name="serviceProvider">Провайдер сервисов.</param>
/// <param name="serviceProvider">Провайдер сервисов для разрешения зависимостей.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, когда <paramref name="serviceProvider"/> равен null.
/// </exception>
protected DropTargetBehaviorBase(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
/// <summary>
/// Вызывается при прикреплении к элементу.
/// Вызывается при прикреплении поведения к элементу.
/// </summary>
/// <remarks>
/// Реализация по умолчанию подписывается на события элемента, обновляет границы
/// и регистрирует цель в сервисе перетаскивания.
/// </remarks>
protected virtual void AttachToElement()
{
if (_associatedElement != null)
@@ -96,8 +143,12 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
}
/// <summary>
/// Вызывается при откреплении от элемента.
/// Вызывается при откреплении поведения от элемента.
/// </summary>
/// <remarks>
/// Реализация по умолчанию отписывается от событий элемента и отменяет
/// регистрацию цели в сервисе перетаскивания.
/// </remarks>
protected virtual void DetachFromElement()
{
if (_associatedElement != null)
@@ -108,20 +159,32 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
}
/// <summary>
/// Подписывается на события элемента.
/// Подписывается на события элемента, необходимые для отслеживания изменений размера и позиции.
/// </summary>
/// <param name="element">Элемент.</param>
/// <param name="element">Элемент, к событиям которого нужно подписаться.</param>
/// <remarks>
/// Производные классы должны реализовать этот метод для подписки на события конкретной
/// UI-платформы (например, SizeChanged, LayoutUpdated для WPF).
/// </remarks>
protected abstract void SubscribeToEvents(TElement element);
/// <summary>
/// Отписывается от событий элемента.
/// </summary>
/// <param name="element">Элемент.</param>
/// <param name="element">Элемент, от событий которого нужно отписаться.</param>
/// <remarks>
/// Производные классы должны реализовать этот метод для корректной отписки
/// от событий, на которые была выполнена подписка в <see cref="SubscribeToEvents"/>.
/// </remarks>
protected abstract void UnsubscribeFromEvents(TElement element);
/// <summary>
/// Обновляет границы элемента в экранных координатах.
/// </summary>
/// <remarks>
/// Этот метод вызывается при изменении размера или позиции элемента для
/// обновления области, в которой цель может принимать сбрасываемые данные.
/// </remarks>
protected virtual void UpdateBounds()
{
if (_associatedElement != null)
@@ -139,13 +202,27 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
/// <summary>
/// Получает границы элемента в экранных координатах.
/// </summary>
/// <param name="element">Элемент.</param>
/// <returns>Границы в экранных координатах.</returns>
/// <param name="element">Элемент, границы которого нужно получить.</param>
/// <returns>Границы элемента в экранных координатах.</returns>
/// <remarks>
/// Производные классы должны реализовать этот метод для получения границ
/// элемента в соответствии с конкретной UI-платформой.
/// </remarks>
protected abstract Rect GetScreenBounds(TElement element);
/// <summary>
/// Регистрирует цель в сервисе перетаскивания.
/// </summary>
/// <remarks>
/// <para>
/// Этот метод регистрирует текущий объект как цель сброса в сервисе перетаскивания
/// с указанными приоритетом и группой.
/// </para>
/// <para>
/// Регистрация выполняется только если поведение прикреплено к элементу и
/// цель еще не зарегистрирована.
/// </para>
/// </remarks>
protected virtual void RegisterToService()
{
if (_associatedElement != null && _registrationId == null)
@@ -158,6 +235,10 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
/// <summary>
/// Отменяет регистрацию цели в сервисе перетаскивания.
/// </summary>
/// <remarks>
/// Этот метод отменяет регистрацию цели сброса, освобождая ресурсы
/// в сервисе перетаскивания и предотвращая дальнейшую обработку событий.
/// </remarks>
protected virtual void UnregisterFromService()
{
if (_registrationId != null)
@@ -170,6 +251,10 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
/// <summary>
/// Вызывается при изменении размера или позиции элемента.
/// </summary>
/// <remarks>
/// Производные классы должны вызывать этот метод из обработчиков событий
/// изменения размера или позиции элемента для обновления границ цели.
/// </remarks>
protected virtual void OnElementLayoutChanged()
{
UpdateBounds();
@@ -181,7 +266,7 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
public abstract Task<bool> CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default);
/// <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))
@@ -207,22 +292,58 @@ public abstract class DropTargetBehaviorBase<TElement> : IDropTarget
}
/// <inheritdoc/>
public abstract Task DropAsync(DropInfo dropInfo, CancellationToken ct = default);
public abstract Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default);
/// <inheritdoc/>
public virtual async Task DragLeaveAsync(CancellationToken ct = default)
public virtual Task OnDragLeaveAsync(CancellationToken cancellationToken = default)
{
// Базовая реализация не делает ничего
// Базовая реализация не выполняет действий при выходе курсора из области цели
return Task.CompletedTask;
}
#endregion
/// <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()
{
DetachFromElement();
_associatedElement = null;
_registrationId = null;
_currentBounds = default;
}
}