DragAndDrop core

This commit is contained in:
FrigaT
2026-01-18 16:33:35 +03:00
parent 9ea82af329
commit 79bdd8bc62
229 changed files with 21214 additions and 2494 deletions

View File

@@ -0,0 +1,429 @@
using Lattice.Core.Geometry;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
/// <summary>
/// Поведение источника перетаскивания для WinUI UIElement.
/// Исправленная версия с правильной очисткой ресурсов.
/// </summary>
public static class WinUIDragSourceBehavior
{
#region Вложенные типы
private sealed class DragSourceContext : IDisposable
{
public Point DragStartPosition { get; set; }
public bool IsDragging { get; set; }
public UIElement? CurrentDragElement { get; set; }
public object? DragData { get; set; }
public Action<UIElement>? DragStartedHandler { get; set; }
public Action<UIElement, Core.DragDrop.Enums.DragDropEffects>? DragCompletedHandler { get; set; }
public Core.DragDrop.Enums.DragDropEffects AllowedEffects { get; set; }
= Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move;
public void Dispose()
{
DragStartedHandler = null;
DragCompletedHandler = null;
DragData = null;
CurrentDragElement = null;
}
}
#endregion
#region Прикрепленные свойства
/// <summary>
/// Идентификатор свойства для привязки данных перетаскивания.
/// </summary>
public static readonly DependencyProperty DragDataProperty =
DependencyProperty.RegisterAttached(
"DragData",
typeof(object),
typeof(WinUIDragSourceBehavior),
new PropertyMetadata(null, OnDragDataChanged));
/// <summary>
/// Идентификатор свойства для включения перетаскивания.
/// </summary>
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(WinUIDragSourceBehavior),
new PropertyMetadata(false, OnIsEnabledChanged));
/// <summary>
/// Идентификатор свойства для разрешенных эффектов перетаскивания.
/// </summary>
public static readonly DependencyProperty AllowedEffectsProperty =
DependencyProperty.RegisterAttached(
"AllowedEffects",
typeof(Core.DragDrop.Enums.DragDropEffects),
typeof(WinUIDragSourceBehavior),
new PropertyMetadata(Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move));
/// <summary>
/// Идентификатор свойства для обработчика начала перетаскивания.
/// </summary>
public static readonly DependencyProperty DragStartedHandlerProperty =
DependencyProperty.RegisterAttached(
"DragStartedHandler",
typeof(Action<UIElement>),
typeof(WinUIDragSourceBehavior),
new PropertyMetadata(null));
/// <summary>
/// Идентификатор свойства для обработчика завершения перетаскивания.
/// </summary>
public static readonly DependencyProperty DragCompletedHandlerProperty =
DependencyProperty.RegisterAttached(
"DragCompletedHandler",
typeof(Action<UIElement, Core.DragDrop.Enums.DragDropEffects>),
typeof(WinUIDragSourceBehavior),
new PropertyMetadata(null));
/// <summary>
/// Идентификатор свойства для обработчика создания данных перетаскивания.
/// </summary>
public static readonly DependencyProperty DragDataFactoryProperty =
DependencyProperty.RegisterAttached(
"DragDataFactory",
typeof(Func<UIElement, Task<object>>),
typeof(WinUIDragSourceBehavior),
new PropertyMetadata(null));
#endregion
#region Статические поля
private static readonly ConditionalWeakTable<UIElement, DragSourceContext> _contexts = new();
#endregion
#region Методы доступа к свойствам
/// <summary>
/// Получает значение свойства DragData для указанного элемента.
/// </summary>
public static object GetDragData(UIElement element) =>
element.GetValue(DragDataProperty);
/// <summary>
/// Устанавливает значение свойства DragData для указанного элемента.
/// </summary>
public static void SetDragData(UIElement element, object value) =>
element.SetValue(DragDataProperty, value);
/// <summary>
/// Получает значение свойства IsEnabled для указанного элемента.
/// </summary>
public static bool GetIsEnabled(UIElement element) =>
(bool)element.GetValue(IsEnabledProperty);
/// <summary>
/// Устанавливает значение свойства IsEnabled для указанного элемента.
/// </summary>
public static void SetIsEnabled(UIElement element, bool value) =>
element.SetValue(IsEnabledProperty, value);
/// <summary>
/// Получает значение свойства AllowedEffects для указанного элемента.
/// </summary>
public static Core.DragDrop.Enums.DragDropEffects GetAllowedEffects(UIElement element) =>
(Core.DragDrop.Enums.DragDropEffects)element.GetValue(AllowedEffectsProperty);
/// <summary>
/// Устанавливает значение свойства AllowedEffects для указанного элемента.
/// </summary>
public static void SetAllowedEffects(UIElement element, Core.DragDrop.Enums.DragDropEffects value) =>
element.SetValue(AllowedEffectsProperty, value);
/// <summary>
/// Получает обработчик начала перетаскивания.
/// </summary>
public static Action<UIElement> GetDragStartedHandler(UIElement element) =>
(Action<UIElement>)element.GetValue(DragStartedHandlerProperty);
/// <summary>
/// Устанавливает обработчик начала перетаскивания.
/// </summary>
public static void SetDragStartedHandler(UIElement element, Action<UIElement> value) =>
element.SetValue(DragStartedHandlerProperty, value);
/// <summary>
/// Получает обработчик завершения перетаскивания.
/// </summary>
public static Action<UIElement, Core.DragDrop.Enums.DragDropEffects> GetDragCompletedHandler(UIElement element) =>
(Action<UIElement, Core.DragDrop.Enums.DragDropEffects>)element.GetValue(DragCompletedHandlerProperty);
/// <summary>
/// Устанавливает обработчик завершения перетаскивания.
/// </summary>
public static void SetDragCompletedHandler(UIElement element, Action<UIElement, Core.DragDrop.Enums.DragDropEffects> value) =>
element.SetValue(DragCompletedHandlerProperty, value);
/// <summary>
/// Получает фабрику данных перетаскивания.
/// </summary>
public static Func<UIElement, Task<object>> GetDragDataFactory(UIElement element) =>
(Func<UIElement, Task<object>>)element.GetValue(DragDataFactoryProperty);
/// <summary>
/// Устанавливает фабрику данных перетаскивания.
/// </summary>
public static void SetDragDataFactory(UIElement element, Func<UIElement, Task<object>> value) =>
element.SetValue(DragDataFactoryProperty, value);
#endregion
#region Обработчики изменений свойств
private static void OnDragDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement element)
{
if (_contexts.TryGetValue(element, out var context))
{
context.DragData = e.NewValue;
}
}
}
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement element)
{
if ((bool)e.NewValue)
{
EnableDrag(element);
}
else
{
DisableDrag(element);
}
}
}
#endregion
#region Включение/выключение перетаскивания
private static void EnableDrag(UIElement element)
{
if (_contexts.TryGetValue(element, out _))
return;
var context = new DragSourceContext
{
DragData = GetDragData(element),
DragStartedHandler = GetDragStartedHandler(element),
DragCompletedHandler = GetDragCompletedHandler(element),
AllowedEffects = GetAllowedEffects(element)
};
_contexts.Add(element, context);
element.PointerPressed += OnPointerPressed;
element.PointerMoved += OnPointerMoved;
element.PointerReleased += OnPointerReleased;
element.PointerCanceled += OnPointerCanceled;
element.PointerCaptureLost += OnPointerCaptureLost;
element.Unloaded += OnElementUnloaded;
}
private static void DisableDrag(UIElement element)
{
element.PointerPressed -= OnPointerPressed;
element.PointerMoved -= OnPointerMoved;
element.PointerReleased -= OnPointerReleased;
element.PointerCanceled -= OnPointerCanceled;
element.PointerCaptureLost -= OnPointerCaptureLost;
element.Unloaded -= OnElementUnloaded;
if (_contexts.TryGetValue(element, out var context))
{
context.Dispose();
_contexts.Remove(element);
}
}
#endregion
#region Обработчики событий
private static void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
if (sender is UIElement element && _contexts.TryGetValue(element, out var context))
{
var point = e.GetCurrentPoint(element);
context.DragStartPosition = new Point(point.Position.X, point.Position.Y);
context.CurrentDragElement = element;
element.CapturePointer(e.Pointer);
}
}
private static async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
if (sender is UIElement element && _contexts.TryGetValue(element, out var context))
{
if (context.IsDragging || context.CurrentDragElement == null)
return;
var currentPoint = e.GetCurrentPoint(context.CurrentDragElement);
var currentPosition = new Point(currentPoint.Position.X, currentPoint.Position.Y);
var distance = CalculateDistance(context.DragStartPosition, currentPosition);
if (distance > 3.0) // Порог перетаскивания
{
context.IsDragging = true;
await StartDragAsync(context.CurrentDragElement, currentPosition, context);
}
}
}
private static async Task StartDragAsync(UIElement element, Point currentPosition, DragSourceContext context)
{
try
{
object? data = context.DragData;
// Если есть фабрика данных, используем ее
var factory = GetDragDataFactory(element);
if (factory != null)
{
data = await factory(element);
}
if (data != null)
{
context.DragData = data;
// Вызываем обработчик начала перетаскивания
context.DragStartedHandler?.Invoke(element);
// Устанавливаем визуальный эффект
SetDragVisualState(element, true);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error starting drag: {ex.Message}");
context.IsDragging = false;
}
}
private static void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
CompleteDrag(sender as UIElement, Core.DragDrop.Enums.DragDropEffects.Copy);
}
private static void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
CompleteDrag(sender as UIElement, Core.DragDrop.Enums.DragDropEffects.None);
}
private static void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
CompleteDrag(sender as UIElement, Core.DragDrop.Enums.DragDropEffects.None);
}
private static void OnElementUnloaded(object sender, RoutedEventArgs e)
{
if (sender is UIElement element)
{
DisableDrag(element);
}
}
#endregion
#region Вспомогательные методы
private static void CompleteDrag(UIElement? element, Core.DragDrop.Enums.DragDropEffects effects)
{
if (element != null && _contexts.TryGetValue(element, out var context))
{
// Вызываем обработчик завершения перетаскивания
context.DragCompletedHandler?.Invoke(element, effects);
// Сбрасываем визуальный эффект
SetDragVisualState(element, false);
element.ReleasePointerCaptures();
ResetDragState(context);
}
}
private static void SetDragVisualState(UIElement element, bool isDragging)
{
// Для элементов, которые являются Control, используем VisualStateManager
if (element is Control control)
{
// Пытаемся перейти к состоянию "Dragging" или "Normal"
try
{
VisualStateManager.GoToState(control, isDragging ? "Dragging" : "Normal", true);
}
catch
{
// Если состояния не определены, меняем свойства напрямую
control.Opacity = isDragging ? 0.7 : 1.0;
}
}
else
{
// Для обычных UIElement меняем свойства напрямую
element.Opacity = isDragging ? 0.7 : 1.0;
}
}
private static double CalculateDistance(Point p1, Point p2)
{
var dx = p2.X - p1.X;
var dy = p2.Y - p1.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
private static void ResetDragState(DragSourceContext context)
{
context.IsDragging = false;
context.CurrentDragElement = null;
context.DragStartPosition = default;
}
#endregion
#region Методы очистки
/// <summary>
/// Очищает все ресурсы, связанные с поведением перетаскивания.
/// </summary>
public static void Cleanup()
{
var elements = new List<UIElement>();
foreach (var kvp in _contexts)
{
elements.Add(kvp.Key);
}
foreach (var element in elements)
{
DisableDrag(element);
}
_contexts.Clear();
}
#endregion
}