diff --git a/Lattice.Core.DragDrop/Exceptions/DragDropException.cs b/Lattice.Core.DragDrop/Exceptions/DragDropException.cs
index 59874c0..66c8e91 100644
--- a/Lattice.Core.DragDrop/Exceptions/DragDropException.cs
+++ b/Lattice.Core.DragDrop/Exceptions/DragDropException.cs
@@ -59,7 +59,7 @@ public class DragDropException : Exception
}
///
-/// Коды ошибок Drag & Drop системы.
+/// Коды ошибок Drag and Drop системы.
///
public static class DragDropErrorCodes
{
diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs
deleted file mode 100644
index 55454d0..0000000
--- a/Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using Microsoft.UI.Xaml;
-
-namespace Lattice.UI.DragDrop.WinUI.Behaviors;
-
-///
-/// Attached properties для DragSource.
-///
-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;
- }
- }
-}
\ No newline at end of file
diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs
index aa738c1..d1f97e6 100644
--- a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs
+++ b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs
@@ -1,290 +1,267 @@
-using Lattice.Core.DragDrop.Models;
+using Lattice.Core.DragDrop.Abstractions;
+using Lattice.Core.DragDrop.Models;
+using Lattice.Core.DragDrop.Services;
using Lattice.Core.Geometry;
-using Lattice.UI.DragDrop.Behaviors;
+using Lattice.UI.DragDrop.WinUI.Services;
using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using System;
+using System.Threading;
using System.Threading.Tasks;
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
///
-/// Поведение источника перетаскивания для WinUI FrameworkElement.
+/// Реализация поведения источника перетаскивания для элементов WinUI.
///
-public class WinUIDragSourceBehavior : DragSourceBehaviorBase
+///
+///
+/// Этот класс обрабатывает события мыши/тач и преобразует их в операции перетаскивания.
+/// Он реализует интерфейс для интеграции с системой перетаскивания.
+///
+///
+/// Поведение автоматически отслеживает начало перемещения мыши, проверяет порог
+/// перетаскивания и инициирует операцию через .
+///
+///
+public sealed class WinUIDragSourceBehavior : IDragSource
{
- ///
- /// Прикрепленное свойство для данных перетаскивания.
- ///
- public static readonly DependencyProperty DragDataProperty =
- DependencyProperty.RegisterAttached(
- "DragData",
- typeof(object),
- typeof(WinUIDragSourceBehavior),
- new PropertyMetadata(null));
+ #region Поля
+
+ private readonly IDragDropService _dragDropService;
+ private readonly WinUIDragDropHost _host;
+
+ private FrameworkElement? _element;
+ private object? _dragData;
+ private Point _dragStartPosition;
+ private bool _isDragging;
+ private CancellationTokenSource? _cancellationTokenSource;
+
+ #endregion
+
+ #region Конструктор
///
- /// Прикрепленное свойство для включения перетаскивания.
+ /// Инициализирует новый экземпляр класса .
///
- public static readonly DependencyProperty IsEnabledProperty =
- DependencyProperty.RegisterAttached(
- "IsEnabled",
- typeof(bool),
- typeof(WinUIDragSourceBehavior),
- new PropertyMetadata(false, OnIsEnabledChanged));
-
- ///
- /// Получает значение DragData.
- ///
- public static object GetDragData(FrameworkElement element)
+ /// Сервис для управления операциями перетаскивания.
+ /// Хост для отображения визуальных элементов.
+ ///
+ /// Выбрасывается, если любой из параметров равен null.
+ ///
+ public WinUIDragSourceBehavior(IDragDropService dragDropService, WinUIDragDropHost host)
{
- return element.GetValue(DragDataProperty);
+ _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
+ _host = host ?? throw new ArgumentNullException(nameof(host));
+ }
+
+ #endregion
+
+ #region Публичные методы
+
+ ///
+ /// Прикрепляет поведение к указанному элементу.
+ ///
+ /// Элемент, к которому прикрепляется поведение.
+ /// Данные для перетаскивания. Если не указано, используются
+ /// DataContext или Tag элемента.
+ ///
+ /// Выбрасывается, если равен null.
+ ///
+ ///
+ /// После вызова этого метода элемент начинает обрабатывать события перетаскивания.
+ ///
+ public void Attach(FrameworkElement element, object? dragData = null)
+ {
+ Detach();
+
+ _element = element ?? throw new ArgumentNullException(nameof(element));
+ _dragData = dragData ?? element.DataContext ?? element.Tag;
+
+ SubscribeToEvents();
}
///
- /// Устанавливает значение DragData.
+ /// Открепляет поведение от элемента.
///
- public static void SetDragData(FrameworkElement element, object value)
+ public void Detach()
{
- element.SetValue(DragDataProperty, value);
- }
+ if (_element == null) return;
- ///
- /// Получает значение IsEnabled.
- ///
- public static bool GetIsEnabled(FrameworkElement element)
- {
- return (bool)element.GetValue(IsEnabledProperty);
- }
+ UnsubscribeFromEvents();
- ///
- /// Устанавливает значение IsEnabled.
- ///
- public static void SetIsEnabled(FrameworkElement element, bool value)
- {
- element.SetValue(IsEnabledProperty, value);
- }
-
- private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is FrameworkElement element)
+ if (_isDragging)
{
- // Получаем или создаем экземпляр поведения через Attached Property
- var behavior = GetBehavior(element);
-
- if ((bool)e.NewValue)
- {
- if (behavior == null)
- {
- behavior = new WinUIDragSourceBehavior();
- SetBehavior(element, behavior);
- }
-
- behavior.AssociatedElement = element;
- }
- else
- {
- if (behavior != null)
- {
- behavior.Detach();
- SetBehavior(element, null);
- }
- }
+ _dragDropService.CancelDragAsync().ConfigureAwait(false);
}
+
+ _element = null;
+ _dragData = null;
+ _isDragging = false;
+
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
}
- public WinUIDragSourceBehavior()
- : base(ServiceProviderHelper.GetServiceProvider())
+ #endregion
+
+ #region Обработчики событий
+
+ private void SubscribeToEvents()
{
+ if (_element == null) return;
+
+ _element.PointerPressed += OnPointerPressed;
+ _element.PointerMoved += OnPointerMoved;
+ _element.PointerReleased += OnPointerReleased;
+ _element.PointerCanceled += OnPointerCanceled;
+ _element.PointerCaptureLost += OnPointerCaptureLost;
}
- protected override void SubscribeToEvents(FrameworkElement element)
+ private void UnsubscribeFromEvents()
{
- element.PointerPressed += OnPointerPressed;
- element.PointerMoved += OnPointerMoved;
- element.PointerReleased += OnPointerReleased;
- element.PointerCanceled += OnPointerCanceled;
- element.PointerCaptureLost += OnPointerCaptureLost;
- element.LostFocus += OnLostFocus;
- }
+ if (_element == null) return;
- protected override void UnsubscribeFromEvents(FrameworkElement element)
- {
- element.PointerPressed -= OnPointerPressed;
- element.PointerMoved -= OnPointerMoved;
- element.PointerReleased -= OnPointerReleased;
- element.PointerCanceled -= OnPointerCanceled;
- element.PointerCaptureLost -= OnPointerCaptureLost;
- element.LostFocus -= OnLostFocus;
+ _element.PointerPressed -= OnPointerPressed;
+ _element.PointerMoved -= OnPointerMoved;
+ _element.PointerReleased -= OnPointerReleased;
+ _element.PointerCanceled -= OnPointerCanceled;
+ _element.PointerCaptureLost -= OnPointerCaptureLost;
}
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
- if (AssociatedElement == null) return;
+ if (_element == null || _isDragging) return;
- var point = e.GetCurrentPoint(AssociatedElement);
- OnInteractionStarted(new Point(point.Position.X, point.Position.Y));
+ var point = e.GetCurrentPoint(_element);
+ _dragStartPosition = new Point(point.Position.X, point.Position.Y);
+
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = new CancellationTokenSource();
}
- private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
+ private async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
- if (AssociatedElement == null) return;
+ if (_element == null || _isDragging || _cancellationTokenSource?.IsCancellationRequested == true)
+ return;
- var point = e.GetCurrentPoint(AssociatedElement);
- OnInteractionMoved(new Point(point.Position.X, point.Position.Y));
+ var point = e.GetCurrentPoint(_element);
+ var currentPosition = new Point(point.Position.X, point.Position.Y);
+
+ var distance = CalculateDistance(_dragStartPosition, currentPosition);
+ if (distance > _dragDropService.DragStartThreshold)
+ {
+ await StartDragAsync(currentPosition);
+ }
}
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
- OnInteractionEnded();
+ ResetState();
}
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
- OnInteractionCancelled();
+ ResetState();
}
private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
- OnInteractionCancelled();
+ ResetState();
}
- private void OnLostFocus(object sender, RoutedEventArgs e)
+ private double CalculateDistance(Point p1, Point p2)
{
- OnInteractionCancelled();
+ var dx = p2.X - p1.X;
+ var dy = p2.Y - p1.Y;
+ return Math.Sqrt(dx * dx + dy * dy);
}
- protected override Point ConvertToScreenCoordinates(Point point)
+ ///
+ /// Преобразует координаты элемента в экранные координаты.
+ ///
+ /// Точка в координатах элемента.
+ /// Точка в экранных координатах.
+ private Point ConvertToScreenCoordinates(Point point)
{
- if (AssociatedElement == null)
+ if (_element == null) return point;
+
+ try
+ {
+ var transform = _element.TransformToVisual(Window.Current.Content);
+ var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y));
+ return new Point(screenPoint.X, screenPoint.Y);
+ }
+ catch
+ {
return point;
-
- var transform = AssociatedElement.TransformToVisual(null);
- var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y));
- return new Point(screenPoint.X, screenPoint.Y);
- }
-
- ///
- public override async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
- {
- if (AssociatedElement == null)
- {
- return (false, null);
- }
-
- var data = GetDragData(AssociatedElement);
- if (data == null)
- {
- // Пробуем получить данные из Tag или других источников
- data = AssociatedElement.Tag ?? AssociatedElement.DataContext;
- }
-
- if (data == null)
- {
- return (false, null);
- }
-
- // Получаем начальную позицию в экранных координатах
- var startPosition = ConvertToScreenCoordinates(_dragStartPosition);
-
- // Создаем DragInfo с учетом вашего конструктора
- var dragInfo = new DragInfo(
- data: data,
- allowedEffects: Core.DragDrop.Enums.DragDropEffects.Move |
- Core.DragDrop.Enums.DragDropEffects.Copy,
- startPosition: startPosition,
- source: this
- );
-
- return (true, dragInfo);
- }
-
- protected override void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects)
- {
- base.OnDragCompleted(dragInfo, effects);
-
- // Визуальная обратная связь при завершении
- SetVisualState(AssociatedElement, "Normal");
- }
-
- protected override void OnDragCancelled(DragInfo dragInfo)
- {
- base.OnDragCancelled(dragInfo);
-
- // Визуальная обратная связь при отмене
- SetVisualState(AssociatedElement, "Normal");
- }
-
- private void SetVisualState(FrameworkElement? element, string stateName)
- {
- if (element is Control control)
- {
- try
- {
- VisualStateManager.GoToState(control, stateName, true);
- }
- catch
- {
- // Fallback
- control.Opacity = 1.0;
- }
- }
- else if (element != null)
- {
- // Альтернативная визуальная обратная связь для не-Control элементов
- element.Opacity = 1.0;
}
}
- // Attached property для хранения экземпляра поведения
- private static readonly DependencyProperty BehaviorProperty =
- DependencyProperty.RegisterAttached(
- "Behavior",
- typeof(WinUIDragSourceBehavior),
- typeof(WinUIDragSourceBehavior),
- new PropertyMetadata(null));
-
- private static WinUIDragSourceBehavior GetBehavior(FrameworkElement element)
+ private async Task StartDragAsync(Point position)
{
- return (WinUIDragSourceBehavior)element.GetValue(BehaviorProperty);
- }
+ if (_element == null || _dragData == null || _isDragging) return;
- private static void SetBehavior(FrameworkElement element, WinUIDragSourceBehavior? value)
- {
- element.SetValue(BehaviorProperty, value);
- }
-}
-
-///
-/// Вспомогательный класс для получения IServiceProvider.
-///
-internal static class ServiceProviderHelper
-{
- private static IServiceProvider? _serviceProvider;
-
- public static IServiceProvider GetServiceProvider()
- {
- if (_serviceProvider == null)
+ try
{
- // Ищем IServiceProvider в Application.Current.Resources
- if (Application.Current.Resources.TryGetValue("ServiceProvider", out var provider) &&
- provider is IServiceProvider serviceProvider)
- {
- _serviceProvider = serviceProvider;
- }
- else
- {
- throw new InvalidOperationException(
- "IServiceProvider не найден. Убедитесь, что ServiceProvider зарегистрирован в ресурсах приложения.");
- }
+ var screenPosition = ConvertToScreenCoordinates(position);
+ var started = await _dragDropService.StartDragAsync(this, screenPosition);
+ _isDragging = started;
+ }
+ catch
+ {
+ ResetState();
}
-
- return _serviceProvider;
}
- public static void SetServiceProvider(IServiceProvider serviceProvider)
+ private void ResetState()
{
- _serviceProvider = serviceProvider;
+ _isDragging = false;
+ _dragStartPosition = default;
+
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
}
+
+ #endregion
+
+ #region IDragSource Implementation
+
+ public async Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default)
+ {
+ if (_element == null || _dragData == null)
+ return null;
+
+ try
+ {
+ var dragInfo = new DragInfo(
+ data: _dragData,
+ allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy |
+ Core.DragDrop.Enums.DragDropEffects.Move,
+ startPosition: startPosition,
+ source: this
+ );
+
+ return dragInfo;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ public Task OnDragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects, CancellationToken cancellationToken = default)
+ {
+ _isDragging = false;
+ return Task.CompletedTask;
+ }
+
+ public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default)
+ {
+ _isDragging = false;
+ return Task.CompletedTask;
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs
index a5848e9..23bb6f7 100644
--- a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs
+++ b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs
@@ -1,345 +1,295 @@
-using Lattice.Core.DragDrop.Models;
+using Lattice.Core.DragDrop.Abstractions;
+using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
-using Lattice.UI.DragDrop.Behaviors;
+using Lattice.UI.DragDrop.WinUI.Services;
using Microsoft.UI.Xaml;
using System;
-using System.Collections.Concurrent;
+using System.Threading;
using System.Threading.Tasks;
-namespace Lattice.UI.DragDrop.WinUI.Behaviors
+namespace Lattice.UI.DragDrop.WinUI.Behaviors;
+
+///
+/// Реализация поведения цели сброса для элементов WinUI.
+///
+///
+///
+/// Этот класс обрабатывает события перетаскивания WinUI и преобразует их в вызовы
+/// методов интерфейса .
+///
+///
+/// Поведение автоматически регистрирует элемент в системе перетаскивания,
+/// обновляет его границы при изменении размера и обрабатывает все этапы операции сброса.
+///
+///
+public sealed class WinUIDropTargetBehavior : IDropTarget
{
+ #region Поля
+
+ private readonly Lattice.Core.DragDrop.Services.IDragDropService _dragDropService;
+ private readonly WinUIDragDropHost _host;
+
+ private FrameworkElement? _element;
+ private string? _registrationId;
+ private Rect _currentBounds;
+
+ #endregion
+
+ #region Конструктор
+
///
- /// Поведение цели сброса для элементов WinUI.
- /// Позволяет элементам принимать данные при операции перетаскивания.
+ /// Инициализирует новый экземпляр класса .
///
- ///
- ///
- /// Это поведение должно быть прикреплено к , который должен выступать в качестве цели сброса.
- /// Поведение автоматически регистрирует элемент в системе перетаскивания и обрабатывает все аспекты операции сброса.
- ///
- ///
- /// Для использования необходимо:
- /// 1. Создать экземпляр поведения с помощью или через DI.
- /// 2. Переопределить методы и для реализации логики принятия данных.
- /// 3. При необходимости переопределить для настройки визуальной обратной связи.
- ///
- ///
- public class WinUIDropTargetBehavior : DropTargetBehaviorBase
+ /// Сервис для управления операциями перетаскивания.
+ /// Хост для отображения визуальных элементов.
+ ///
+ /// Выбрасывается, если любой из параметров равен null.
+ ///
+ public WinUIDropTargetBehavior(Lattice.Core.DragDrop.Services.IDragDropService dragDropService, WinUIDragDropHost host)
{
- private static readonly ConcurrentDictionary _attachedBehaviors = new();
- private readonly WeakReference? _weakElement;
-
- ///
- /// Инициализирует новый экземпляр класса .
- ///
- /// Провайдер сервисов.
- ///
- /// Конструктор создает экземпляр поведения, но не прикрепляет его к элементу.
- /// Для прикрепления используйте метод .
- ///
- public WinUIDropTargetBehavior(IServiceProvider serviceProvider)
- : base(serviceProvider)
- {
- }
-
- ///
- /// Инициализирует новый экземпляр класса .
- ///
- /// Провайдер сервисов.
- /// Элемент, к которому прикрепляется поведение.
- ///
- /// Конструктор создает экземпляр поведения и сразу прикрепляет его к указанному элементу.
- ///
- public WinUIDropTargetBehavior(IServiceProvider serviceProvider, FrameworkElement element)
- : base(serviceProvider)
- {
- AssociatedElement = element ?? throw new ArgumentNullException(nameof(element));
- }
-
- ///
- /// Прикрепляет поведение к указанному элементу.
- ///
- /// Элемент, к которому прикрепляется поведение.
- /// Провайдер сервисов.
- ///
- /// Экземпляр поведения, прикрепленного к элементу. Если к элементу уже прикреплено поведение,
- /// возвращает существующий экземпляр.
- ///
- ///
- /// Выбрасывается, когда или равны null.
- ///
- ///
- ///
- /// Этот метод обеспечивает, что к каждому элементу прикреплен только один экземпляр поведения.
- /// Если метод вызывается повторно для того же элемента, возвращается существующий экземпляр.
- ///
- ///
- /// Прикрепленное поведение автоматически отслеживает изменения макета элемента и обновляет
- /// его границы в системе перетаскивания.
- ///
- ///
- 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 =>
- {
- var behavior = new WinUIDropTargetBehavior(serviceProvider, key);
-
- // Подписка на события жизненного цикла элемента
- key.Unloaded += OnElementUnloaded;
- return behavior;
- });
- }
-
- ///
- /// Открепляет поведение от указанного элемента.
- ///
- /// Элемент, от которого открепляется поведение.
- ///
- /// true, если поведение было успешно откреплено; false, если поведение не было прикреплено к элементу.
- ///
- ///
- /// Этот метод освобождает все ресурсы, связанные с поведением, и отписывается от событий элемента.
- /// После вызова этого метода элемент перестает быть целью сброса.
- ///
- 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;
- }
-
- return false;
- }
-
- ///
- /// Получает поведение, прикрепленное к указанному элементу.
- ///
- /// Элемент, для которого требуется получить поведение.
- ///
- /// Экземпляр поведения, прикрепленного к элементу, или null, если поведение не прикреплено.
- ///
- public static WinUIDropTargetBehavior? GetAttachedBehavior(FrameworkElement element)
- {
- _attachedBehaviors.TryGetValue(element, out var behavior);
- return behavior;
- }
-
- ///
- /// Подписывается на события элемента.
- ///
- /// Элемент, к которому прикрепляется поведение.
- ///
- ///
- /// Этот метод подписывается на следующие события:
- ///
- ///
- /// - - для отслеживания изменений макета
- /// - - для отслеживания изменений размера
- /// - - для инициализации при загрузке элемента
- ///
- ///
- /// Переопределите этот метод, чтобы добавить подписку на дополнительные события.
- ///
- ///
- protected override void SubscribeToEvents(FrameworkElement element)
- {
- if (element == null) return;
-
- element.LayoutUpdated += OnLayoutUpdated;
- element.SizeChanged += OnSizeChanged;
- element.Loaded += OnLoaded;
-
- // Если элемент уже загружен, сразу обновляем границы
- if (element.IsLoaded)
- {
- UpdateBounds();
- }
- }
-
- ///
- /// Отписывается от событий элемента.
- ///
- /// Элемент, от которого отписывается поведение.
- ///
- /// Этот метод отписывается от всех событий, на которые подписался .
- ///
- protected override void UnsubscribeFromEvents(FrameworkElement element)
- {
- if (element == null) return;
-
- element.LayoutUpdated -= OnLayoutUpdated;
- element.SizeChanged -= OnSizeChanged;
- element.Loaded -= OnLoaded;
- }
-
- ///
- /// Получает границы элемента в экранных координатах.
- ///
- /// Элемент, границы которого нужно получить.
- ///
- /// Прямоугольник, описывающий границы элемента в экранных координатах.
- ///
- ///
- ///
- /// Метод использует преобразование координат через
- /// для получения глобальных координат элемента.
- ///
- ///
- /// Если элемент не прикреплен к визуальному дереву или его границы не могут быть вычислены,
- /// возвращается пустой прямоугольник.
- ///
- ///
- protected override Rect GetScreenBounds(FrameworkElement element)
- {
- if (element == null || !element.IsLoaded)
- return Rect.Empty;
-
- try
- {
- // Получаем корневой элемент окна
- 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
- {
- // В случае ошибки возвращаем пустой прямоугольник
- return Rect.Empty;
- }
- }
-
- ///
- /// Определяет, может ли элемент принять сбрасываемые данные.
- ///
- /// Информация о сбросе.
- ///
- /// true, если элемент может принять данные; в противном случае — false.
- ///
- ///
- ///
- /// Этот метод является абстрактным и должен быть переопределен в производных классах
- /// для реализации логики принятия данных.
- ///
- ///
- /// Базовая реализация всегда возвращает false. Переопределите этот метод, чтобы определить,
- /// какие типы данных может принимать ваш элемент и при каких условиях.
- ///
- ///
- /// Пример реализации:
- ///
- /// public override bool CanAcceptDrop(DropInfo dropInfo)
- /// {
- /// // Принимаем только строковые данные
- /// return dropInfo.Data is string;
- /// }
- ///
- ///
- ///
- public override async Task CanAcceptDropAsync(DropInfo dropInfo)
- {
- // Базовая реализация - не принимает никакие данные.
- // Переопределите этот метод в производных классах.
- return false;
- }
-
- ///
- /// Обрабатывает сброс данных на элемент.
- ///
- /// Информация о сбросе.
- ///
- ///
- /// Этот метод вызывается, когда пользователь отпускает кнопку мыши над элементом,
- /// и данные должны быть приняты.
- ///
- ///
- /// Базовая реализация ничего не делает. Переопределите этот метод, чтобы реализовать
- /// логику обработки принятых данных.
- ///
- ///
- /// Пример реализации:
- ///
- /// public override void Drop(DropInfo dropInfo)
- /// {
- /// if (dropInfo.Data is string text)
- /// {
- /// // Обработка текстовых данных
- /// AssociatedElement.SetValue(TextBlock.TextProperty, text);
- /// dropInfo.MarkAsHandled();
- /// }
- /// }
- ///
- ///
- ///
- public override async Task DropAsync(DropInfo dropInfo)
- {
- // Базовая реализация ничего не делает.
- // Переопределите этот метод в производных классах.
- }
-
- ///
- /// Освобождает ресурсы, связанные с поведением.
- ///
- ///
- ///
- /// Этот метод отписывается от всех событий, отменяет регистрацию в сервисе перетаскивания
- /// и очищает все ресурсы.
- ///
- ///
- /// После вызова этого метода поведение больше не может быть использовано.
- ///
- ///
- public override void Detach()
- {
- if (AssociatedElement != null && _attachedBehaviors.TryGetValue(AssociatedElement, out _))
- {
- _attachedBehaviors.TryRemove(AssociatedElement, out _);
- }
-
- 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
+ _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
+ _host = host ?? throw new ArgumentNullException(nameof(host));
}
+
+ #endregion
+
+ #region Публичные методы
+
+ ///
+ /// Прикрепляет поведение к указанному элементу.
+ ///
+ /// Элемент, к которому прикрепляется поведение.
+ ///
+ /// Выбрасывается, если равен null.
+ ///
+ ///
+ /// После вызова этого метода:
+ /// 1. Элементу устанавливается = true
+ /// 2. Поведение подписывается на события перетаскивания
+ /// 3. Элемент регистрируется в системе перетаскивания
+ ///
+ public void Attach(FrameworkElement element)
+ {
+ Detach();
+
+ _element = element ?? throw new ArgumentNullException(nameof(element));
+ element.AllowDrop = true;
+
+ SubscribeToEvents();
+ UpdateBounds();
+ RegisterToService();
+ }
+
+ ///
+ /// Открепляет поведение от элемента.
+ ///
+ public void Detach()
+ {
+ if (_element == null) return;
+
+ UnsubscribeFromEvents();
+ UnregisterFromService();
+
+ _element.AllowDrop = false;
+ _element = null;
+ _currentBounds = Rect.Empty;
+ }
+
+ #endregion
+
+ #region Обработчики событий
+
+ private void SubscribeToEvents()
+ {
+ if (_element == null) return;
+
+ _element.DragEnter += OnDragEnter;
+ _element.DragOver += OnDragOver;
+ _element.DragLeave += OnDragLeave;
+ _element.Drop += OnDrop;
+ _element.SizeChanged += OnSizeChanged;
+ }
+
+ private void UnsubscribeFromEvents()
+ {
+ if (_element == null) return;
+
+ _element.DragEnter -= OnDragEnter;
+ _element.DragOver -= OnDragOver;
+ _element.DragLeave -= OnDragLeave;
+ _element.Drop -= OnDrop;
+ _element.SizeChanged -= OnSizeChanged;
+ }
+
+ private async void OnDragEnter(object sender, DragEventArgs e)
+ {
+ if (_element == null) return;
+
+ try
+ {
+ var position = e.GetPosition(_element);
+ var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y));
+
+ if (await CanAcceptDropAsync(dropInfo))
+ {
+ e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
+ }
+ }
+ catch { }
+ }
+
+ private async void OnDragOver(object sender, DragEventArgs e)
+ {
+ if (_element == null) return;
+
+ try
+ {
+ var position = e.GetPosition(_element);
+ var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y));
+
+ if (await CanAcceptDropAsync(dropInfo))
+ {
+ e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
+ await OnDragOverAsync(dropInfo);
+ }
+
+ e.Handled = true;
+ }
+ catch { }
+ }
+
+ private async void OnDragLeave(object sender, DragEventArgs e)
+ {
+ await OnDragLeaveAsync();
+ }
+
+ private async void OnDrop(object sender, DragEventArgs e)
+ {
+ if (_element == null) return;
+
+ try
+ {
+ var position = e.GetPosition(_element);
+ var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y));
+
+ if (await CanAcceptDropAsync(dropInfo))
+ {
+ await OnDropAsync(dropInfo);
+ dropInfo.MarkAsHandled();
+ e.Handled = true;
+ }
+ }
+ catch { }
+ }
+
+ private void OnSizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ UpdateBounds();
+ }
+
+ private DropInfo CreateDropInfo(DragEventArgs e, Point position)
+ {
+ object? data = null;
+
+ 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: _element
+ );
+ }
+
+ ///
+ /// Получает границы элемента в экранных координатах.
+ ///
+ /// Прямоугольник с границами элемента или , если
+ /// элемент не загружен или не может быть преобразован.
+ private Rect GetScreenBounds()
+ {
+ if (_element == null || !_element.IsLoaded)
+ return Rect.Empty;
+
+ try
+ {
+ var transform = _element.TransformToVisual(Window.Current.Content);
+ var position = transform.TransformPoint(new Windows.Foundation.Point(0, 0));
+
+ return new Rect(
+ position.X,
+ position.Y,
+ _element.ActualWidth,
+ _element.ActualHeight
+ );
+ }
+ catch
+ {
+ return Rect.Empty;
+ }
+ }
+
+ private void UpdateBounds()
+ {
+ if (_element == null) return;
+
+ _currentBounds = GetScreenBounds();
+
+ if (_registrationId != null && _currentBounds != Rect.Empty)
+ {
+ _dragDropService.UpdateDropTargetBounds(_registrationId, _currentBounds);
+ }
+ }
+
+ private void RegisterToService()
+ {
+ if (_element == null) return;
+
+ _currentBounds = GetScreenBounds();
+
+ if (_currentBounds != Rect.Empty && _registrationId == null)
+ {
+ _registrationId = _dragDropService.RegisterDropTarget(this, _currentBounds);
+ }
+ }
+
+ private void UnregisterFromService()
+ {
+ if (_registrationId != null)
+ {
+ _dragDropService.UnregisterDropTarget(_registrationId);
+ _registrationId = null;
+ }
+ }
+
+ #endregion
+
+ #region IDropTarget Implementation
+
+ public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default)
+ {
+ return Task.FromResult(true); // Принимаем все по умолчанию
+ }
+
+ public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct = default)
+ {
+ dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Move;
+ return Task.CompletedTask;
+ }
+
+ public Task OnDropAsync(DropInfo dropInfo, CancellationToken ct = default)
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task OnDragLeaveAsync(CancellationToken ct = default)
+ {
+ return Task.CompletedTask;
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs b/Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs
index 8d69ff9..6fbc446 100644
--- a/Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs
+++ b/Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs
@@ -7,8 +7,18 @@ using System;
namespace Lattice.UI.DragDrop.WinUI.Controls;
///
-/// Визуальный элемент для отображения перетаскиваемого объекта.
+/// Визуальный элемент, отображаемый во время перетаскивания.
///
+///
+///
+/// Этот элемент отображает репрезентативное представление перетаскиваемых данных
+/// и следует за курсором мыши во время операции перетаскивания.
+///
+///
+/// Элемент поддерживает настройку прозрачности, смещения и угла поворота,
+/// а также анимированное появление и скрытие.
+///
+///
public class DragAdorner : Control
{
///
@@ -66,8 +76,11 @@ public class DragAdorner : Control
}
///
- /// Получает или задает данные перетаскивания.
+ /// Получает или задает данные, которые отображаются в визуальном элементе.
///
+ ///
+ /// Объект данных для отображения. Обычно это те же данные, которые перетаскиваются.
+ ///
public object DragData
{
get => GetValue(DragDataProperty);
@@ -75,8 +88,13 @@ public class DragAdorner : Control
}
///
- /// Получает или задает смещение относительно курсора.
+ /// Получает или задает смещение элемента относительно позиции курсора.
///
+ ///
+ /// Смещение по осям X и Y. Используется для позиционирования элемента так,
+ /// чтобы он не перекрывал курсор. Значение по умолчанию вычисляется автоматически
+ /// на основе размера элемента.
+ ///
public Point Offset
{
get => (Point)GetValue(OffsetProperty);
@@ -111,9 +129,13 @@ public class DragAdorner : Control
}
///
- /// Обновляет позицию элемента относительно курсора.
+ /// Обновляет позицию элемента в соответствии с позицией курсора.
///
- /// Позиция курсора в экранных координатах.
+ /// Текущая позиция курсора в экранных координатах.
+ ///
+ /// Метод применяет трансформации для позиционирования элемента с учетом
+ /// заданного смещения и угла поворота.
+ ///
public void UpdatePosition(Point cursorPosition)
{
var transform = new TranslateTransform
diff --git a/Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs b/Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs
index 0d828a6..8d7182b 100644
--- a/Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs
+++ b/Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs
@@ -7,8 +7,19 @@ using System.Linq;
namespace Lattice.UI.DragDrop.WinUI.Controls;
///
-/// Оверлей для отображения визуальных элементов перетаскивания.
+/// Оверлейный слой для отображения всех визуальных элементов перетаскивания.
///
+///
+///
+/// Этот элемент добавляется поверх всего содержимого окна и содержит:
+/// - Drag-визуализации (элементы, следующие за курсором)
+/// - Drop-превью (подсветка областей сброса)
+///
+///
+/// Элемент имеет = false, чтобы не перехватывать
+/// пользовательский ввод во время операций перетаскивания.
+///
+///
public class DragDropOverlay : Canvas
{
private readonly List _dragVisuals = new();
diff --git a/Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs b/Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs
index a6711f2..7fcb552 100644
--- a/Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs
+++ b/Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs
@@ -8,8 +8,12 @@ using Windows.UI;
namespace Lattice.UI.DragDrop.WinUI.Controls;
///
-/// Визуальный элемент для предварительного просмотра области сброса.
+/// Визуальный элемент для подсветки области сброса.
///
+///
+/// Этот элемент отображается вокруг целевого элемента при наведении перетаскиваемого
+/// объекта для визуального указания возможности сброса.
+///
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Highlighted", GroupName = "CommonStates")]
public class DropPreviewAdorner : Control
@@ -54,8 +58,11 @@ public class DropPreviewAdorner : Control
}
///
- /// Получает или задает цвет предварительного просмотра.
+ /// Получает или задает цвет подсветки области сброса.
///
+ ///
+ /// Цвет границы и фона подсветки. Значение по умолчанию берется из ресурсов темы.
+ ///
public Color PreviewColor
{
get => (Color)GetValue(PreviewColorProperty);
@@ -81,9 +88,12 @@ public class DropPreviewAdorner : Control
}
///
- /// Показывает элемент с указанными границами.
+ /// Показывает элемент подсветки для указанной области.
///
- /// Границы для отображения.
+ /// Границы области для подсветки.
+ ///
+ /// Метод позиционирует элемент по указанным границам и запускает анимацию появления.
+ ///
public void Show(Core.Geometry.Rect bounds)
{
Width = bounds.Width;
@@ -124,9 +134,9 @@ public class DropPreviewAdorner : Control
}
///
- /// Обновляет позицию элемента.
+ /// Обновляет позицию и размер элемента подсветки.
///
- /// Новые границы.
+ /// Новые границы области для подсветки.
public void UpdatePosition(Core.Geometry.Rect bounds)
{
if (RenderTransform is TranslateTransform transform)
diff --git a/Lattice.UI.DragDrop.WinUI/DragDropProperties.cs b/Lattice.UI.DragDrop.WinUI/DragDropProperties.cs
new file mode 100644
index 0000000..2ffac4b
--- /dev/null
+++ b/Lattice.UI.DragDrop.WinUI/DragDropProperties.cs
@@ -0,0 +1,168 @@
+using Microsoft.UI.Xaml;
+
+namespace Lattice.UI.DragDrop.WinUI;
+
+///
+/// Предоставляет attached properties для настройки drag-and-drop поведения элементов WinUI.
+///
+///
+/// Этот класс содержит attached properties, которые позволяют включать и настраивать
+/// возможности перетаскивания и сброса для любых FrameworkElement в приложении WinUI.
+///
+public static class DragDropProperties
+{
+ #region Drag Source Properties
+
+ ///
+ /// Прикрепленное свойство для включения перетаскивания.
+ ///
+ public static readonly DependencyProperty IsDragSourceProperty =
+ DependencyProperty.RegisterAttached(
+ "IsDragSource",
+ typeof(bool),
+ typeof(DragDropProperties),
+ new PropertyMetadata(false, OnIsDragSourceChanged));
+
+ ///
+ /// Прикрепленное свойство для данных перетаскивания.
+ ///
+ public static readonly DependencyProperty DragDataProperty =
+ DependencyProperty.RegisterAttached(
+ "DragData",
+ typeof(object),
+ typeof(DragDropProperties),
+ new PropertyMetadata(null));
+
+ ///
+ /// Получает значение IsDragSource.
+ ///
+ public static bool GetIsDragSource(UIElement element) =>
+ (bool)element.GetValue(IsDragSourceProperty);
+
+ ///
+ /// Устанавливает значение IsDragSource.
+ ///
+ public static void SetIsDragSource(UIElement element, bool value) =>
+ element.SetValue(IsDragSourceProperty, value);
+
+ ///
+ /// Получает значение DragData.
+ ///
+ public static object? GetDragData(UIElement element) =>
+ element.GetValue(DragDataProperty);
+
+ ///
+ /// Устанавливает значение DragData.
+ ///
+ public static void SetDragData(UIElement element, object? value) =>
+ element.SetValue(DragDataProperty, value);
+
+ private static void OnIsDragSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is FrameworkElement element)
+ {
+ if ((bool)e.NewValue)
+ {
+ var data = GetDragData(element);
+ Services.WinUIDragDropManager.Instance.MakeDragSource(element, data);
+ }
+ else
+ {
+ Services.WinUIDragDropManager.Instance.RemoveDragSource(element);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Drop Target Properties
+
+ ///
+ /// Прикрепленное свойство для включения цели сброса.
+ ///
+ public static readonly DependencyProperty IsDropTargetProperty =
+ DependencyProperty.RegisterAttached(
+ "IsDropTarget",
+ typeof(bool),
+ typeof(DragDropProperties),
+ new PropertyMetadata(false, OnIsDropTargetChanged));
+
+ ///
+ /// Получает значение IsDropTarget.
+ ///
+ public static bool GetIsDropTarget(UIElement element) =>
+ (bool)element.GetValue(IsDropTargetProperty);
+
+ ///
+ /// Устанавливает значение IsDropTarget.
+ ///
+ public static void SetIsDropTarget(UIElement element, bool value) =>
+ element.SetValue(IsDropTargetProperty, value);
+
+ private static void OnIsDropTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is FrameworkElement element)
+ {
+ if ((bool)e.NewValue)
+ {
+ Services.WinUIDragDropManager.Instance.MakeDropTarget(element);
+ }
+ else
+ {
+ Services.WinUIDragDropManager.Instance.RemoveDropTarget(element);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ ///
+ /// Включает перетаскивание для элемента с указанными данными.
+ ///
+ /// Элемент, для которого включается перетаскивание.
+ /// Данные для перетаскивания. Если не указано, используются DataContext или Tag элемента.
+ ///
+ ///
+ /// myElement.EnableDrag(item);
+ ///
+ ///
+ public static void EnableDrag(this FrameworkElement element, object? dragData = null)
+ {
+ if (dragData != null)
+ {
+ SetDragData(element, dragData);
+ }
+ SetIsDragSource(element, true);
+ }
+
+ ///
+ /// Отключает перетаскивание для элемента.
+ ///
+ /// Элемент, для которого отключается перетаскивание.
+ public static void DisableDrag(this FrameworkElement element)
+ {
+ SetIsDragSource(element, false);
+ }
+
+ ///
+ /// Включает возможность сброса для элемента.
+ ///
+ /// Элемент, для которого включается возможность сброса.
+ public static void EnableDrop(this FrameworkElement element)
+ {
+ SetIsDropTarget(element, true);
+ }
+
+ ///
+ /// Отключает возможность сброса для элемента.
+ ///
+ /// Элемент, для которого отключается возможность сброса.
+ public static void DisableDrop(this FrameworkElement element)
+ {
+ SetIsDropTarget(element, false);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs b/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs
deleted file mode 100644
index 1cd8ccf..0000000
--- a/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs
+++ /dev/null
@@ -1,217 +0,0 @@
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Media;
-using System;
-using System.Linq;
-
-namespace Lattice.UI.DragDrop.WinUI.Extensions;
-
-///
-/// Методы расширения для настройки перетаскивания в WinUI.
-///
-public static class DragDropExtensions
-{
- #region Drag Source Extensions
-
- ///
- /// Делает элемент источником перетаскивания с указанными данными.
- ///
- public static void MakeDragSource(this FrameworkElement element, object dragData)
- {
- Behaviors.WinUIDragSourceBehavior.SetDragData(element, dragData);
- Behaviors.WinUIDragSourceBehavior.SetIsEnabled(element, true);
- }
-
- ///
- /// Делает элемент источником перетаскивания с фабрикой данных.
- ///
- public static void MakeDragSource(this FrameworkElement element, Func