Доработара WinUI реализация.
This commit is contained in:
@@ -1,290 +1,267 @@
|
||||
using Lattice.Core.DragDrop.Models;
|
||||
using Lattice.Core.DragDrop.Abstractions;
|
||||
using Lattice.Core.DragDrop.Models;
|
||||
using Lattice.Core.DragDrop.Services;
|
||||
using Lattice.Core.Geometry;
|
||||
using Lattice.UI.DragDrop.Behaviors;
|
||||
using Lattice.UI.DragDrop.WinUI.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// Поведение источника перетаскивания для WinUI FrameworkElement.
|
||||
/// Реализация поведения источника перетаскивания для элементов WinUI.
|
||||
/// </summary>
|
||||
public class WinUIDragSourceBehavior : DragSourceBehaviorBase<FrameworkElement>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот класс обрабатывает события мыши/тач и преобразует их в операции перетаскивания.
|
||||
/// Он реализует интерфейс <see cref="IDragSource"/> для интеграции с системой перетаскивания.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Поведение автоматически отслеживает начало перемещения мыши, проверяет порог
|
||||
/// перетаскивания и инициирует операцию через <see cref="IDragDropService"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class WinUIDragSourceBehavior : IDragSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Прикрепленное свойство для данных перетаскивания.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DragDataProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"DragData",
|
||||
typeof(object),
|
||||
typeof(WinUIDragSourceBehavior),
|
||||
new PropertyMetadata(null));
|
||||
#region Поля
|
||||
|
||||
private readonly IDragDropService _dragDropService;
|
||||
private readonly WinUIDragDropHost _host;
|
||||
|
||||
private FrameworkElement? _element;
|
||||
private object? _dragData;
|
||||
private Point _dragStartPosition;
|
||||
private bool _isDragging;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Конструктор
|
||||
|
||||
/// <summary>
|
||||
/// Прикрепленное свойство для включения перетаскивания.
|
||||
/// Инициализирует новый экземпляр класса <see cref="WinUIDragSourceBehavior"/>.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsEnabledProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsEnabled",
|
||||
typeof(bool),
|
||||
typeof(WinUIDragSourceBehavior),
|
||||
new PropertyMetadata(false, OnIsEnabledChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение DragData.
|
||||
/// </summary>
|
||||
public static object GetDragData(FrameworkElement element)
|
||||
/// <param name="dragDropService">Сервис для управления операциями перетаскивания.</param>
|
||||
/// <param name="host">Хост для отображения визуальных элементов.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если любой из параметров равен null.
|
||||
/// </exception>
|
||||
public WinUIDragSourceBehavior(IDragDropService dragDropService, WinUIDragDropHost host)
|
||||
{
|
||||
return element.GetValue(DragDataProperty);
|
||||
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
|
||||
_host = host ?? throw new ArgumentNullException(nameof(host));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Публичные методы
|
||||
|
||||
/// <summary>
|
||||
/// Прикрепляет поведение к указанному элементу.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
|
||||
/// <param name="dragData">Данные для перетаскивания. Если не указано, используются
|
||||
/// DataContext или Tag элемента.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="element"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// После вызова этого метода элемент начинает обрабатывать события перетаскивания.
|
||||
/// </remarks>
|
||||
public void Attach(FrameworkElement element, object? dragData = null)
|
||||
{
|
||||
Detach();
|
||||
|
||||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||||
_dragData = dragData ?? element.DataContext ?? element.Tag;
|
||||
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение DragData.
|
||||
/// Открепляет поведение от элемента.
|
||||
/// </summary>
|
||||
public static void SetDragData(FrameworkElement element, object value)
|
||||
public void Detach()
|
||||
{
|
||||
element.SetValue(DragDataProperty, value);
|
||||
}
|
||||
if (_element == null) return;
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение IsEnabled.
|
||||
/// </summary>
|
||||
public static bool GetIsEnabled(FrameworkElement element)
|
||||
{
|
||||
return (bool)element.GetValue(IsEnabledProperty);
|
||||
}
|
||||
UnsubscribeFromEvents();
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение IsEnabled.
|
||||
/// </summary>
|
||||
public static void SetIsEnabled(FrameworkElement element, bool value)
|
||||
{
|
||||
element.SetValue(IsEnabledProperty, value);
|
||||
}
|
||||
|
||||
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is FrameworkElement element)
|
||||
if (_isDragging)
|
||||
{
|
||||
// Получаем или создаем экземпляр поведения через Attached Property
|
||||
var behavior = GetBehavior(element);
|
||||
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
if (behavior == null)
|
||||
{
|
||||
behavior = new WinUIDragSourceBehavior();
|
||||
SetBehavior(element, behavior);
|
||||
}
|
||||
|
||||
behavior.AssociatedElement = element;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (behavior != null)
|
||||
{
|
||||
behavior.Detach();
|
||||
SetBehavior(element, null);
|
||||
}
|
||||
}
|
||||
_dragDropService.CancelDragAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_element = null;
|
||||
_dragData = null;
|
||||
_isDragging = false;
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
public WinUIDragSourceBehavior()
|
||||
: base(ServiceProviderHelper.GetServiceProvider())
|
||||
#endregion
|
||||
|
||||
#region Обработчики событий
|
||||
|
||||
private void SubscribeToEvents()
|
||||
{
|
||||
if (_element == null) return;
|
||||
|
||||
_element.PointerPressed += OnPointerPressed;
|
||||
_element.PointerMoved += OnPointerMoved;
|
||||
_element.PointerReleased += OnPointerReleased;
|
||||
_element.PointerCanceled += OnPointerCanceled;
|
||||
_element.PointerCaptureLost += OnPointerCaptureLost;
|
||||
}
|
||||
|
||||
protected override void SubscribeToEvents(FrameworkElement element)
|
||||
private void UnsubscribeFromEvents()
|
||||
{
|
||||
element.PointerPressed += OnPointerPressed;
|
||||
element.PointerMoved += OnPointerMoved;
|
||||
element.PointerReleased += OnPointerReleased;
|
||||
element.PointerCanceled += OnPointerCanceled;
|
||||
element.PointerCaptureLost += OnPointerCaptureLost;
|
||||
element.LostFocus += OnLostFocus;
|
||||
}
|
||||
if (_element == null) return;
|
||||
|
||||
protected override void UnsubscribeFromEvents(FrameworkElement element)
|
||||
{
|
||||
element.PointerPressed -= OnPointerPressed;
|
||||
element.PointerMoved -= OnPointerMoved;
|
||||
element.PointerReleased -= OnPointerReleased;
|
||||
element.PointerCanceled -= OnPointerCanceled;
|
||||
element.PointerCaptureLost -= OnPointerCaptureLost;
|
||||
element.LostFocus -= OnLostFocus;
|
||||
_element.PointerPressed -= OnPointerPressed;
|
||||
_element.PointerMoved -= OnPointerMoved;
|
||||
_element.PointerReleased -= OnPointerReleased;
|
||||
_element.PointerCanceled -= OnPointerCanceled;
|
||||
_element.PointerCaptureLost -= OnPointerCaptureLost;
|
||||
}
|
||||
|
||||
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (AssociatedElement == null) return;
|
||||
if (_element == null || _isDragging) return;
|
||||
|
||||
var point = e.GetCurrentPoint(AssociatedElement);
|
||||
OnInteractionStarted(new Point(point.Position.X, point.Position.Y));
|
||||
var point = e.GetCurrentPoint(_element);
|
||||
_dragStartPosition = new Point(point.Position.X, point.Position.Y);
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
private async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (AssociatedElement == null) return;
|
||||
if (_element == null || _isDragging || _cancellationTokenSource?.IsCancellationRequested == true)
|
||||
return;
|
||||
|
||||
var point = e.GetCurrentPoint(AssociatedElement);
|
||||
OnInteractionMoved(new Point(point.Position.X, point.Position.Y));
|
||||
var point = e.GetCurrentPoint(_element);
|
||||
var currentPosition = new Point(point.Position.X, point.Position.Y);
|
||||
|
||||
var distance = CalculateDistance(_dragStartPosition, currentPosition);
|
||||
if (distance > _dragDropService.DragStartThreshold)
|
||||
{
|
||||
await StartDragAsync(currentPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
OnInteractionEnded();
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
OnInteractionCancelled();
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
OnInteractionCancelled();
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void OnLostFocus(object sender, RoutedEventArgs e)
|
||||
private double CalculateDistance(Point p1, Point p2)
|
||||
{
|
||||
OnInteractionCancelled();
|
||||
var dx = p2.X - p1.X;
|
||||
var dy = p2.Y - p1.Y;
|
||||
return Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
protected override Point ConvertToScreenCoordinates(Point point)
|
||||
/// <summary>
|
||||
/// Преобразует координаты элемента в экранные координаты.
|
||||
/// </summary>
|
||||
/// <param name="point">Точка в координатах элемента.</param>
|
||||
/// <returns>Точка в экранных координатах.</returns>
|
||||
private Point ConvertToScreenCoordinates(Point point)
|
||||
{
|
||||
if (AssociatedElement == null)
|
||||
if (_element == null) return point;
|
||||
|
||||
try
|
||||
{
|
||||
var transform = _element.TransformToVisual(Window.Current.Content);
|
||||
var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y));
|
||||
return new Point(screenPoint.X, screenPoint.Y);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return point;
|
||||
|
||||
var transform = AssociatedElement.TransformToVisual(null);
|
||||
var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y));
|
||||
return new Point(screenPoint.X, screenPoint.Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
|
||||
{
|
||||
if (AssociatedElement == null)
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var data = GetDragData(AssociatedElement);
|
||||
if (data == null)
|
||||
{
|
||||
// Пробуем получить данные из Tag или других источников
|
||||
data = AssociatedElement.Tag ?? AssociatedElement.DataContext;
|
||||
}
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
// Получаем начальную позицию в экранных координатах
|
||||
var startPosition = ConvertToScreenCoordinates(_dragStartPosition);
|
||||
|
||||
// Создаем DragInfo с учетом вашего конструктора
|
||||
var dragInfo = new DragInfo(
|
||||
data: data,
|
||||
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Move |
|
||||
Core.DragDrop.Enums.DragDropEffects.Copy,
|
||||
startPosition: startPosition,
|
||||
source: this
|
||||
);
|
||||
|
||||
return (true, dragInfo);
|
||||
}
|
||||
|
||||
protected override void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects)
|
||||
{
|
||||
base.OnDragCompleted(dragInfo, effects);
|
||||
|
||||
// Визуальная обратная связь при завершении
|
||||
SetVisualState(AssociatedElement, "Normal");
|
||||
}
|
||||
|
||||
protected override void OnDragCancelled(DragInfo dragInfo)
|
||||
{
|
||||
base.OnDragCancelled(dragInfo);
|
||||
|
||||
// Визуальная обратная связь при отмене
|
||||
SetVisualState(AssociatedElement, "Normal");
|
||||
}
|
||||
|
||||
private void SetVisualState(FrameworkElement? element, string stateName)
|
||||
{
|
||||
if (element is Control control)
|
||||
{
|
||||
try
|
||||
{
|
||||
VisualStateManager.GoToState(control, stateName, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback
|
||||
control.Opacity = 1.0;
|
||||
}
|
||||
}
|
||||
else if (element != null)
|
||||
{
|
||||
// Альтернативная визуальная обратная связь для не-Control элементов
|
||||
element.Opacity = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Attached property для хранения экземпляра поведения
|
||||
private static readonly DependencyProperty BehaviorProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"Behavior",
|
||||
typeof(WinUIDragSourceBehavior),
|
||||
typeof(WinUIDragSourceBehavior),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
private static WinUIDragSourceBehavior GetBehavior(FrameworkElement element)
|
||||
private async Task StartDragAsync(Point position)
|
||||
{
|
||||
return (WinUIDragSourceBehavior)element.GetValue(BehaviorProperty);
|
||||
}
|
||||
if (_element == null || _dragData == null || _isDragging) return;
|
||||
|
||||
private static void SetBehavior(FrameworkElement element, WinUIDragSourceBehavior? value)
|
||||
{
|
||||
element.SetValue(BehaviorProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вспомогательный класс для получения IServiceProvider.
|
||||
/// </summary>
|
||||
internal static class ServiceProviderHelper
|
||||
{
|
||||
private static IServiceProvider? _serviceProvider;
|
||||
|
||||
public static IServiceProvider GetServiceProvider()
|
||||
{
|
||||
if (_serviceProvider == null)
|
||||
try
|
||||
{
|
||||
// Ищем IServiceProvider в Application.Current.Resources
|
||||
if (Application.Current.Resources.TryGetValue("ServiceProvider", out var provider) &&
|
||||
provider is IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"IServiceProvider не найден. Убедитесь, что ServiceProvider зарегистрирован в ресурсах приложения.");
|
||||
}
|
||||
var screenPosition = ConvertToScreenCoordinates(position);
|
||||
var started = await _dragDropService.StartDragAsync(this, screenPosition);
|
||||
_isDragging = started;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ResetState();
|
||||
}
|
||||
|
||||
return _serviceProvider;
|
||||
}
|
||||
|
||||
public static void SetServiceProvider(IServiceProvider serviceProvider)
|
||||
private void ResetState()
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_isDragging = false;
|
||||
_dragStartPosition = default;
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDragSource Implementation
|
||||
|
||||
public async Task<DragInfo?> TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_element == null || _dragData == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var dragInfo = new DragInfo(
|
||||
data: _dragData,
|
||||
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy |
|
||||
Core.DragDrop.Enums.DragDropEffects.Move,
|
||||
startPosition: startPosition,
|
||||
source: this
|
||||
);
|
||||
|
||||
return dragInfo;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnDragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_isDragging = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_isDragging = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user