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; /// /// Поведение источника перетаскивания для WinUI UIElement. /// Исправленная версия с правильной очисткой ресурсов. /// 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? DragStartedHandler { get; set; } public Action? 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 Прикрепленные свойства /// /// Идентификатор свойства для привязки данных перетаскивания. /// public static readonly DependencyProperty DragDataProperty = DependencyProperty.RegisterAttached( "DragData", typeof(object), typeof(WinUIDragSourceBehavior), new PropertyMetadata(null, OnDragDataChanged)); /// /// Идентификатор свойства для включения перетаскивания. /// public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached( "IsEnabled", typeof(bool), typeof(WinUIDragSourceBehavior), new PropertyMetadata(false, OnIsEnabledChanged)); /// /// Идентификатор свойства для разрешенных эффектов перетаскивания. /// 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)); /// /// Идентификатор свойства для обработчика начала перетаскивания. /// public static readonly DependencyProperty DragStartedHandlerProperty = DependencyProperty.RegisterAttached( "DragStartedHandler", typeof(Action), typeof(WinUIDragSourceBehavior), new PropertyMetadata(null)); /// /// Идентификатор свойства для обработчика завершения перетаскивания. /// public static readonly DependencyProperty DragCompletedHandlerProperty = DependencyProperty.RegisterAttached( "DragCompletedHandler", typeof(Action), typeof(WinUIDragSourceBehavior), new PropertyMetadata(null)); /// /// Идентификатор свойства для обработчика создания данных перетаскивания. /// public static readonly DependencyProperty DragDataFactoryProperty = DependencyProperty.RegisterAttached( "DragDataFactory", typeof(Func>), typeof(WinUIDragSourceBehavior), new PropertyMetadata(null)); #endregion #region Статические поля private static readonly ConditionalWeakTable _contexts = new(); #endregion #region Методы доступа к свойствам /// /// Получает значение свойства DragData для указанного элемента. /// public static object GetDragData(UIElement element) => element.GetValue(DragDataProperty); /// /// Устанавливает значение свойства DragData для указанного элемента. /// public static void SetDragData(UIElement element, object value) => element.SetValue(DragDataProperty, value); /// /// Получает значение свойства IsEnabled для указанного элемента. /// public static bool GetIsEnabled(UIElement element) => (bool)element.GetValue(IsEnabledProperty); /// /// Устанавливает значение свойства IsEnabled для указанного элемента. /// public static void SetIsEnabled(UIElement element, bool value) => element.SetValue(IsEnabledProperty, value); /// /// Получает значение свойства AllowedEffects для указанного элемента. /// public static Core.DragDrop.Enums.DragDropEffects GetAllowedEffects(UIElement element) => (Core.DragDrop.Enums.DragDropEffects)element.GetValue(AllowedEffectsProperty); /// /// Устанавливает значение свойства AllowedEffects для указанного элемента. /// public static void SetAllowedEffects(UIElement element, Core.DragDrop.Enums.DragDropEffects value) => element.SetValue(AllowedEffectsProperty, value); /// /// Получает обработчик начала перетаскивания. /// public static Action GetDragStartedHandler(UIElement element) => (Action)element.GetValue(DragStartedHandlerProperty); /// /// Устанавливает обработчик начала перетаскивания. /// public static void SetDragStartedHandler(UIElement element, Action value) => element.SetValue(DragStartedHandlerProperty, value); /// /// Получает обработчик завершения перетаскивания. /// public static Action GetDragCompletedHandler(UIElement element) => (Action)element.GetValue(DragCompletedHandlerProperty); /// /// Устанавливает обработчик завершения перетаскивания. /// public static void SetDragCompletedHandler(UIElement element, Action value) => element.SetValue(DragCompletedHandlerProperty, value); /// /// Получает фабрику данных перетаскивания. /// public static Func> GetDragDataFactory(UIElement element) => (Func>)element.GetValue(DragDataFactoryProperty); /// /// Устанавливает фабрику данных перетаскивания. /// public static void SetDragDataFactory(UIElement element, Func> 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 Методы очистки /// /// Очищает все ресурсы, связанные с поведением перетаскивания. /// public static void Cleanup() { var elements = new List(); foreach (var kvp in _contexts) { elements.Add(kvp.Key); } foreach (var element in elements) { DisableDrag(element); } _contexts.Clear(); } #endregion }