301 lines
12 KiB
C#
301 lines
12 KiB
C#
using Lattice.Core.DragDrop.Models;
|
||
using Lattice.Core.DragDrop.Services;
|
||
using Lattice.Core.Geometry;
|
||
using Lattice.UI.DragDrop.Abstractions;
|
||
using Lattice.UI.DragDrop.Behaviors;
|
||
using Lattice.UI.DragDrop.WinUI.Services;
|
||
using Microsoft.UI.Xaml;
|
||
using Microsoft.UI.Xaml.Input;
|
||
using System;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
|
||
|
||
/// <summary>
|
||
/// Реализация поведения источника перетаскивания для элементов WinUI.
|
||
/// Наследуется от <see cref="DragSourceBehaviorBase{FrameworkElement}"/> для использования
|
||
/// общей логики управления операциями перетаскивания и интеграции с системой <see cref="Core.DragDrop"/>.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>
|
||
/// Этот класс предоставляет конкретную реализацию для платформы WinUI, обрабатывая события
|
||
/// указателя (мышь, тач, перо) и преобразуя их в операции перетаскивания через центральный
|
||
/// сервис <see cref="IDragDropService"/>.
|
||
/// </para>
|
||
/// <para>
|
||
/// Основные функции:
|
||
/// <list type="bullet">
|
||
/// <item>Обработка событий PointerPressed, PointerMoved, PointerReleased</item>
|
||
/// <item>Автоматическое отслеживание порога начала перетаскивания</item>
|
||
/// <item>Создание информации о перетаскивании на основе данных элемента</item>
|
||
/// <item>Интеграция с визуальной обратной связью через <see cref="WinUIDragDropHost"/></item>
|
||
/// <item>Преобразование координат между локальной системой элемента и экранными координатами</item>
|
||
/// </list>
|
||
/// </para>
|
||
/// <para>
|
||
/// Для использования необходимо:
|
||
/// <list type="number">
|
||
/// <item>Создать экземпляр поведения через фабрику <see cref="Factories.WinUIDragDropFactory.CreateDragSourceBehavior"/></item>
|
||
/// <item>Прикрепить к элементу с помощью метода <see cref="Attach"/></item>
|
||
/// <item>Указать данные для перетаскивания (опционально, по умолчанию используется DataContext)</item>
|
||
/// </list>
|
||
/// </para>
|
||
/// <example>
|
||
/// <code>
|
||
/// // Создание поведения
|
||
/// var behavior = WinUIDragDropFactory.CreateDragSourceBehavior(dragDropService, host);
|
||
///
|
||
/// // Прикрепление к элементу
|
||
/// behavior.Attach(myElement, myData);
|
||
///
|
||
/// // Или через attached properties
|
||
/// <Border x:Name="DragElement"
|
||
/// local:DragDropProperties.IsDragSource="True"
|
||
/// local:DragDropProperties.DragData="{Binding MyData}" />
|
||
/// </code>
|
||
/// </example>
|
||
/// </remarks>
|
||
public sealed class WinUIDragSourceBehavior : DragSourceBehaviorBase<FrameworkElement>
|
||
{
|
||
#region Поля
|
||
|
||
private readonly IDragDropHost _host;
|
||
private object? _dragData;
|
||
|
||
#endregion
|
||
|
||
#region Конструктор
|
||
|
||
/// <summary>
|
||
/// Инициализирует новый экземпляр класса <see cref="WinUIDragSourceBehavior"/>.
|
||
/// </summary>
|
||
/// <param name="dragDropService">
|
||
/// Сервис управления операциями перетаскивания. Используется для координации
|
||
/// между источниками и целями перетаскивания.
|
||
/// </param>
|
||
/// <param name="host">
|
||
/// Хост для управления визуальными элементами перетаскивания. Обеспечивает
|
||
/// отображение визуальной обратной связи во время операции.
|
||
/// </param>
|
||
/// <exception cref="ArgumentNullException">
|
||
/// Выбрасывается, если <paramref name="dragDropService"/> или <paramref name="host"/>
|
||
/// равны null.
|
||
/// </exception>
|
||
/// <remarks>
|
||
/// Конструктор инициализирует базовый класс <see cref="DragSourceBehaviorBase{FrameworkElement}"/>
|
||
/// и сохраняет ссылки на необходимые сервисы для последующего использования.
|
||
/// </remarks>
|
||
public WinUIDragSourceBehavior(
|
||
Core.DragDrop.Services.IDragDropService dragDropService,
|
||
WinUIDragDropHost host)
|
||
: base(dragDropService)
|
||
{
|
||
_host = host ?? throw new ArgumentNullException(nameof(host));
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Публичные методы
|
||
|
||
/// <summary>
|
||
/// Прикрепляет поведение к указанному элементу WinUI.
|
||
/// </summary>
|
||
/// <param name="element">
|
||
/// Элемент <see cref="FrameworkElement"/>, который должен стать источником перетаскивания.
|
||
/// Не может быть null.
|
||
/// </param>
|
||
/// <param name="dragData">
|
||
/// Данные, которые будут перетаскиваться. Может быть null.
|
||
/// Если не указано, используется <see cref="FrameworkElement.DataContext"/> или
|
||
/// <see cref="FrameworkElement.Tag"/> элемента.
|
||
/// </param>
|
||
/// <exception cref="ArgumentNullException">
|
||
/// Выбрасывается, если <paramref name="element"/> равен null.
|
||
/// </exception>
|
||
/// <remarks>
|
||
/// <para>
|
||
/// После вызова этого метода:
|
||
/// <list type="bullet">
|
||
/// <item>Элементу автоматически подписываются обработчики событий указателя</item>
|
||
/// <item>Поведение начинает отслеживать взаимодействия с элементом</item>
|
||
/// <item>При превышении порога перетаскивания инициируется операция через <see cref="Core.DragDrop.Services.IDragDropService"/></item>
|
||
/// </list>
|
||
/// </para>
|
||
/// <para>
|
||
/// Для открепления поведения используйте метод <see cref="Detach"/>.
|
||
/// </para>
|
||
/// </remarks>
|
||
public void Attach(FrameworkElement element, object? dragData = null)
|
||
{
|
||
if (element == null)
|
||
throw new ArgumentNullException(nameof(element));
|
||
|
||
_dragData = dragData ?? element.DataContext ?? element.Tag;
|
||
AssociatedElement = element;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Открепляет поведение от текущего элемента.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>
|
||
/// Этот метод выполняет следующие действия:
|
||
/// <list type="bullet">
|
||
/// <item>Отписывается от всех событий элемента</item>
|
||
/// <item>Сбрасывает внутреннее состояние</item>
|
||
/// <item>Освобождает ссылки на связанные объекты</item>
|
||
/// </list>
|
||
/// </para>
|
||
/// <para>
|
||
/// Если в момент вызова активна операция перетаскивания, она будет автоматически отменена.
|
||
/// </para>
|
||
/// <para>
|
||
/// После вызова этого метода поведение может быть повторно прикреплено к другому элементу.
|
||
/// </para>
|
||
/// </remarks>
|
||
public new void Detach()
|
||
{
|
||
base.Detach();
|
||
_dragData = null;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Реализация абстрактных методов DragSourceBehaviorBase<FrameworkElement>
|
||
|
||
/// <inheritdoc/>
|
||
protected override void SubscribeToEvents(FrameworkElement element)
|
||
{
|
||
if (element == null) return;
|
||
|
||
element.PointerPressed += OnPointerPressed;
|
||
element.PointerMoved += OnPointerMoved;
|
||
element.PointerReleased += OnPointerReleased;
|
||
element.PointerCanceled += OnPointerCanceled;
|
||
element.PointerCaptureLost += OnPointerCaptureLost;
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
protected override void UnsubscribeFromEvents(FrameworkElement element)
|
||
{
|
||
if (element == null) return;
|
||
|
||
element.PointerPressed -= OnPointerPressed;
|
||
element.PointerMoved -= OnPointerMoved;
|
||
element.PointerReleased -= OnPointerReleased;
|
||
element.PointerCanceled -= OnPointerCanceled;
|
||
element.PointerCaptureLost -= OnPointerCaptureLost;
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
protected override Point ConvertToScreenCoordinates(Point point)
|
||
{
|
||
if (AssociatedElement == null)
|
||
return point;
|
||
|
||
return WinUIWindowHelper.ConvertToScreenCoordinates(AssociatedElement, point);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Реализация интерфейса IDragSource
|
||
|
||
/// <inheritdoc/>
|
||
public override async Task<DragInfo?> TryStartDragAsync(
|
||
Point startPosition,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
if (AssociatedElement == null || _dragData == null)
|
||
return null;
|
||
|
||
try
|
||
{
|
||
// Создаем информацию о перетаскивании
|
||
var dragInfo = new DragInfo(
|
||
data: _dragData,
|
||
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy |
|
||
Core.DragDrop.Enums.DragDropEffects.Move |
|
||
Core.DragDrop.Enums.DragDropEffects.Link,
|
||
startPosition: startPosition,
|
||
source: this
|
||
);
|
||
|
||
// Добавляем дополнительные параметры
|
||
dragInfo.SetParameter("SourceElement", AssociatedElement);
|
||
dragInfo.SetParameter("SourceType", AssociatedElement.GetType().Name);
|
||
|
||
return await Task.FromResult(dragInfo);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// Логирование ошибки создания информации о перетаскивании
|
||
System.Diagnostics.Debug.WriteLine(
|
||
$"Ошибка создания DragInfo: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Обработчики событий WinUI
|
||
|
||
private async void OnPointerPressed(object sender, PointerRoutedEventArgs e)
|
||
{
|
||
if (AssociatedElement == null) return;
|
||
|
||
var point = e.GetCurrentPoint(AssociatedElement);
|
||
var position = new Point(point.Position.X, point.Position.Y);
|
||
|
||
await OnInteractionStarted(position);
|
||
}
|
||
|
||
private async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
|
||
{
|
||
if (AssociatedElement == null) return;
|
||
|
||
var point = e.GetCurrentPoint(AssociatedElement);
|
||
var position = new Point(point.Position.X, point.Position.Y);
|
||
|
||
await OnInteractionMoved(position);
|
||
}
|
||
|
||
private async void OnPointerReleased(object sender, PointerRoutedEventArgs e)
|
||
{
|
||
await OnInteractionEnded();
|
||
}
|
||
|
||
private async void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
|
||
{
|
||
await OnInteractionCancelled();
|
||
}
|
||
|
||
private async void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
|
||
{
|
||
await OnInteractionCancelled();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Переопределение виртуальных методов
|
||
|
||
/// <inheritdoc/>
|
||
protected override void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects)
|
||
{
|
||
base.OnDragCompleted(dragInfo, effects);
|
||
|
||
// Дополнительная логика для WinUI может быть добавлена здесь
|
||
// Например, обновление состояния элемента после успешного перетаскивания
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
protected override void OnDragCancelled(DragInfo dragInfo)
|
||
{
|
||
base.OnDragCancelled(dragInfo);
|
||
|
||
// Дополнительная логика для WinUI может быть добавлена здесь
|
||
// Например, восстановление визуального состояния элемента после отмены
|
||
}
|
||
|
||
#endregion
|
||
} |