Убраны синхронные методы

This commit is contained in:
2026-01-25 01:52:03 +03:00
parent 79bdd8bc62
commit a6ee6fcb36
22 changed files with 1108 additions and 2137 deletions

View File

@@ -0,0 +1,49 @@
using Microsoft.UI.Xaml;
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
/// <summary>
/// Attached properties для DragSource.
/// </summary>
public static class DragSource
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(DragSource),
new PropertyMetadata(false, OnIsEnabledChanged));
public static readonly DependencyProperty DragDataProperty =
DependencyProperty.RegisterAttached(
"DragData",
typeof(object),
typeof(DragSource),
new PropertyMetadata(null));
public static bool GetIsEnabled(UIElement element) =>
(bool)element.GetValue(IsEnabledProperty);
public static void SetIsEnabled(UIElement element, bool value) =>
element.SetValue(IsEnabledProperty, value);
public static object GetDragData(UIElement element) =>
element.GetValue(DragDataProperty);
public static void SetDragData(UIElement element, object value) =>
element.SetValue(DragDataProperty, value);
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not UIElement element) return;
// TODO: Здесь нужно создать экземпляр WinUIDragSourceBehavior
// и прикрепить его к элементу через DI
// Пока что устанавливаем данные в Tag
if ((bool)e.NewValue)
{
var data = GetDragData(element);
element.Tag = data;
}
}
}

View File

@@ -1,58 +1,31 @@
using Lattice.Core.Geometry;
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.Behaviors;
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.
/// Исправленная версия с правильной очисткой ресурсов.
/// Поведение источника перетаскивания для WinUI FrameworkElement.
/// </summary>
public static class WinUIDragSourceBehavior
public class WinUIDragSourceBehavior : DragSourceBehaviorBase<FrameworkElement>
{
#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));
new PropertyMetadata(null));
/// <summary>
/// Идентификатор свойства для включения перетаскивания.
/// Прикрепленное свойство для включения перетаскивания.
/// </summary>
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached(
@@ -62,368 +35,256 @@ public static class WinUIDragSourceBehavior
new PropertyMetadata(false, OnIsEnabledChanged));
/// <summary>
/// Идентификатор свойства для разрешенных эффектов перетаскивания.
/// Получает значение DragData.
/// </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)
public static object GetDragData(FrameworkElement element)
{
if (d is UIElement element)
{
if (_contexts.TryGetValue(element, out var context))
{
context.DragData = e.NewValue;
}
}
return element.GetValue(DragDataProperty);
}
/// <summary>
/// Устанавливает значение DragData.
/// </summary>
public static void SetDragData(FrameworkElement element, object value)
{
element.SetValue(DragDataProperty, value);
}
/// <summary>
/// Получает значение IsEnabled.
/// </summary>
public static bool GetIsEnabled(FrameworkElement element)
{
return (bool)element.GetValue(IsEnabledProperty);
}
/// <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 UIElement element)
if (d is FrameworkElement element)
{
// Получаем или создаем экземпляр поведения через Attached Property
var behavior = GetBehavior(element);
if ((bool)e.NewValue)
{
EnableDrag(element);
if (behavior == null)
{
behavior = new WinUIDragSourceBehavior();
SetBehavior(element, behavior);
}
behavior.AssociatedElement = element;
}
else
{
DisableDrag(element);
if (behavior != null)
{
behavior.Detach();
SetBehavior(element, null);
}
}
}
}
#endregion
#region Включение/выключение перетаскивания
private static void EnableDrag(UIElement element)
public WinUIDragSourceBehavior()
: base(ServiceProviderHelper.GetServiceProvider())
{
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);
}
protected override void SubscribeToEvents(FrameworkElement element)
{
element.PointerPressed += OnPointerPressed;
element.PointerMoved += OnPointerMoved;
element.PointerReleased += OnPointerReleased;
element.PointerCanceled += OnPointerCanceled;
element.PointerCaptureLost += OnPointerCaptureLost;
element.Unloaded += OnElementUnloaded;
element.LostFocus += OnLostFocus;
}
private static void DisableDrag(UIElement element)
protected override void UnsubscribeFromEvents(FrameworkElement element)
{
element.PointerPressed -= OnPointerPressed;
element.PointerMoved -= OnPointerMoved;
element.PointerReleased -= OnPointerReleased;
element.PointerCanceled -= OnPointerCanceled;
element.PointerCaptureLost -= OnPointerCaptureLost;
element.Unloaded -= OnElementUnloaded;
element.LostFocus -= OnLostFocus;
}
if (_contexts.TryGetValue(element, out var context))
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
if (AssociatedElement == null) return;
var point = e.GetCurrentPoint(AssociatedElement);
OnInteractionStarted(new Point(point.Position.X, point.Position.Y));
}
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
if (AssociatedElement == null) return;
var point = e.GetCurrentPoint(AssociatedElement);
OnInteractionMoved(new Point(point.Position.X, point.Position.Y));
}
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
OnInteractionEnded();
}
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
OnInteractionCancelled();
}
private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
OnInteractionCancelled();
}
private void OnLostFocus(object sender, RoutedEventArgs e)
{
OnInteractionCancelled();
}
protected override Point ConvertToScreenCoordinates(Point point)
{
if (AssociatedElement == null)
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)
{
context.Dispose();
_contexts.Remove(element);
return (false, null);
}
}
#endregion
#region Обработчики событий
private static void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
if (sender is UIElement element && _contexts.TryGetValue(element, out var context))
var data = GetDragData(AssociatedElement);
if (data == null)
{
var point = e.GetCurrentPoint(element);
context.DragStartPosition = new Point(point.Position.X, point.Position.Y);
context.CurrentDragElement = element;
element.CapturePointer(e.Pointer);
// Пробуем получить данные из Tag или других источников
data = AssociatedElement.Tag ?? AssociatedElement.DataContext;
}
}
private static async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
if (sender is UIElement element && _contexts.TryGetValue(element, out var context))
if (data == null)
{
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);
}
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);
}
private static async Task StartDragAsync(UIElement element, Point currentPosition, DragSourceContext context)
protected override void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects)
{
try
{
object? data = context.DragData;
base.OnDragCompleted(dragInfo, effects);
// Если есть фабрика данных, используем ее
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;
}
// Визуальная обратная связь при завершении
SetVisualState(AssociatedElement, "Normal");
}
private static void OnPointerReleased(object sender, PointerRoutedEventArgs e)
protected override void OnDragCancelled(DragInfo dragInfo)
{
CompleteDrag(sender as UIElement, Core.DragDrop.Enums.DragDropEffects.Copy);
base.OnDragCancelled(dragInfo);
// Визуальная обратная связь при отмене
SetVisualState(AssociatedElement, "Normal");
}
private static void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
private void SetVisualState(FrameworkElement? element, string stateName)
{
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);
VisualStateManager.GoToState(control, stateName, true);
}
catch
{
// Если состояния не определены, меняем свойства напрямую
control.Opacity = isDragging ? 0.7 : 1.0;
// Fallback
control.Opacity = 1.0;
}
}
else
else if (element != null)
{
// Для обычных UIElement меняем свойства напрямую
element.Opacity = isDragging ? 0.7 : 1.0;
// Альтернативная визуальная обратная связь для не-Control элементов
element.Opacity = 1.0;
}
}
private static double CalculateDistance(Point p1, Point p2)
// Attached property для хранения экземпляра поведения
private static readonly DependencyProperty BehaviorProperty =
DependencyProperty.RegisterAttached(
"Behavior",
typeof(WinUIDragSourceBehavior),
typeof(WinUIDragSourceBehavior),
new PropertyMetadata(null));
private static WinUIDragSourceBehavior GetBehavior(FrameworkElement element)
{
var dx = p2.X - p1.X;
var dy = p2.Y - p1.Y;
return Math.Sqrt(dx * dx + dy * dy);
return (WinUIDragSourceBehavior)element.GetValue(BehaviorProperty);
}
private static void ResetDragState(DragSourceContext context)
private static void SetBehavior(FrameworkElement element, WinUIDragSourceBehavior? value)
{
context.IsDragging = false;
context.CurrentDragElement = null;
context.DragStartPosition = default;
element.SetValue(BehaviorProperty, value);
}
}
#endregion
/// <summary>
/// Вспомогательный класс для получения IServiceProvider.
/// </summary>
internal static class ServiceProviderHelper
{
private static IServiceProvider? _serviceProvider;
#region Методы очистки
/// <summary>
/// Очищает все ресурсы, связанные с поведением перетаскивания.
/// </summary>
public static void Cleanup()
public static IServiceProvider GetServiceProvider()
{
var elements = new List<UIElement>();
foreach (var kvp in _contexts)
if (_serviceProvider == null)
{
elements.Add(kvp.Key);
// Ищем 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 зарегистрирован в ресурсах приложения.");
}
}
foreach (var element in elements)
{
DisableDrag(element);
}
_contexts.Clear();
return _serviceProvider;
}
#endregion
public static void SetServiceProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
}

View File

@@ -1,402 +1,345 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.Behaviors;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
/// <summary>
/// Поведение цели сброса для WinUI UIElement.
/// </summary>
public static class WinUIDropTargetBehavior
namespace Lattice.UI.DragDrop.WinUI.Behaviors
{
/// <summary>
/// Идентификатор свойства для включения цели сброса.
/// Поведение цели сброса для элементов WinUI.
/// Позволяет элементам принимать данные при операции перетаскивания.
/// </summary>
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(WinUIDropTargetBehavior),
new PropertyMetadata(false, OnIsEnabledChanged));
/// <summary>
/// Идентификатор свойства для фильтрации принимаемых данных.
/// </summary>
public static readonly DependencyProperty AcceptsDataTypesProperty =
DependencyProperty.RegisterAttached(
"AcceptsDataTypes",
typeof(Type[]),
typeof(WinUIDropTargetBehavior),
new PropertyMetadata(null));
/// <summary>
/// Идентификатор свойства для обработчика сброса.
/// </summary>
public static readonly DependencyProperty DropHandlerProperty =
DependencyProperty.RegisterAttached(
"DropHandler",
typeof(Core.DragDrop.Abstractions.IDropTarget),
typeof(WinUIDropTargetBehavior),
new PropertyMetadata(null));
/// <summary>
/// Идентификатор свойства для отображения визуальной обратной связи.
/// </summary>
public static readonly DependencyProperty ShowVisualFeedbackProperty =
DependencyProperty.RegisterAttached(
"ShowVisualFeedback",
typeof(bool),
typeof(WinUIDropTargetBehavior),
new PropertyMetadata(true));
/// <summary>
/// Идентификатор свойства для стиля визуальной обратной связи.
/// </summary>
public static readonly DependencyProperty FeedbackStyleProperty =
DependencyProperty.RegisterAttached(
"FeedbackStyle",
typeof(Style),
typeof(WinUIDropTargetBehavior),
new PropertyMetadata(null));
/// <summary>
/// Идентификатор свойства для обработчика входа перетаскивания.
/// </summary>
public static readonly DependencyProperty DragEnterHandlerProperty =
DependencyProperty.RegisterAttached(
"DragEnterHandler",
typeof(Action<UIElement>),
typeof(WinUIDropTargetBehavior),
new PropertyMetadata(null));
/// <summary>
/// Идентификатор свойства для обработчика выхода перетаскивания.
/// </summary>
public static readonly DependencyProperty DragLeaveHandlerProperty =
DependencyProperty.RegisterAttached(
"DragLeaveHandler",
typeof(Action<UIElement>),
typeof(WinUIDropTargetBehavior),
new PropertyMetadata(null));
/// <summary>
/// Идентификатор свойства для обработчика сброса.
/// </summary>
public static readonly DependencyProperty DropHandlerActionProperty =
DependencyProperty.RegisterAttached(
"DropHandlerAction",
typeof(Action<UIElement, object>),
typeof(WinUIDropTargetBehavior),
new PropertyMetadata(null));
// Словарь для отслеживания текущих перетаскиваемых элементов
private static readonly Dictionary<UIElement, bool> _dragOverElements = new();
/// <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>
/// Получает значение свойства AcceptsDataTypes для указанного элемента.
/// </summary>
public static Type[] GetAcceptsDataTypes(UIElement element) =>
(Type[])element.GetValue(AcceptsDataTypesProperty);
/// <summary>
/// Устанавливает значение свойства AcceptsDataTypes для указанного элемента.
/// </summary>
public static void SetAcceptsDataTypes(UIElement element, Type[] value) =>
element.SetValue(AcceptsDataTypesProperty, value);
/// <summary>
/// Получает значение свойства DropHandler для указанного элемента.
/// </summary>
public static Core.DragDrop.Abstractions.IDropTarget GetDropHandler(UIElement element) =>
(Core.DragDrop.Abstractions.IDropTarget)element.GetValue(DropHandlerProperty);
/// <summary>
/// Устанавливает значение свойства DropHandler для указанного элемента.
/// </summary>
public static void SetDropHandler(UIElement element, Core.DragDrop.Abstractions.IDropTarget value) =>
element.SetValue(DropHandlerProperty, value);
/// <summary>
/// Получает значение свойства ShowVisualFeedback для указанного элемента.
/// </summary>
public static bool GetShowVisualFeedback(UIElement element) =>
(bool)element.GetValue(ShowVisualFeedbackProperty);
/// <summary>
/// Устанавливает значение свойства ShowVisualFeedback для указанного элемента.
/// </summary>
public static void SetShowVisualFeedback(UIElement element, bool value) =>
element.SetValue(ShowVisualFeedbackProperty, value);
/// <summary>
/// Получает значение свойства FeedbackStyle для указанного элемента.
/// </summary>
public static Style GetFeedbackStyle(UIElement element) =>
(Style)element.GetValue(FeedbackStyleProperty);
/// <summary>
/// Устанавливает значение свойства FeedbackStyle для указанного элемента.
/// </summary>
public static void SetFeedbackStyle(UIElement element, Style value) =>
element.SetValue(FeedbackStyleProperty, value);
/// <summary>
/// Получает обработчик входа перетаскивания.
/// </summary>
public static Action<UIElement> GetDragEnterHandler(UIElement element) =>
(Action<UIElement>)element.GetValue(DragEnterHandlerProperty);
/// <summary>
/// Устанавливает обработчик входа перетаскивания.
/// </summary>
public static void SetDragEnterHandler(UIElement element, Action<UIElement> value) =>
element.SetValue(DragEnterHandlerProperty, value);
/// <summary>
/// Получает обработчик выхода перетаскивания.
/// </summary>
public static Action<UIElement> GetDragLeaveHandler(UIElement element) =>
(Action<UIElement>)element.GetValue(DragLeaveHandlerProperty);
/// <summary>
/// Устанавливает обработчик выхода перетаскивания.
/// </summary>
public static void SetDragLeaveHandler(UIElement element, Action<UIElement> value) =>
element.SetValue(DragLeaveHandlerProperty, value);
/// <summary>
/// Получает обработчик сброса.
/// </summary>
public static Action<UIElement, object> GetDropHandlerAction(UIElement element) =>
(Action<UIElement, object>)element.GetValue(DropHandlerActionProperty);
/// <summary>
/// Устанавливает обработчик сброса.
/// </summary>
public static void SetDropHandlerAction(UIElement element, Action<UIElement, object> value) =>
element.SetValue(DropHandlerActionProperty, value);
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
/// <remarks>
/// <para>
/// Это поведение должно быть прикреплено к <see cref="FrameworkElement"/>, который должен выступать в качестве цели сброса.
/// Поведение автоматически регистрирует элемент в системе перетаскивания и обрабатывает все аспекты операции сброса.
/// </para>
/// <para>
/// Для использования необходимо:
/// 1. Создать экземпляр поведения с помощью <see cref="Attach"/> или через DI.
/// 2. Переопределить методы <see cref="CanAcceptDrop"/> и <see cref="Drop"/> для реализации логики принятия данных.
/// 3. При необходимости переопределить <see cref="DragOver"/> для настройки визуальной обратной связи.
/// </para>
/// </remarks>
public class WinUIDropTargetBehavior : DropTargetBehaviorBase<FrameworkElement>
{
if (d is UIElement element)
private static readonly ConcurrentDictionary<FrameworkElement, WinUIDropTargetBehavior> _attachedBehaviors = new();
private readonly WeakReference<FrameworkElement>? _weakElement;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="WinUIDropTargetBehavior"/>.
/// </summary>
/// <param name="serviceProvider">Провайдер сервисов.</param>
/// <remarks>
/// Конструктор создает экземпляр поведения, но не прикрепляет его к элементу.
/// Для прикрепления используйте метод <see cref="Attach(FrameworkElement, IServiceProvider)"/>.
/// </remarks>
public WinUIDropTargetBehavior(IServiceProvider serviceProvider)
: base(serviceProvider)
{
if ((bool)e.NewValue)
}
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="WinUIDropTargetBehavior"/>.
/// </summary>
/// <param name="serviceProvider">Провайдер сервисов.</param>
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
/// <remarks>
/// Конструктор создает экземпляр поведения и сразу прикрепляет его к указанному элементу.
/// </remarks>
public WinUIDropTargetBehavior(IServiceProvider serviceProvider, FrameworkElement element)
: base(serviceProvider)
{
AssociatedElement = element ?? throw new ArgumentNullException(nameof(element));
}
/// <summary>
/// Прикрепляет поведение к указанному элементу.
/// </summary>
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
/// <param name="serviceProvider">Провайдер сервисов.</param>
/// <returns>
/// Экземпляр поведения, прикрепленного к элементу. Если к элементу уже прикреплено поведение,
/// возвращает существующий экземпляр.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, когда <paramref name="element"/> или <paramref name="serviceProvider"/> равны null.
/// </exception>
/// <remarks>
/// <para>
/// Этот метод обеспечивает, что к каждому элементу прикреплен только один экземпляр поведения.
/// Если метод вызывается повторно для того же элемента, возвращается существующий экземпляр.
/// </para>
/// <para>
/// Прикрепленное поведение автоматически отслеживает изменения макета элемента и обновляет
/// его границы в системе перетаскивания.
/// </para>
/// </remarks>
public static WinUIDropTargetBehavior Attach(FrameworkElement element, IServiceProvider serviceProvider)
{
if (element == null) throw new ArgumentNullException(nameof(element));
if (serviceProvider == null) throw new ArgumentNullException(nameof(serviceProvider));
return _attachedBehaviors.GetOrAdd(element, key =>
{
EnableDrop(element);
var behavior = new WinUIDropTargetBehavior(serviceProvider, key);
// Подписка на события жизненного цикла элемента
key.Unloaded += OnElementUnloaded;
return behavior;
});
}
/// <summary>
/// Открепляет поведение от указанного элемента.
/// </summary>
/// <param name="element">Элемент, от которого открепляется поведение.</param>
/// <returns>
/// true, если поведение было успешно откреплено; false, если поведение не было прикреплено к элементу.
/// </returns>
/// <remarks>
/// Этот метод освобождает все ресурсы, связанные с поведением, и отписывается от событий элемента.
/// После вызова этого метода элемент перестает быть целью сброса.
/// </remarks>
public static bool Detach(FrameworkElement element)
{
if (element == null) return false;
if (_attachedBehaviors.TryRemove(element, out var behavior))
{
element.Unloaded -= OnElementUnloaded;
behavior.Detach();
return true;
}
else
return false;
}
/// <summary>
/// Получает поведение, прикрепленное к указанному элементу.
/// </summary>
/// <param name="element">Элемент, для которого требуется получить поведение.</param>
/// <returns>
/// Экземпляр поведения, прикрепленного к элементу, или null, если поведение не прикреплено.
/// </returns>
public static WinUIDropTargetBehavior? GetAttachedBehavior(FrameworkElement element)
{
_attachedBehaviors.TryGetValue(element, out var behavior);
return behavior;
}
/// <summary>
/// Подписывается на события элемента.
/// </summary>
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
/// <remarks>
/// <para>
/// Этот метод подписывается на следующие события:
/// </para>
/// <list type="bullet">
/// <item><see cref="FrameworkElement.LayoutUpdated"/> - для отслеживания изменений макета</item>
/// <item><see cref="FrameworkElement.SizeChanged"/> - для отслеживания изменений размера</item>
/// <item><see cref="FrameworkElement.Loaded"/> - для инициализации при загрузке элемента</item>
/// </list>
/// <para>
/// Переопределите этот метод, чтобы добавить подписку на дополнительные события.
/// </para>
/// </remarks>
protected override void SubscribeToEvents(FrameworkElement element)
{
if (element == null) return;
element.LayoutUpdated += OnLayoutUpdated;
element.SizeChanged += OnSizeChanged;
element.Loaded += OnLoaded;
// Если элемент уже загружен, сразу обновляем границы
if (element.IsLoaded)
{
DisableDrop(element);
UpdateBounds();
}
}
}
private static void EnableDrop(UIElement element)
{
element.AllowDrop = true;
element.DragEnter += OnDragEnter;
element.DragOver += OnDragOver;
element.DragLeave += OnDragLeave;
element.Drop += OnDrop;
}
private static void DisableDrop(UIElement element)
{
element.AllowDrop = false;
element.DragEnter -= OnDragEnter;
element.DragOver -= OnDragOver;
element.DragLeave -= OnDragLeave;
element.Drop -= OnDrop;
}
private static void OnDragEnter(object sender, DragEventArgs e)
{
var element = sender as UIElement;
if (element == null) return;
// Проверяем, можно ли принять данные
if (CanAcceptData(element, e.DataView))
/// <summary>
/// Отписывается от событий элемента.
/// </summary>
/// <param name="element">Элемент, от которого отписывается поведение.</param>
/// <remarks>
/// Этот метод отписывается от всех событий, на которые подписался <see cref="SubscribeToEvents"/>.
/// </remarks>
protected override void UnsubscribeFromEvents(FrameworkElement element)
{
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
if (element == null) return;
// Визуальная обратная связь
ShowVisualFeedback(element, true);
// Вызываем пользовательский обработчик
GetDragEnterHandler(element)?.Invoke(element);
// Добавляем в словарь
_dragOverElements[element] = true;
}
else
{
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None;
element.LayoutUpdated -= OnLayoutUpdated;
element.SizeChanged -= OnSizeChanged;
element.Loaded -= OnLoaded;
}
e.Handled = true;
}
private static void OnDragOver(object sender, DragEventArgs e)
{
var element = sender as UIElement;
if (element == null) return;
if (CanAcceptData(element, e.DataView))
/// <summary>
/// Получает границы элемента в экранных координатах.
/// </summary>
/// <param name="element">Элемент, границы которого нужно получить.</param>
/// <returns>
/// Прямоугольник, описывающий границы элемента в экранных координатах.
/// </returns>
/// <remarks>
/// <para>
/// Метод использует преобразование координат через <see cref="UIElement.TransformToVisual"/>
/// для получения глобальных координат элемента.
/// </para>
/// <para>
/// Если элемент не прикреплен к визуальному дереву или его границы не могут быть вычислены,
/// возвращается пустой прямоугольник.
/// </para>
/// </remarks>
protected override Rect GetScreenBounds(FrameworkElement element)
{
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
if (element == null || !element.IsLoaded)
return Rect.Empty;
// Обновляем визуальную обратную связь на основе позиции
var position = e.GetPosition(element);
UpdateVisualFeedback(element, position);
}
else
{
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None;
}
e.Handled = true;
}
private static void OnDragLeave(object sender, DragEventArgs e)
{
var element = sender as UIElement;
if (element == null) return;
// Скрываем визуальную обратную связь
ShowVisualFeedback(element, false);
// Вызываем пользовательский обработчик
GetDragLeaveHandler(element)?.Invoke(element);
// Удаляем из словаря
_dragOverElements.Remove(element);
}
private static void OnDrop(object sender, DragEventArgs e)
{
var element = sender as UIElement;
if (element == null) return;
// Скрываем визуальную обратную связь
ShowVisualFeedback(element, false);
// Получаем данные
var data = ExtractData(e.DataView);
// Получаем обработчик или используем встроенный
var handler = GetDropHandler(element);
if (handler != null)
{
// Создаем DropInfo для обработчика
var dropInfo = CreateDropInfo(element, e, data);
handler.Drop(dropInfo);
}
// Вызываем пользовательский обработчик
var actionHandler = GetDropHandlerAction(element);
if (actionHandler != null && data != null)
{
actionHandler.Invoke(element, data);
}
// Удаляем из словаря
_dragOverElements.Remove(element);
e.Handled = true;
}
private static bool CanAcceptData(UIElement element, Windows.ApplicationModel.DataTransfer.DataPackageView dataView)
{
// Проверяем фильтр типов данных
var acceptedTypes = GetAcceptsDataTypes(element);
if (acceptedTypes != null && acceptedTypes.Length > 0)
{
// В реальной реализации нужно проверять доступные форматы данных
// Здесь упрощенная проверка
return dataView.AvailableFormats.Count > 0;
}
// Если нет фильтра, принимаем все
return true;
}
private static object? ExtractData(Windows.ApplicationModel.DataTransfer.DataPackageView dataView)
{
// Упрощенная реализация извлечения данных
// В реальном приложении нужно обрабатывать разные форматы данных
try
{
if (dataView.Contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.Text))
{
// В WinUI 3 нужно использовать async/await, но это упрощенный пример
// В реальном коде нужно использовать async методы
return "Text data from drag";
}
}
catch
{
// Игнорируем ошибки извлечения данных
}
return null;
}
private static void ShowVisualFeedback(UIElement element, bool show)
{
if (!GetShowVisualFeedback(element)) return;
// Для элементов, которые являются Control, используем VisualStateManager
if (element is Control control)
{
// Пытаемся перейти к состоянию "DragOver" или "Normal"
try
{
VisualStateManager.GoToState(control, show ? "DragOver" : "Normal", true);
// Получаем корневой элемент окна
var rootVisual = element.XamlRoot?.Content as UIElement;
if (rootVisual == null)
return Rect.Empty;
// Преобразуем границы элемента в координаты корневого элемента
var transform = element.TransformToVisual(rootVisual);
var position = transform.TransformPoint(new Windows.Foundation.Point(0, 0));
return new Rect(
position.X,
position.Y,
element.ActualWidth,
element.ActualHeight);
}
catch
{
// Если состояния не определены, меняем свойства напрямую
control.Background = show ?
new Microsoft.UI.Xaml.Media.SolidColorBrush(Windows.UI.Color.FromArgb(30, 0, 120, 215)) :
null;
// В случае ошибки возвращаем пустой прямоугольник
return Rect.Empty;
}
}
else
/// <summary>
/// Определяет, может ли элемент принять сбрасываемые данные.
/// </summary>
/// <param name="dropInfo">Информация о сбросе.</param>
/// <returns>
/// true, если элемент может принять данные; в противном случае — false.
/// </returns>
/// <remarks>
/// <para>
/// Этот метод является абстрактным и должен быть переопределен в производных классах
/// для реализации логики принятия данных.
/// </para>
/// <para>
/// Базовая реализация всегда возвращает false. Переопределите этот метод, чтобы определить,
/// какие типы данных может принимать ваш элемент и при каких условиях.
/// </para>
/// <example>
/// Пример реализации:
/// <code>
/// public override bool CanAcceptDrop(DropInfo dropInfo)
/// {
/// // Принимаем только строковые данные
/// return dropInfo.Data is string;
/// }
/// </code>
/// </example>
/// </remarks>
public override async Task<bool> CanAcceptDropAsync(DropInfo dropInfo)
{
// Для обычных UIElement меняем свойства напрямую
element.Opacity = show ? 0.8 : 1.0;
// Базовая реализация - не принимает никакие данные.
// Переопределите этот метод в производных классах.
return false;
}
}
private static void UpdateVisualFeedback(UIElement element, Windows.Foundation.Point position)
{
// Можно добавить логику для различных зон сброса
// Например, подсветка разных частей элемента
}
/// <summary>
/// Обрабатывает сброс данных на элемент.
/// </summary>
/// <param name="dropInfo">Информация о сбросе.</param>
/// <remarks>
/// <para>
/// Этот метод вызывается, когда пользователь отпускает кнопку мыши над элементом,
/// и данные должны быть приняты.
/// </para>
/// <para>
/// Базовая реализация ничего не делает. Переопределите этот метод, чтобы реализовать
/// логику обработки принятых данных.
/// </para>
/// <example>
/// Пример реализации:
/// <code>
/// public override void Drop(DropInfo dropInfo)
/// {
/// if (dropInfo.Data is string text)
/// {
/// // Обработка текстовых данных
/// AssociatedElement.SetValue(TextBlock.TextProperty, text);
/// dropInfo.MarkAsHandled();
/// }
/// }
/// </code>
/// </example>
/// </remarks>
public override async Task DropAsync(DropInfo dropInfo)
{
// Базовая реализация ничего не делает.
// Переопределите этот метод в производных классах.
}
private static Core.DragDrop.Models.DropInfo CreateDropInfo(UIElement element, DragEventArgs e, object? data)
{
var position = e.GetPosition(element);
var screenPosition = element.TransformToVisual(null).TransformPoint(position);
/// <summary>
/// Освобождает ресурсы, связанные с поведением.
/// </summary>
/// <remarks>
/// <para>
/// Этот метод отписывается от всех событий, отменяет регистрацию в сервисе перетаскивания
/// и очищает все ресурсы.
/// </para>
/// <para>
/// После вызова этого метода поведение больше не может быть использовано.
/// </para>
/// </remarks>
public override void Detach()
{
if (AssociatedElement != null && _attachedBehaviors.TryGetValue(AssociatedElement, out _))
{
_attachedBehaviors.TryRemove(AssociatedElement, out _);
}
return new Core.DragDrop.Models.DropInfo(
data: data,
position: new Core.Geometry.Point(screenPosition.X, screenPosition.Y),
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move,
target: GetDropHandler(element)
);
base.Detach();
}
#region Event Handlers
private void OnLayoutUpdated(object? sender, object e)
{
OnElementLayoutChanged();
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
OnElementLayoutChanged();
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
UpdateBounds();
}
private static void OnElementUnloaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement element)
{
Detach(element);
}
}
#endregion
}
}

View File

@@ -16,7 +16,7 @@ public static class DragDropExtensions
/// <summary>
/// Делает элемент источником перетаскивания с указанными данными.
/// </summary>
public static void MakeDragSource(this UIElement element, object dragData)
public static void MakeDragSource(this FrameworkElement element, object dragData)
{
Behaviors.WinUIDragSourceBehavior.SetDragData(element, dragData);
Behaviors.WinUIDragSourceBehavior.SetIsEnabled(element, true);
@@ -25,53 +25,33 @@ public static class DragDropExtensions
/// <summary>
/// Делает элемент источником перетаскивания с фабрикой данных.
/// </summary>
public static void MakeDragSource(this UIElement element, Func<object> dataFactory)
public static void MakeDragSource(this FrameworkElement element, Func<object> dataFactory)
{
element.MakeDragSource(dataFactory());
}
/// <summary>
/// Делает элемент источником перетаскивания с настраиваемыми параметрами.
/// </summary>
public static void MakeDragSource(this UIElement element,
object dragData,
Core.DragDrop.Enums.DragDropEffects allowedEffects,
Func<bool>? canDrag = null)
{
element.MakeDragSource(dragData);
element.SetAllowedEffects(allowedEffects);
if (canDrag != null)
{
// Можно добавить кастомную логику проверки
}
}
/// <summary>
/// Удаляет возможность перетаскивания с элемента.
/// </summary>
public static void RemoveDragSource(this UIElement element)
public static void RemoveDragSource(this FrameworkElement element)
{
Behaviors.WinUIDragSourceBehavior.SetIsEnabled(element, false);
}
/// <summary>
/// Устанавливает разрешенные эффекты перетаскивания для элемента.
/// Проверяет, является ли элемент источником перетаскивания.
/// </summary>
public static void SetAllowedEffects(this UIElement element, Core.DragDrop.Enums.DragDropEffects effects)
public static bool IsDragSource(this FrameworkElement element)
{
Behaviors.WinUIDragSourceBehavior.SetAllowedEffects(element, effects);
return Behaviors.WinUIDragSourceBehavior.GetIsEnabled(element);
}
/// <summary>
/// Устанавливает смещение для визуального элемента перетаскивания.
/// Получает данные перетаскивания из элемента.
/// </summary>
public static void SetDragVisualOffset(this UIElement element, double offsetX, double offsetY)
public static object? GetDragData(this FrameworkElement element)
{
if (element is FrameworkElement frameworkElement)
{
frameworkElement.Tag = new Windows.Foundation.Point(offsetX, offsetY);
}
return Behaviors.WinUIDragSourceBehavior.GetDragData(element);
}
#endregion
@@ -81,80 +61,65 @@ public static class DragDropExtensions
/// <summary>
/// Делает элемент целью сброса.
/// </summary>
public static void MakeDropTarget(this UIElement element)
public static void MakeDropTarget(this FrameworkElement element)
{
Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, true);
// Включаем AllowDrop для WinUI
element.AllowDrop = true;
element.SetValue(IsDropTargetProperty, true);
}
/// <summary>
/// Делает элемент целью сброса с фильтром типов данных.
/// </summary>
public static void MakeDropTarget(this UIElement element, params Type[] acceptedTypes)
public static void MakeDropTarget(this FrameworkElement element, params Type[] acceptedTypes)
{
Behaviors.WinUIDropTargetBehavior.SetAcceptsDataTypes(element, acceptedTypes);
Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, true);
}
/// <summary>
/// Делает элемент целью сброса с обработчиком.
/// </summary>
public static void MakeDropTarget(this UIElement element, Core.DragDrop.Abstractions.IDropTarget handler)
{
Behaviors.WinUIDropTargetBehavior.SetDropHandler(element, handler);
Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, true);
}
/// <summary>
/// Делает элемент целью сброса с полной настройкой.
/// </summary>
public static void MakeDropTarget(this UIElement element,
Type[] acceptedTypes,
Core.DragDrop.Abstractions.IDropTarget? handler = null,
bool showVisualFeedback = true,
Style? feedbackStyle = null)
{
if (acceptedTypes != null && acceptedTypes.Length > 0)
{
Behaviors.WinUIDropTargetBehavior.SetAcceptsDataTypes(element, acceptedTypes);
}
if (handler != null)
{
Behaviors.WinUIDropTargetBehavior.SetDropHandler(element, handler);
}
Behaviors.WinUIDropTargetBehavior.SetShowVisualFeedback(element, showVisualFeedback);
if (feedbackStyle != null)
{
Behaviors.WinUIDropTargetBehavior.SetFeedbackStyle(element, feedbackStyle);
}
Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, true);
element.SetValue(AcceptsDataTypesProperty, acceptedTypes);
element.MakeDropTarget();
}
/// <summary>
/// Удаляет возможность сброса с элемента.
/// </summary>
public static void RemoveDropTarget(this UIElement element)
public static void RemoveDropTarget(this FrameworkElement element)
{
Behaviors.WinUIDropTargetBehavior.SetIsEnabled(element, false);
element.AllowDrop = false;
element.SetValue(IsDropTargetProperty, false);
}
/// <summary>
/// Устанавливает стиль визуальной обратной связи для цели сброса.
/// Проверяет, является ли элемент целью сброса.
/// </summary>
public static void SetDropFeedbackStyle(this UIElement element, Style style)
public static bool IsDropTarget(this FrameworkElement element)
{
Behaviors.WinUIDropTargetBehavior.SetFeedbackStyle(element, style);
return (bool)element.GetValue(IsDropTargetProperty);
}
/// <summary>
/// Включает или выключает визуальную обратную связь при сбросе.
/// Attached property для отметки цели сброса.
/// </summary>
public static void SetDropVisualFeedback(this UIElement element, bool enabled)
public static readonly DependencyProperty IsDropTargetProperty =
DependencyProperty.RegisterAttached(
"IsDropTarget",
typeof(bool),
typeof(DragDropExtensions),
new PropertyMetadata(false));
/// <summary>
/// Attached property для фильтра типов данных.
/// </summary>
public static readonly DependencyProperty AcceptsDataTypesProperty =
DependencyProperty.RegisterAttached(
"AcceptsDataTypes",
typeof(Type[]),
typeof(DragDropExtensions),
new PropertyMetadata(null));
/// <summary>
/// Получает фильтр типов данных.
/// </summary>
public static Type[]? GetAcceptsDataTypes(this FrameworkElement element)
{
Behaviors.WinUIDropTargetBehavior.SetShowVisualFeedback(element, enabled);
return (Type[]?)element.GetValue(AcceptsDataTypesProperty);
}
#endregion
@@ -171,32 +136,17 @@ public static class DragDropExtensions
{
control.Style = style;
}
}
/// <summary>
/// Применяет стиль цели сброса к элементу.
/// </summary>
public static void ApplyDropTargetStyle(this Control control)
{
var style = Application.Current.Resources["DropTargetStyle"] as Style;
if (style != null)
else
{
control.Style = style;
// Fallback стиль
var brush = Application.Current.Resources["SystemControlBackgroundAccentBrush"] as SolidColorBrush;
if (brush != null)
{
control.Background = brush;
}
}
}
/// <summary>
/// Включает визуальную обратную связь для элемента при перетаскивании.
/// </summary>
public static void EnableDragVisualFeedback(this Control control)
{
control.Loaded += (sender, e) =>
{
// Убеждаемся, что у элемента есть визуальные состояния
EnsureVisualStates(control);
};
}
/// <summary>
/// Переключает визуальное состояние элемента для перетаскивания.
/// </summary>
@@ -208,22 +158,18 @@ public static class DragDropExtensions
}
catch
{
// Если состояние не найдено, используем альтернативные методы
// Fallback для элементов без визуальных состояний
switch (stateName)
{
case "Dragging":
control.Opacity = 0.7;
control.RenderTransform = new ScaleTransform { ScaleX = 0.95, ScaleY = 0.95 };
break;
case "DragOver":
control.Background = Application.Current.Resources["DragOverBackgroundBrush"] as Brush;
control.BorderBrush = Application.Current.Resources["DragOverBorderBrush"] as Brush;
control.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(50, 0, 120, 215));
break;
case "Normal":
control.ClearValue(Control.OpacityProperty);
control.ClearValue(Control.RenderTransformProperty);
control.ClearValue(Control.BackgroundProperty);
control.ClearValue(Control.BorderBrushProperty);
break;
}
}
@@ -233,17 +179,6 @@ public static class DragDropExtensions
#region Advanced Configuration
/// <summary>
/// Настраивает элемент для работы с сервисом перетаскивания.
/// </summary>
public static void ConfigureForDragDropService(this UIElement element,
Core.DragDrop.Services.IDragDropService service,
string? dropTargetGroup = null)
{
// Этот метод позволяет интегрировать элемент с централизованным сервисом
// В реальной реализации нужно регистрировать элемент в сервисе
}
/// <summary>
/// Создает контейнер с поддержкой перетаскивания для элементов.
/// </summary>
@@ -260,8 +195,7 @@ public static class DragDropExtensions
if (enableReordering)
{
// Настраиваем контейнер для переупорядочивания элементов
container.MakeDropTarget(typeof(UIElement));
container.MakeDropTarget(typeof(FrameworkElement));
}
return container;
@@ -270,9 +204,9 @@ public static class DragDropExtensions
/// <summary>
/// Делает все дочерние элементы перетаскиваемыми.
/// </summary>
public static void MakeChildrenDraggable(this Panel container, Func<UIElement, object> dataSelector)
public static void MakeChildrenDraggable(this Panel container, Func<FrameworkElement, object> dataSelector)
{
foreach (var child in container.Children.OfType<UIElement>())
foreach (var child in container.Children.OfType<FrameworkElement>())
{
var data = dataSelector(child);
child.MakeDragSource(data);
@@ -280,48 +214,4 @@ public static class DragDropExtensions
}
#endregion
#region Utility Methods
/// <summary>
/// Проверяет, является ли элемент источником перетаскивания.
/// </summary>
public static bool IsDragSource(this UIElement element)
{
return Behaviors.WinUIDragSourceBehavior.GetIsEnabled(element);
}
/// <summary>
/// Проверяет, является ли элемент целью сброса.
/// </summary>
public static bool IsDropTarget(this UIElement element)
{
return Behaviors.WinUIDropTargetBehavior.GetIsEnabled(element);
}
/// <summary>
/// Получает данные перетаскивания из элемента.
/// </summary>
public static object? GetDragData(this UIElement element)
{
return Behaviors.WinUIDragSourceBehavior.GetDragData(element);
}
#endregion
#region Helper Methods
private static void EnsureVisualStates(Control control)
{
// В WinUI 3 визуальные состояния должны быть определены в стиле элемента
// Этот метод проверяет, что у элемента есть базовые визуальные состояния
if (control.Template == null && control.Style == null)
{
// Применяем стандартный стиль, если у элемента нет своего
control.ApplyDragStyle();
}
}
#endregion
}

View File

@@ -0,0 +1,26 @@
using Microsoft.UI.Xaml;
namespace Lattice.UI.DragDrop.WinUI.Helpers;
public static class FrameworkElementExtensions
{
/// <summary>
/// Получает фактические размеры FrameworkElement.
/// </summary>
public static Windows.Foundation.Size GetActualSize(this FrameworkElement element)
{
return new Windows.Foundation.Size(element.ActualWidth, element.ActualHeight);
}
/// <summary>
/// Получает границы элемента в экранных координатах.
/// </summary>
public static Windows.Foundation.Rect GetScreenBounds(this FrameworkElement element)
{
var transform = element.TransformToVisual(null);
var topLeft = transform.TransformPoint(new Windows.Foundation.Point(0, 0));
var bottomRight = transform.TransformPoint(new Windows.Foundation.Point(element.ActualWidth, element.ActualHeight));
return new Windows.Foundation.Rect(topLeft, bottomRight);
}
}

View File

@@ -3,6 +3,7 @@ using Lattice.Core.DragDrop.Models;
using Lattice.Core.DragDrop.Services;
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.Abstractions;
using Lattice.UI.DragDrop.WinUI.Behaviors;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using System;
@@ -14,25 +15,23 @@ namespace Lattice.UI.DragDrop.WinUI.Integration;
/// <summary>
/// Сервис интеграции Drag & Drop с WinUI приложением.
/// Исправленная версия с правильной обработкой событий и очисткой ресурсов.
/// </summary>
public sealed class WinUIDragDropIntegrationService : IDisposable
{
#region Вложенные типы
private sealed class DragSourceAdapter : IDisposable
private sealed class DragSourceAdapter : IDragSource
{
private readonly UIElement _element;
private readonly Func<Task<DragInfo>> _dragInfoFactory;
private readonly FrameworkElement _element;
private readonly Func<DragInfo> _dragInfoFactory;
private readonly IDragDropService _dragDropService;
private Point _dragStartPosition;
private bool _isDragging;
private bool _disposed;
private object? _dragData;
public DragSourceAdapter(
UIElement element,
Func<Task<DragInfo>> dragInfoFactory,
FrameworkElement element,
Func<DragInfo> dragInfoFactory,
IDragDropService dragDropService)
{
_element = element ?? throw new ArgumentNullException(nameof(element));
@@ -42,24 +41,6 @@ public sealed class WinUIDragDropIntegrationService : IDisposable
SubscribeToEvents();
}
public DragSourceAdapter(
UIElement element,
object dragData,
IDragDropService dragDropService)
{
_element = element ?? throw new ArgumentNullException(nameof(element));
_dragData = dragData ?? throw new ArgumentNullException(nameof(dragData));
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
_dragInfoFactory = async () => new DragInfo(
_dragData,
Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move,
Point.Zero,
_element);
SubscribeToEvents();
}
private void SubscribeToEvents()
{
_element.PointerPressed += OnPointerPressed;
@@ -86,7 +67,7 @@ public sealed class WinUIDragDropIntegrationService : IDisposable
_dragStartPosition = new Point(point.Position.X, point.Position.Y);
}
private async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
if (_isDragging || _disposed) return;
@@ -96,16 +77,24 @@ public sealed class WinUIDragDropIntegrationService : IDisposable
var distance = CalculateDistance(_dragStartPosition, currentPosition);
if (distance > _dragDropService.DragStartThreshold)
{
await StartDragAsync(currentPosition);
StartDragOperation(currentPosition);
}
}
private async Task StartDragAsync(Point position)
private async Task StartDragOperation(Point position)
{
try
{
var dragInfo = await _dragInfoFactory();
var dragInfo = _dragInfoFactory();
// Обновляем позицию в dragInfo
var screenPosition = GetScreenPosition(position);
dragInfo = new DragInfo(
dragInfo.Data,
dragInfo.AllowedEffects,
screenPosition,
dragInfo.Source
);
_isDragging = await _dragDropService.StartDragAsync(this, screenPosition);
}
@@ -134,131 +123,71 @@ public sealed class WinUIDragDropIntegrationService : IDisposable
{
if (!_isDragging || _disposed) return;
var point = e.GetCurrentPoint(_element);
var position = GetScreenPosition(new Point(point.Position.X, point.Position.Y));
await _dragDropService.EndDragAsync(position);
await _dragDropService.EndDragAsync();
_isDragging = false;
}
private async void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
if (!_isDragging || _disposed) return;
await _dragDropService.CancelDragAsync();
_dragDropService.CancelDragAsync();
_isDragging = false;
}
private async void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
if (!_isDragging || _disposed) return;
await _dragDropService.CancelDragAsync();
_dragDropService.CancelDragAsync();
_isDragging = false;
}
#region IDragSource Implementation
public async Task<(bool, DragInfo? dragInfo)> CancelDragAsync()
{
try
{
dragInfo = _dragInfoFactory();
return dragInfo != null;
}
catch
{
dragInfo = null;
return false;
}
}
public async Task<bool> StartDragAsync(DragInfo dragInfo)
{
return true;
}
public async Task DragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects)
{
_isDragging = false;
}
public async Task DragCancelledAsync(DragInfo dragInfo)
{
_isDragging = false;
}
#endregion
public void Dispose()
{
if (_disposed) return;
UnsubscribeFromEvents();
if (_isDragging)
{
Task.Run(async () => await _dragDropService.CancelDragAsync()).Wait(1000);
}
_disposed = true;
GC.SuppressFinalize(this);
}
}
private sealed class DropTargetAdapter : IDisposable
{
private readonly UIElement _element;
private readonly IDropTarget _dropTarget;
private readonly IDragDropService _dragDropService;
private string? _registrationId;
private bool _disposed;
public DropTargetAdapter(
UIElement element,
IDropTarget dropTarget,
IDragDropService dragDropService)
public Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
{
_element = element ?? throw new ArgumentNullException(nameof(element));
_dropTarget = dropTarget ?? throw new ArgumentNullException(nameof(dropTarget));
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
SubscribeToEvents();
UpdateRegistration();
}
private void SubscribeToEvents()
{
_element.LayoutUpdated += OnLayoutUpdated;
_element.Unloaded += OnElementUnloaded;
}
private void UnsubscribeFromEvents()
{
_element.LayoutUpdated -= OnLayoutUpdated;
_element.Unloaded -= OnElementUnloaded;
}
private void UpdateRegistration()
{
var bounds = GetElementBounds();
if (_registrationId is null)
{
_registrationId = _dragDropService.RegisterDropTarget(_dropTarget, bounds);
}
else
{
_dragDropService.UpdateDropTargetBounds(_registrationId, bounds);
}
}
private Rect GetElementBounds()
{
var transform = _element.TransformToVisual(null);
var topLeft = transform.TransformPoint(new Windows.Foundation.Point(0, 0));
var bottomRight = transform.TransformPoint(new Windows.Foundation.Point(_element.ActualWidth, _element.ActualHeight));
return new Rect(
topLeft.X,
topLeft.Y,
bottomRight.X - topLeft.X,
bottomRight.Y - topLeft.Y);
}
private void OnLayoutUpdated(object? sender, object e)
{
if (_disposed) return;
UpdateRegistration();
}
private void OnElementUnloaded(object sender, RoutedEventArgs e)
{
Dispose();
}
public void Dispose()
{
if (_disposed) return;
UnsubscribeFromEvents();
if (_registrationId is not null)
{
_dragDropService.UnregisterDropTarget(_registrationId);
_registrationId = null;
}
if (_dropTarget is IDisposable disposable)
disposable.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
throw new NotImplementedException();
}
}
@@ -269,30 +198,22 @@ public sealed class WinUIDragDropIntegrationService : IDisposable
private readonly IDragDropService _dragDropService;
private readonly IDragVisualProvider _dragVisualProvider;
private readonly IDragDropHost _dragDropHost;
private readonly Dictionary<UIElement, DragSourceAdapter> _dragSources = new();
private readonly Dictionary<UIElement, DropTargetAdapter> _dropTargets = new();
private readonly Window _window;
private readonly Dictionary<FrameworkElement, DragSourceAdapter> _dragSources = new();
private readonly Dictionary<FrameworkElement, WinUIDropTargetBehavior> _dropTargets = new();
private bool _disposed;
#endregion
#region Конструктор
/// <summary>
/// Инициализирует новый экземпляр.
/// </summary>
public WinUIDragDropIntegrationService(
Window window,
IDragDropService dragDropService,
IDragVisualProvider dragVisualProvider,
IDragDropHost dragDropHost)
{
_window = window ?? throw new ArgumentNullException(nameof(window));
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
_dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider));
_dragDropHost = dragDropHost ?? throw new ArgumentNullException(nameof(dragDropHost));
InitializeEvents();
}
#endregion
@@ -302,9 +223,9 @@ public sealed class WinUIDragDropIntegrationService : IDisposable
/// <summary>
/// Регистрирует элемент как источник перетаскивания.
/// </summary>
public void RegisterDragSource(UIElement element, Func<Task<DragInfo>> dragInfoFactory)
public void RegisterDragSource(FrameworkElement element, Func<DragInfo> dragInfoFactory)
{
ThrowIfDisposed();
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
ArgumentNullException.ThrowIfNull(element);
ArgumentNullException.ThrowIfNull(dragInfoFactory);
@@ -317,39 +238,130 @@ public sealed class WinUIDragDropIntegrationService : IDisposable
/// <summary>
/// Регистрирует элемент как источник перетаскивания с данными.
/// </summary>
public void RegisterDragSource(UIElement element, object dragData)
public void RegisterDragSource(FrameworkElement element, object dragData)
{
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(element);
ArgumentNullException.ThrowIfNull(dragData);
if (_dragSources.ContainsKey(element)) return;
var adapter = new DragSourceAdapter(element, dragData, _dragDropService);
_dragSources[element] = adapter;
RegisterDragSource(element, () =>
{
var position = GetScreenPosition(element, new Point(0, 0));
return new DragInfo(
dragData,
Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move,
position,
element
);
});
}
/// <summary>
/// Регистрирует элемент как цель сброса.
/// </summary>
public void RegisterDropTarget(UIElement element, IDropTarget dropTarget)
public void RegisterDropTarget(FrameworkElement element)
{
ThrowIfDisposed();
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
ArgumentNullException.ThrowIfNull(element);
ArgumentNullException.ThrowIfNull(dropTarget);
if (_dropTargets.ContainsKey(element)) return;
var adapter = new DropTargetAdapter(element, dropTarget, _dragDropService);
_dropTargets[element] = adapter;
var behavior = new WinUIDropTargetBehavior(ServiceProviderHelper.GetServiceProvider())
{
AssociatedElement = element
};
_dropTargets[element] = behavior;
// Настраиваем события WinUI
element.AllowDrop = true;
element.DragEnter += OnDragEnter;
element.DragOver += OnDragOver;
element.DragLeave += OnDragLeave;
element.Drop += OnDrop;
}
private Point GetScreenPosition(FrameworkElement element, Point relativePoint)
{
var transform = element.TransformToVisual(null);
var point = transform.TransformPoint(new Windows.Foundation.Point(relativePoint.X, relativePoint.Y));
return new Point(point.X, point.Y);
}
private void OnDragEnter(object sender, DragEventArgs e)
{
if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior))
{
var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint());
var dropInfo = CreateDropInfo(e, position, element);
if (behavior.CanAcceptDrop(dropInfo))
{
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
behavior.DragOver(dropInfo);
}
else
{
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None;
}
}
}
private void OnDragOver(object sender, DragEventArgs e)
{
if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior))
{
var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint());
var dropInfo = CreateDropInfo(e, position, element);
if (behavior.CanAcceptDrop(dropInfo))
{
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
behavior.DragOver(dropInfo);
}
}
}
private void OnDragLeave(object sender, DragEventArgs e)
{
if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior))
{
behavior.DragLeave();
}
}
private void OnDrop(object sender, DragEventArgs e)
{
if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior))
{
var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint());
var dropInfo = CreateDropInfo(e, position, element);
behavior.Drop(dropInfo);
}
}
private DropInfo CreateDropInfo(DragEventArgs e, Point position, FrameworkElement target)
{
// Извлекаем данные из DragEventArgs
object? data = null;
// В реальной реализации нужно извлечь данные из e.DataView
// Это упрощенная версия
if (e.DataView.Properties.TryGetValue("DragData", out var dragData))
{
data = dragData;
}
return new DropInfo(
data: data,
position: position,
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move,
target: target
);
}
/// <summary>
/// Удаляет регистрацию элемента как источника.
/// </summary>
public void UnregisterDragSource(UIElement element)
public void UnregisterDragSource(FrameworkElement element)
{
ThrowIfDisposed();
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
if (_dragSources.Remove(element, out var adapter))
{
@@ -360,122 +372,38 @@ public sealed class WinUIDragDropIntegrationService : IDisposable
/// <summary>
/// Удаляет регистрацию элемента как цели.
/// </summary>
public void UnregisterDropTarget(UIElement element)
public void UnregisterDropTarget(FrameworkElement element)
{
ThrowIfDisposed();
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
if (_dropTargets.Remove(element, out var adapter))
if (_dropTargets.Remove(element, out var behavior))
{
adapter.Dispose();
behavior.Detach();
element.AllowDrop = false;
element.DragEnter -= OnDragEnter;
element.DragOver -= OnDragOver;
element.DragLeave -= OnDragLeave;
element.Drop -= OnDrop;
}
}
#endregion
#region Обработка событий
private void InitializeEvents()
{
_dragDropService.DragStarted += OnDragStarted;
_dragDropService.DragUpdated += OnDragUpdated;
_dragDropService.DragCompleted += OnDragCompleted;
_dragDropService.DragCancelled += OnDragCancelled;
_dragDropService.ErrorOccurred += OnErrorOccurred;
}
private void UnsubscribeFromEvents()
{
_dragDropService.DragStarted -= OnDragStarted;
_dragDropService.DragUpdated -= OnDragUpdated;
_dragDropService.DragCompleted -= OnDragCompleted;
_dragDropService.DragCancelled -= OnDragCancelled;
_dragDropService.ErrorOccurred -= OnErrorOccurred;
}
private void OnDragStarted(object? sender, DragStartedEventArgs e)
{
try
{
var visual = _dragVisualProvider.CreateDragVisual(e.DragInfo, e.StartPosition);
_dragDropHost.ShowDragVisual(visual, e.StartPosition);
}
catch (Exception ex)
{
Debug.WriteLine($"Error in OnDragStarted: {ex.Message}");
}
}
private void OnDragUpdated(object? sender, DragUpdatedEventArgs e)
{
try
{
// Обновление визуала будет через хост
}
catch (Exception ex)
{
Debug.WriteLine($"Error in OnDragUpdated: {ex.Message}");
}
}
private void OnDragCompleted(object? sender, DragCompletedEventArgs e)
{
try
{
_dragDropHost.HideDragVisual(null!); // В реальности нужно передать конкретный визуал
}
catch (Exception ex)
{
Debug.WriteLine($"Error in OnDragCompleted: {ex.Message}");
}
}
private void OnDragCancelled(object? sender, DragCancelledEventArgs e)
{
try
{
_dragDropHost.HideDragVisual(null!);
}
catch (Exception ex)
{
Debug.WriteLine($"Error in OnDragCancelled: {ex.Message}");
}
}
private void OnErrorOccurred(object? sender, DragDropErrorEventArgs e)
{
Debug.WriteLine($"DragDrop error in {e.Operation}: {e.Exception.Message}");
}
#endregion
#region Вспомогательные методы
private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
}
#endregion
#region IDisposable
/// <inheritdoc/>
public void Dispose()
{
if (_disposed) return;
UnsubscribeFromEvents();
foreach (var adapter in _dragSources.Values)
{
adapter.Dispose();
}
_dragSources.Clear();
foreach (var adapter in _dropTargets.Values)
foreach (var behavior in _dropTargets.Values)
{
adapter.Dispose();
behavior.Detach();
}
_dropTargets.Clear();
@@ -484,4 +412,15 @@ public sealed class WinUIDragDropIntegrationService : IDisposable
}
#endregion
}
/// <summary>
/// Методы расширения для преобразования координат.
/// </summary>
internal static class PointExtensions
{
public static Point ToCorePoint(this Windows.Foundation.Point point)
{
return new Point(point.X, point.Y);
}
}

View File

@@ -0,0 +1,72 @@
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.Abstractions;
using Lattice.UI.DragDrop.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
namespace Lattice.UI.DragDrop.WinUI.Services;
public class WinUIDragDropHost : IDragDropHost
{
private readonly DragDropOverlay _overlay;
private readonly Window _window;
public WinUIDragDropHost(Window window)
{
_window = window ?? throw new ArgumentNullException(nameof(window));
_overlay = new DragDropOverlay();
// Добавляем оверлей в окно
if (_window.Content is Panel panel)
{
panel.Children.Add(_overlay);
}
}
public void ShowDragVisual(object dragVisual, Point position)
{
if (dragVisual is UIElement element)
{
_overlay.ShowDragVisual(element, position.X, position.Y);
}
}
public void UpdateDragVisualPosition(object dragVisual, Point position)
{
if (dragVisual is UIElement element)
{
_overlay.UpdateDragVisualPosition(element, position.X, position.Y);
}
}
public void HideDragVisual(object dragVisual)
{
if (dragVisual is UIElement element)
{
_overlay.HideDragVisual(element);
}
else
{
// Скрываем все, если передан null
var current = _overlay.GetCurrentDragVisual();
if (current != null)
{
_overlay.HideDragVisual(current);
}
}
}
public void ShowDropAdorner(IDropVisualAdorner adorner)
{
if (adorner is DropPreviewAdorner dropAdorner)
{
// TODO: Показываем превью сброса
}
}
public void HideDropAdorner(IDropVisualAdorner adorner)
{
_overlay.HideAllDropPreviews();
}
}

View File

@@ -2,45 +2,22 @@
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.Abstractions;
using Lattice.UI.DragDrop.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
namespace Lattice.UI.DragDrop.WinUI.Services;
/// <summary>
/// Поставщик визуального представления для WinUI.
/// </summary>
public class WinUIDragVisualProvider : IDragVisualProvider
{
private readonly ResourceDictionary _resources;
private DragAdorner? _currentAdorner;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="WinUIDragVisualProvider"/>.
/// </summary>
/// <param name="resources">Ресурсы для стилей.</param>
public WinUIDragVisualProvider(ResourceDictionary resources)
{
_resources = resources ?? throw new ArgumentNullException(nameof(resources));
}
/// <inheritdoc/>
public object CreateDragVisual(DragInfo dragInfo, Point initialPosition)
{
// Создаем новый DragAdorner
// Создаем DragAdorner на основе данных
_currentAdorner = new DragAdorner
{
DragData = dragInfo.Data,
OpacityLevel = 0.8
};
// Применяем стиль из ресурсов, если есть
if (_resources.ContainsKey("DragAdornerStyle"))
{
_currentAdorner.Style = _resources["DragAdornerStyle"] as Style;
}
// Настраиваем начальную позицию
_currentAdorner.UpdatePosition(initialPosition);
_currentAdorner.Show();
@@ -48,7 +25,6 @@ public class WinUIDragVisualProvider : IDragVisualProvider
return _currentAdorner;
}
/// <inheritdoc/>
public void UpdateDragVisualPosition(object dragVisual, Point position)
{
if (dragVisual is DragAdorner adorner)
@@ -57,31 +33,12 @@ public class WinUIDragVisualProvider : IDragVisualProvider
}
}
/// <inheritdoc/>
public void ReleaseDragVisual(object dragVisual)
{
if (dragVisual is DragAdorner adorner)
{
adorner.Hide();
// Отложенное удаление после анимации
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(150)
};
timer.Tick += (s, e) =>
{
timer.Stop();
if (adorner.Parent is Panel panel)
{
panel.Children.Remove(adorner);
}
};
timer.Start();
_currentAdorner = null;
}
_currentAdorner = null;
}
}