Files
Lattice/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs

301 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
/// &lt;Border x:Name="DragElement"
/// local:DragDropProperties.IsDragSource="True"
/// local:DragDropProperties.DragData="{Binding MyData}" /&gt;
/// </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
}