Compare commits
2 Commits
2bd7d3c474
...
6ad7b5dcdb
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ad7b5dcdb | |||
| bbb20edb03 |
@@ -59,7 +59,7 @@ public class DragDropException : Exception
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Коды ошибок Drag & Drop системы.
|
||||
/// Коды ошибок Drag and Drop системы.
|
||||
/// </summary>
|
||||
public static class DragDropErrorCodes
|
||||
{
|
||||
|
||||
@@ -28,13 +28,10 @@ Lattice.Core.DragDrop/
|
||||
├── Models/ # Модели данных
|
||||
│ ├── DragInfo.cs # Информация о перетаскивании
|
||||
│ └── DropInfo.cs # Информация о сбросе
|
||||
├── Services/ # Сервисы
|
||||
│ ├── IDragDropService.cs # Основной интерфейс
|
||||
│ ├── DragDropService.cs # Реализация сервиса
|
||||
│ └── EventArgs/ # Аргументы событий
|
||||
└── Utilities/ # Утилиты и фабрики
|
||||
├── DragDropUtilities.cs # Синхронные утилиты
|
||||
└── AsyncDragDropUtilities.cs # Асинхронные утилиты
|
||||
└── Services/ # Сервисы
|
||||
├── IDragDropService.cs # Основной интерфейс
|
||||
├── DragDropService.cs # Реализация сервиса
|
||||
└── EventArgs/ # Аргументы событий
|
||||
```
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,290 +1,267 @@
|
||||
using Lattice.Core.DragDrop.Models;
|
||||
using Lattice.Core.DragDrop.Abstractions;
|
||||
using Lattice.Core.DragDrop.Models;
|
||||
using Lattice.Core.DragDrop.Services;
|
||||
using Lattice.Core.Geometry;
|
||||
using Lattice.UI.DragDrop.Behaviors;
|
||||
using Lattice.UI.DragDrop.WinUI.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// Поведение источника перетаскивания для WinUI FrameworkElement.
|
||||
/// Реализация поведения источника перетаскивания для элементов WinUI.
|
||||
/// </summary>
|
||||
public class WinUIDragSourceBehavior : DragSourceBehaviorBase<FrameworkElement>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот класс обрабатывает события мыши/тач и преобразует их в операции перетаскивания.
|
||||
/// Он реализует интерфейс <see cref="IDragSource"/> для интеграции с системой перетаскивания.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Поведение автоматически отслеживает начало перемещения мыши, проверяет порог
|
||||
/// перетаскивания и инициирует операцию через <see cref="IDragDropService"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class WinUIDragSourceBehavior : IDragSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Прикрепленное свойство для данных перетаскивания.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DragDataProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"DragData",
|
||||
typeof(object),
|
||||
typeof(WinUIDragSourceBehavior),
|
||||
new PropertyMetadata(null));
|
||||
#region Поля
|
||||
|
||||
private readonly IDragDropService _dragDropService;
|
||||
private readonly WinUIDragDropHost _host;
|
||||
|
||||
private FrameworkElement? _element;
|
||||
private object? _dragData;
|
||||
private Point _dragStartPosition;
|
||||
private bool _isDragging;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Конструктор
|
||||
|
||||
/// <summary>
|
||||
/// Прикрепленное свойство для включения перетаскивания.
|
||||
/// Инициализирует новый экземпляр класса <see cref="WinUIDragSourceBehavior"/>.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsEnabledProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsEnabled",
|
||||
typeof(bool),
|
||||
typeof(WinUIDragSourceBehavior),
|
||||
new PropertyMetadata(false, OnIsEnabledChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение DragData.
|
||||
/// </summary>
|
||||
public static object GetDragData(FrameworkElement element)
|
||||
/// <param name="dragDropService">Сервис для управления операциями перетаскивания.</param>
|
||||
/// <param name="host">Хост для отображения визуальных элементов.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если любой из параметров равен null.
|
||||
/// </exception>
|
||||
public WinUIDragSourceBehavior(IDragDropService dragDropService, WinUIDragDropHost host)
|
||||
{
|
||||
return element.GetValue(DragDataProperty);
|
||||
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
|
||||
_host = host ?? throw new ArgumentNullException(nameof(host));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Публичные методы
|
||||
|
||||
/// <summary>
|
||||
/// Прикрепляет поведение к указанному элементу.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
|
||||
/// <param name="dragData">Данные для перетаскивания. Если не указано, используются
|
||||
/// DataContext или Tag элемента.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="element"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// После вызова этого метода элемент начинает обрабатывать события перетаскивания.
|
||||
/// </remarks>
|
||||
public void Attach(FrameworkElement element, object? dragData = null)
|
||||
{
|
||||
Detach();
|
||||
|
||||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||||
_dragData = dragData ?? element.DataContext ?? element.Tag;
|
||||
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение DragData.
|
||||
/// Открепляет поведение от элемента.
|
||||
/// </summary>
|
||||
public static void SetDragData(FrameworkElement element, object value)
|
||||
public void Detach()
|
||||
{
|
||||
element.SetValue(DragDataProperty, value);
|
||||
}
|
||||
if (_element == null) return;
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение IsEnabled.
|
||||
/// </summary>
|
||||
public static bool GetIsEnabled(FrameworkElement element)
|
||||
{
|
||||
return (bool)element.GetValue(IsEnabledProperty);
|
||||
}
|
||||
UnsubscribeFromEvents();
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение IsEnabled.
|
||||
/// </summary>
|
||||
public static void SetIsEnabled(FrameworkElement element, bool value)
|
||||
{
|
||||
element.SetValue(IsEnabledProperty, value);
|
||||
}
|
||||
|
||||
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is FrameworkElement element)
|
||||
if (_isDragging)
|
||||
{
|
||||
// Получаем или создаем экземпляр поведения через Attached Property
|
||||
var behavior = GetBehavior(element);
|
||||
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
if (behavior == null)
|
||||
{
|
||||
behavior = new WinUIDragSourceBehavior();
|
||||
SetBehavior(element, behavior);
|
||||
}
|
||||
|
||||
behavior.AssociatedElement = element;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (behavior != null)
|
||||
{
|
||||
behavior.Detach();
|
||||
SetBehavior(element, null);
|
||||
}
|
||||
}
|
||||
_dragDropService.CancelDragAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_element = null;
|
||||
_dragData = null;
|
||||
_isDragging = false;
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
public WinUIDragSourceBehavior()
|
||||
: base(ServiceProviderHelper.GetServiceProvider())
|
||||
#endregion
|
||||
|
||||
#region Обработчики событий
|
||||
|
||||
private void SubscribeToEvents()
|
||||
{
|
||||
if (_element == null) return;
|
||||
|
||||
_element.PointerPressed += OnPointerPressed;
|
||||
_element.PointerMoved += OnPointerMoved;
|
||||
_element.PointerReleased += OnPointerReleased;
|
||||
_element.PointerCanceled += OnPointerCanceled;
|
||||
_element.PointerCaptureLost += OnPointerCaptureLost;
|
||||
}
|
||||
|
||||
protected override void SubscribeToEvents(FrameworkElement element)
|
||||
private void UnsubscribeFromEvents()
|
||||
{
|
||||
element.PointerPressed += OnPointerPressed;
|
||||
element.PointerMoved += OnPointerMoved;
|
||||
element.PointerReleased += OnPointerReleased;
|
||||
element.PointerCanceled += OnPointerCanceled;
|
||||
element.PointerCaptureLost += OnPointerCaptureLost;
|
||||
element.LostFocus += OnLostFocus;
|
||||
}
|
||||
if (_element == null) return;
|
||||
|
||||
protected override void UnsubscribeFromEvents(FrameworkElement element)
|
||||
{
|
||||
element.PointerPressed -= OnPointerPressed;
|
||||
element.PointerMoved -= OnPointerMoved;
|
||||
element.PointerReleased -= OnPointerReleased;
|
||||
element.PointerCanceled -= OnPointerCanceled;
|
||||
element.PointerCaptureLost -= OnPointerCaptureLost;
|
||||
element.LostFocus -= OnLostFocus;
|
||||
_element.PointerPressed -= OnPointerPressed;
|
||||
_element.PointerMoved -= OnPointerMoved;
|
||||
_element.PointerReleased -= OnPointerReleased;
|
||||
_element.PointerCanceled -= OnPointerCanceled;
|
||||
_element.PointerCaptureLost -= OnPointerCaptureLost;
|
||||
}
|
||||
|
||||
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (AssociatedElement == null) return;
|
||||
if (_element == null || _isDragging) return;
|
||||
|
||||
var point = e.GetCurrentPoint(AssociatedElement);
|
||||
OnInteractionStarted(new Point(point.Position.X, point.Position.Y));
|
||||
var point = e.GetCurrentPoint(_element);
|
||||
_dragStartPosition = new Point(point.Position.X, point.Position.Y);
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
private async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (AssociatedElement == null) return;
|
||||
if (_element == null || _isDragging || _cancellationTokenSource?.IsCancellationRequested == true)
|
||||
return;
|
||||
|
||||
var point = e.GetCurrentPoint(AssociatedElement);
|
||||
OnInteractionMoved(new Point(point.Position.X, point.Position.Y));
|
||||
var point = e.GetCurrentPoint(_element);
|
||||
var currentPosition = new Point(point.Position.X, point.Position.Y);
|
||||
|
||||
var distance = CalculateDistance(_dragStartPosition, currentPosition);
|
||||
if (distance > _dragDropService.DragStartThreshold)
|
||||
{
|
||||
await StartDragAsync(currentPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
OnInteractionEnded();
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
OnInteractionCancelled();
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
OnInteractionCancelled();
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void OnLostFocus(object sender, RoutedEventArgs e)
|
||||
private double CalculateDistance(Point p1, Point p2)
|
||||
{
|
||||
OnInteractionCancelled();
|
||||
var dx = p2.X - p1.X;
|
||||
var dy = p2.Y - p1.Y;
|
||||
return Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
protected override Point ConvertToScreenCoordinates(Point point)
|
||||
/// <summary>
|
||||
/// Преобразует координаты элемента в экранные координаты.
|
||||
/// </summary>
|
||||
/// <param name="point">Точка в координатах элемента.</param>
|
||||
/// <returns>Точка в экранных координатах.</returns>
|
||||
private Point ConvertToScreenCoordinates(Point point)
|
||||
{
|
||||
if (AssociatedElement == null)
|
||||
if (_element == null) return point;
|
||||
|
||||
try
|
||||
{
|
||||
var transform = _element.TransformToVisual(Window.Current.Content);
|
||||
var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y));
|
||||
return new Point(screenPoint.X, screenPoint.Y);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return point;
|
||||
|
||||
var transform = AssociatedElement.TransformToVisual(null);
|
||||
var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y));
|
||||
return new Point(screenPoint.X, screenPoint.Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
|
||||
{
|
||||
if (AssociatedElement == null)
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var data = GetDragData(AssociatedElement);
|
||||
if (data == null)
|
||||
{
|
||||
// Пробуем получить данные из Tag или других источников
|
||||
data = AssociatedElement.Tag ?? AssociatedElement.DataContext;
|
||||
}
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
// Получаем начальную позицию в экранных координатах
|
||||
var startPosition = ConvertToScreenCoordinates(_dragStartPosition);
|
||||
|
||||
// Создаем DragInfo с учетом вашего конструктора
|
||||
var dragInfo = new DragInfo(
|
||||
data: data,
|
||||
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Move |
|
||||
Core.DragDrop.Enums.DragDropEffects.Copy,
|
||||
startPosition: startPosition,
|
||||
source: this
|
||||
);
|
||||
|
||||
return (true, dragInfo);
|
||||
}
|
||||
|
||||
protected override void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects)
|
||||
{
|
||||
base.OnDragCompleted(dragInfo, effects);
|
||||
|
||||
// Визуальная обратная связь при завершении
|
||||
SetVisualState(AssociatedElement, "Normal");
|
||||
}
|
||||
|
||||
protected override void OnDragCancelled(DragInfo dragInfo)
|
||||
{
|
||||
base.OnDragCancelled(dragInfo);
|
||||
|
||||
// Визуальная обратная связь при отмене
|
||||
SetVisualState(AssociatedElement, "Normal");
|
||||
}
|
||||
|
||||
private void SetVisualState(FrameworkElement? element, string stateName)
|
||||
{
|
||||
if (element is Control control)
|
||||
{
|
||||
try
|
||||
{
|
||||
VisualStateManager.GoToState(control, stateName, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback
|
||||
control.Opacity = 1.0;
|
||||
}
|
||||
}
|
||||
else if (element != null)
|
||||
{
|
||||
// Альтернативная визуальная обратная связь для не-Control элементов
|
||||
element.Opacity = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Attached property для хранения экземпляра поведения
|
||||
private static readonly DependencyProperty BehaviorProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"Behavior",
|
||||
typeof(WinUIDragSourceBehavior),
|
||||
typeof(WinUIDragSourceBehavior),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
private static WinUIDragSourceBehavior GetBehavior(FrameworkElement element)
|
||||
private async Task StartDragAsync(Point position)
|
||||
{
|
||||
return (WinUIDragSourceBehavior)element.GetValue(BehaviorProperty);
|
||||
}
|
||||
if (_element == null || _dragData == null || _isDragging) return;
|
||||
|
||||
private static void SetBehavior(FrameworkElement element, WinUIDragSourceBehavior? value)
|
||||
{
|
||||
element.SetValue(BehaviorProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вспомогательный класс для получения IServiceProvider.
|
||||
/// </summary>
|
||||
internal static class ServiceProviderHelper
|
||||
{
|
||||
private static IServiceProvider? _serviceProvider;
|
||||
|
||||
public static IServiceProvider GetServiceProvider()
|
||||
{
|
||||
if (_serviceProvider == null)
|
||||
try
|
||||
{
|
||||
// Ищем IServiceProvider в Application.Current.Resources
|
||||
if (Application.Current.Resources.TryGetValue("ServiceProvider", out var provider) &&
|
||||
provider is IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"IServiceProvider не найден. Убедитесь, что ServiceProvider зарегистрирован в ресурсах приложения.");
|
||||
}
|
||||
var screenPosition = ConvertToScreenCoordinates(position);
|
||||
var started = await _dragDropService.StartDragAsync(this, screenPosition);
|
||||
_isDragging = started;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ResetState();
|
||||
}
|
||||
|
||||
return _serviceProvider;
|
||||
}
|
||||
|
||||
public static void SetServiceProvider(IServiceProvider serviceProvider)
|
||||
private void ResetState()
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_isDragging = false;
|
||||
_dragStartPosition = default;
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDragSource Implementation
|
||||
|
||||
public async Task<DragInfo?> TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_element == null || _dragData == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var dragInfo = new DragInfo(
|
||||
data: _dragData,
|
||||
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy |
|
||||
Core.DragDrop.Enums.DragDropEffects.Move,
|
||||
startPosition: startPosition,
|
||||
source: this
|
||||
);
|
||||
|
||||
return dragInfo;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnDragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_isDragging = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_isDragging = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация поведения цели сброса для элементов WinUI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот класс обрабатывает события перетаскивания WinUI и преобразует их в вызовы
|
||||
/// методов интерфейса <see cref="IDropTarget"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Поведение автоматически регистрирует элемент в системе перетаскивания,
|
||||
/// обновляет его границы при изменении размера и обрабатывает все этапы операции сброса.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
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 Конструктор
|
||||
|
||||
/// <summary>
|
||||
/// Поведение цели сброса для элементов WinUI.
|
||||
/// Позволяет элементам принимать данные при операции перетаскивания.
|
||||
/// Инициализирует новый экземпляр класса <see cref="WinUIDropTargetBehavior"/>.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <param name="dragDropService">Сервис для управления операциями перетаскивания.</param>
|
||||
/// <param name="host">Хост для отображения визуальных элементов.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если любой из параметров равен null.
|
||||
/// </exception>
|
||||
public WinUIDropTargetBehavior(Lattice.Core.DragDrop.Services.IDragDropService dragDropService, WinUIDragDropHost host)
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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 =>
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отписывается от событий элемента.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, от которого отписывается поведение.</param>
|
||||
/// <remarks>
|
||||
/// Этот метод отписывается от всех событий, на которые подписался <see cref="SubscribeToEvents"/>.
|
||||
/// </remarks>
|
||||
protected override void UnsubscribeFromEvents(FrameworkElement element)
|
||||
{
|
||||
if (element == null) return;
|
||||
|
||||
element.LayoutUpdated -= OnLayoutUpdated;
|
||||
element.SizeChanged -= OnSizeChanged;
|
||||
element.Loaded -= OnLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает границы элемента в экранных координатах.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, границы которого нужно получить.</param>
|
||||
/// <returns>
|
||||
/// Прямоугольник, описывающий границы элемента в экранных координатах.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Метод использует преобразование координат через <see cref="UIElement.TransformToVisual"/>
|
||||
/// для получения глобальных координат элемента.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Если элемент не прикреплен к визуальному дереву или его границы не могут быть вычислены,
|
||||
/// возвращается пустой прямоугольник.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// Базовая реализация - не принимает никакие данные.
|
||||
// Переопределите этот метод в производных классах.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// Базовая реализация ничего не делает.
|
||||
// Переопределите этот метод в производных классах.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Освобождает ресурсы, связанные с поведением.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот метод отписывается от всех событий, отменяет регистрацию в сервисе перетаскивания
|
||||
/// и очищает все ресурсы.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// После вызова этого метода поведение больше не может быть использовано.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
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 Публичные методы
|
||||
|
||||
/// <summary>
|
||||
/// Прикрепляет поведение к указанному элементу.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="element"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// После вызова этого метода:
|
||||
/// 1. Элементу устанавливается <see cref="UIElement.AllowDrop"/> = true
|
||||
/// 2. Поведение подписывается на события перетаскивания
|
||||
/// 3. Элемент регистрируется в системе перетаскивания
|
||||
/// </remarks>
|
||||
public void Attach(FrameworkElement element)
|
||||
{
|
||||
Detach();
|
||||
|
||||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||||
element.AllowDrop = true;
|
||||
|
||||
SubscribeToEvents();
|
||||
UpdateBounds();
|
||||
RegisterToService();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Открепляет поведение от элемента.
|
||||
/// </summary>
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает границы элемента в экранных координатах.
|
||||
/// </summary>
|
||||
/// <returns>Прямоугольник с границами элемента или <see cref="Rect.Empty"/>, если
|
||||
/// элемент не загружен или не может быть преобразован.</returns>
|
||||
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<bool> 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
|
||||
}
|
||||
@@ -7,8 +7,18 @@ using System;
|
||||
namespace Lattice.UI.DragDrop.WinUI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Визуальный элемент для отображения перетаскиваемого объекта.
|
||||
/// Визуальный элемент, отображаемый во время перетаскивания.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот элемент отображает репрезентативное представление перетаскиваемых данных
|
||||
/// и следует за курсором мыши во время операции перетаскивания.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Элемент поддерживает настройку прозрачности, смещения и угла поворота,
|
||||
/// а также анимированное появление и скрытие.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class DragAdorner : Control
|
||||
{
|
||||
/// <summary>
|
||||
@@ -66,8 +76,11 @@ public class DragAdorner : Control
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает или задает данные перетаскивания.
|
||||
/// Получает или задает данные, которые отображаются в визуальном элементе.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Объект данных для отображения. Обычно это те же данные, которые перетаскиваются.
|
||||
/// </value>
|
||||
public object DragData
|
||||
{
|
||||
get => GetValue(DragDataProperty);
|
||||
@@ -75,8 +88,13 @@ public class DragAdorner : Control
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает или задает смещение относительно курсора.
|
||||
/// Получает или задает смещение элемента относительно позиции курсора.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Смещение по осям X и Y. Используется для позиционирования элемента так,
|
||||
/// чтобы он не перекрывал курсор. Значение по умолчанию вычисляется автоматически
|
||||
/// на основе размера элемента.
|
||||
/// </value>
|
||||
public Point Offset
|
||||
{
|
||||
get => (Point)GetValue(OffsetProperty);
|
||||
@@ -111,9 +129,13 @@ public class DragAdorner : Control
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет позицию элемента относительно курсора.
|
||||
/// Обновляет позицию элемента в соответствии с позицией курсора.
|
||||
/// </summary>
|
||||
/// <param name="cursorPosition">Позиция курсора в экранных координатах.</param>
|
||||
/// <param name="cursorPosition">Текущая позиция курсора в экранных координатах.</param>
|
||||
/// <remarks>
|
||||
/// Метод применяет трансформации для позиционирования элемента с учетом
|
||||
/// заданного смещения и угла поворота.
|
||||
/// </remarks>
|
||||
public void UpdatePosition(Point cursorPosition)
|
||||
{
|
||||
var transform = new TranslateTransform
|
||||
|
||||
@@ -7,8 +7,19 @@ using System.Linq;
|
||||
namespace Lattice.UI.DragDrop.WinUI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Оверлей для отображения визуальных элементов перетаскивания.
|
||||
/// Оверлейный слой для отображения всех визуальных элементов перетаскивания.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот элемент добавляется поверх всего содержимого окна и содержит:
|
||||
/// - Drag-визуализации (элементы, следующие за курсором)
|
||||
/// - Drop-превью (подсветка областей сброса)
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Элемент имеет <see cref="Canvas.IsHitTestVisible"/> = false, чтобы не перехватывать
|
||||
/// пользовательский ввод во время операций перетаскивания.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class DragDropOverlay : Canvas
|
||||
{
|
||||
private readonly List<UIElement> _dragVisuals = new();
|
||||
|
||||
@@ -8,8 +8,12 @@ using Windows.UI;
|
||||
namespace Lattice.UI.DragDrop.WinUI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Визуальный элемент для предварительного просмотра области сброса.
|
||||
/// Визуальный элемент для подсветки области сброса.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Этот элемент отображается вокруг целевого элемента при наведении перетаскиваемого
|
||||
/// объекта для визуального указания возможности сброса.
|
||||
/// </remarks>
|
||||
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
|
||||
[TemplateVisualState(Name = "Highlighted", GroupName = "CommonStates")]
|
||||
public class DropPreviewAdorner : Control
|
||||
@@ -54,8 +58,11 @@ public class DropPreviewAdorner : Control
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает или задает цвет предварительного просмотра.
|
||||
/// Получает или задает цвет подсветки области сброса.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Цвет границы и фона подсветки. Значение по умолчанию берется из ресурсов темы.
|
||||
/// </value>
|
||||
public Color PreviewColor
|
||||
{
|
||||
get => (Color)GetValue(PreviewColorProperty);
|
||||
@@ -81,9 +88,12 @@ public class DropPreviewAdorner : Control
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Показывает элемент с указанными границами.
|
||||
/// Показывает элемент подсветки для указанной области.
|
||||
/// </summary>
|
||||
/// <param name="bounds">Границы для отображения.</param>
|
||||
/// <param name="bounds">Границы области для подсветки.</param>
|
||||
/// <remarks>
|
||||
/// Метод позиционирует элемент по указанным границам и запускает анимацию появления.
|
||||
/// </remarks>
|
||||
public void Show(Core.Geometry.Rect bounds)
|
||||
{
|
||||
Width = bounds.Width;
|
||||
@@ -124,9 +134,9 @@ public class DropPreviewAdorner : Control
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет позицию элемента.
|
||||
/// Обновляет позицию и размер элемента подсветки.
|
||||
/// </summary>
|
||||
/// <param name="bounds">Новые границы.</param>
|
||||
/// <param name="bounds">Новые границы области для подсветки.</param>
|
||||
public void UpdatePosition(Core.Geometry.Rect bounds)
|
||||
{
|
||||
if (RenderTransform is TranslateTransform transform)
|
||||
|
||||
168
Lattice.UI.DragDrop.WinUI/DragDropProperties.cs
Normal file
168
Lattice.UI.DragDrop.WinUI/DragDropProperties.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI;
|
||||
|
||||
/// <summary>
|
||||
/// Предоставляет attached properties для настройки drag-and-drop поведения элементов WinUI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Этот класс содержит attached properties, которые позволяют включать и настраивать
|
||||
/// возможности перетаскивания и сброса для любых FrameworkElement в приложении WinUI.
|
||||
/// </remarks>
|
||||
public static class DragDropProperties
|
||||
{
|
||||
#region Drag Source Properties
|
||||
|
||||
/// <summary>
|
||||
/// Прикрепленное свойство для включения перетаскивания.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsDragSourceProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsDragSource",
|
||||
typeof(bool),
|
||||
typeof(DragDropProperties),
|
||||
new PropertyMetadata(false, OnIsDragSourceChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Прикрепленное свойство для данных перетаскивания.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DragDataProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"DragData",
|
||||
typeof(object),
|
||||
typeof(DragDropProperties),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение IsDragSource.
|
||||
/// </summary>
|
||||
public static bool GetIsDragSource(UIElement element) =>
|
||||
(bool)element.GetValue(IsDragSourceProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение IsDragSource.
|
||||
/// </summary>
|
||||
public static void SetIsDragSource(UIElement element, bool value) =>
|
||||
element.SetValue(IsDragSourceProperty, value);
|
||||
|
||||
/// <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);
|
||||
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Прикрепленное свойство для включения цели сброса.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsDropTargetProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsDropTarget",
|
||||
typeof(bool),
|
||||
typeof(DragDropProperties),
|
||||
new PropertyMetadata(false, OnIsDropTargetChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение IsDropTarget.
|
||||
/// </summary>
|
||||
public static bool GetIsDropTarget(UIElement element) =>
|
||||
(bool)element.GetValue(IsDropTargetProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение IsDropTarget.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Включает перетаскивание для элемента с указанными данными.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, для которого включается перетаскивание.</param>
|
||||
/// <param name="dragData">Данные для перетаскивания. Если не указано, используются DataContext или Tag элемента.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// myElement.EnableDrag(item);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static void EnableDrag(this FrameworkElement element, object? dragData = null)
|
||||
{
|
||||
if (dragData != null)
|
||||
{
|
||||
SetDragData(element, dragData);
|
||||
}
|
||||
SetIsDragSource(element, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отключает перетаскивание для элемента.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, для которого отключается перетаскивание.</param>
|
||||
public static void DisableDrag(this FrameworkElement element)
|
||||
{
|
||||
SetIsDragSource(element, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Включает возможность сброса для элемента.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, для которого включается возможность сброса.</param>
|
||||
public static void EnableDrop(this FrameworkElement element)
|
||||
{
|
||||
SetIsDropTarget(element, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отключает возможность сброса для элемента.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, для которого отключается возможность сброса.</param>
|
||||
public static void DisableDrop(this FrameworkElement element)
|
||||
{
|
||||
SetIsDropTarget(element, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Методы расширения для настройки перетаскивания в WinUI.
|
||||
/// </summary>
|
||||
public static class DragDropExtensions
|
||||
{
|
||||
#region Drag Source Extensions
|
||||
|
||||
/// <summary>
|
||||
/// Делает элемент источником перетаскивания с указанными данными.
|
||||
/// </summary>
|
||||
public static void MakeDragSource(this FrameworkElement element, object dragData)
|
||||
{
|
||||
Behaviors.WinUIDragSourceBehavior.SetDragData(element, dragData);
|
||||
Behaviors.WinUIDragSourceBehavior.SetIsEnabled(element, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Делает элемент источником перетаскивания с фабрикой данных.
|
||||
/// </summary>
|
||||
public static void MakeDragSource(this FrameworkElement element, Func<object> dataFactory)
|
||||
{
|
||||
element.MakeDragSource(dataFactory());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет возможность перетаскивания с элемента.
|
||||
/// </summary>
|
||||
public static void RemoveDragSource(this FrameworkElement element)
|
||||
{
|
||||
Behaviors.WinUIDragSourceBehavior.SetIsEnabled(element, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, является ли элемент источником перетаскивания.
|
||||
/// </summary>
|
||||
public static bool IsDragSource(this FrameworkElement element)
|
||||
{
|
||||
return Behaviors.WinUIDragSourceBehavior.GetIsEnabled(element);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает данные перетаскивания из элемента.
|
||||
/// </summary>
|
||||
public static object? GetDragData(this FrameworkElement element)
|
||||
{
|
||||
return Behaviors.WinUIDragSourceBehavior.GetDragData(element);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drop Target Extensions
|
||||
|
||||
/// <summary>
|
||||
/// Делает элемент целью сброса.
|
||||
/// </summary>
|
||||
public static void MakeDropTarget(this FrameworkElement element)
|
||||
{
|
||||
// Включаем AllowDrop для WinUI
|
||||
element.AllowDrop = true;
|
||||
element.SetValue(IsDropTargetProperty, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Делает элемент целью сброса с фильтром типов данных.
|
||||
/// </summary>
|
||||
public static void MakeDropTarget(this FrameworkElement element, params Type[] acceptedTypes)
|
||||
{
|
||||
element.SetValue(AcceptsDataTypesProperty, acceptedTypes);
|
||||
element.MakeDropTarget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет возможность сброса с элемента.
|
||||
/// </summary>
|
||||
public static void RemoveDropTarget(this FrameworkElement element)
|
||||
{
|
||||
element.AllowDrop = false;
|
||||
element.SetValue(IsDropTargetProperty, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, является ли элемент целью сброса.
|
||||
/// </summary>
|
||||
public static bool IsDropTarget(this FrameworkElement element)
|
||||
{
|
||||
return (bool)element.GetValue(IsDropTargetProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attached property для отметки цели сброса.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
return (Type[]?)element.GetValue(AcceptsDataTypesProperty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Style Extensions
|
||||
|
||||
/// <summary>
|
||||
/// Применяет стиль перетаскивания к элементу.
|
||||
/// </summary>
|
||||
public static void ApplyDragStyle(this Control control)
|
||||
{
|
||||
var style = Application.Current.Resources["DragEnabledStyle"] as Style;
|
||||
if (style != null)
|
||||
{
|
||||
control.Style = style;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback стиль
|
||||
var brush = Application.Current.Resources["SystemControlBackgroundAccentBrush"] as SolidColorBrush;
|
||||
if (brush != null)
|
||||
{
|
||||
control.Background = brush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Переключает визуальное состояние элемента для перетаскивания.
|
||||
/// </summary>
|
||||
public static void SetDragVisualState(this Control control, string stateName, bool useTransitions = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
VisualStateManager.GoToState(control, stateName, useTransitions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback для элементов без визуальных состояний
|
||||
switch (stateName)
|
||||
{
|
||||
case "Dragging":
|
||||
control.Opacity = 0.7;
|
||||
break;
|
||||
case "DragOver":
|
||||
control.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(50, 0, 120, 215));
|
||||
break;
|
||||
case "Normal":
|
||||
control.ClearValue(Control.OpacityProperty);
|
||||
control.ClearValue(Control.BackgroundProperty);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Advanced Configuration
|
||||
|
||||
/// <summary>
|
||||
/// Создает контейнер с поддержкой перетаскивания для элементов.
|
||||
/// </summary>
|
||||
public static Panel CreateDragDropContainer(
|
||||
Orientation orientation = Orientation.Vertical,
|
||||
double spacing = 8,
|
||||
bool enableReordering = true)
|
||||
{
|
||||
var container = new StackPanel
|
||||
{
|
||||
Orientation = orientation,
|
||||
Spacing = spacing
|
||||
};
|
||||
|
||||
if (enableReordering)
|
||||
{
|
||||
container.MakeDropTarget(typeof(FrameworkElement));
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Делает все дочерние элементы перетаскиваемыми.
|
||||
/// </summary>
|
||||
public static void MakeChildrenDraggable(this Panel container, Func<FrameworkElement, object> dataSelector)
|
||||
{
|
||||
foreach (var child in container.Children.OfType<FrameworkElement>())
|
||||
{
|
||||
var data = dataSelector(child);
|
||||
child.MakeDragSource(data);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
using Lattice.Themes;
|
||||
using Lattice.Themes.Core.Tokens;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Методы расширения для работы с темами в DragDrop.
|
||||
/// </summary>
|
||||
public static class ThemeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Применяет стиль перетаскивания, основанный на токенах темы.
|
||||
/// </summary>
|
||||
public static void ApplyLatticeDragStyle(this Control control)
|
||||
{
|
||||
var style = Application.Current.Resources["Lattice.DragDrop.DragSourceStyle"] as Style;
|
||||
if (style != null)
|
||||
{
|
||||
control.Style = style;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback на старый стиль
|
||||
control.ApplyDragStyle();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Применяет стиль цели сброса, основанный на токенах темы.
|
||||
/// </summary>
|
||||
public static void ApplyLatticeDropTargetStyle(this Control control)
|
||||
{
|
||||
var style = Application.Current.Resources["Lattice.DragDrop.DropTargetStyle"] as Style;
|
||||
if (style != null)
|
||||
{
|
||||
control.Style = style;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback на старый стиль
|
||||
control.ApplyDropTargetStyle();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Переключает визуальное состояние элемента с использованием токенов темы.
|
||||
/// </summary>
|
||||
public static void SetLatticeDragVisualState(this Control control, string stateName, bool useTransitions = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
VisualStateManager.GoToState(control, stateName, useTransitions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback на альтернативные методы с использованием токенов
|
||||
var themeManager = ThemeManager.Current;
|
||||
|
||||
switch (stateName)
|
||||
{
|
||||
case "Dragging":
|
||||
control.Opacity = themeManager.GetTokenValue<double?>(LatticeTokens.OpacityDrag) ?? 0.7;
|
||||
control.RenderTransform = new Microsoft.UI.Xaml.Media.ScaleTransform
|
||||
{
|
||||
ScaleX = 0.95,
|
||||
ScaleY = 0.95
|
||||
};
|
||||
break;
|
||||
|
||||
case "DragOver":
|
||||
var dragOverBrush = themeManager.GetTokenValue<Microsoft.UI.Xaml.Media.Brush>(
|
||||
LatticeTokens.BrushDragOverlay);
|
||||
control.Background = dragOverBrush ??
|
||||
Application.Current.Resources["Lattice.DragDrop.DragOverBackgroundBrush"] as Microsoft.UI.Xaml.Media.Brush;
|
||||
break;
|
||||
|
||||
case "Normal":
|
||||
control.ClearValue(Control.OpacityProperty);
|
||||
control.ClearValue(Control.RenderTransformProperty);
|
||||
control.ClearValue(Control.BackgroundProperty);
|
||||
control.ClearValue(Control.BorderBrushProperty);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение токена для использования в DragDrop.
|
||||
/// </summary>
|
||||
public static T? GetDragDropToken<T>(this Control control, string tokenKey) where T : class
|
||||
{
|
||||
var themeManager = ThemeManager.Current;
|
||||
return themeManager.GetTokenValue<T>(tokenKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает DragAdorner с использованием токенов темы.
|
||||
/// </summary>
|
||||
public static Controls.DragAdorner CreateLatticeDragAdorner(object dragData)
|
||||
{
|
||||
return new Controls.DragAdorner
|
||||
{
|
||||
DragData = dragData,
|
||||
Style = Application.Current.Resources["Lattice.DragDrop.DragSourceStyle"] as Style
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает DropPreviewAdorner с использованием токенов темы.
|
||||
/// </summary>
|
||||
public static Controls.DropPreviewAdorner CreateLatticeDropPreviewAdorner()
|
||||
{
|
||||
return new Controls.DropPreviewAdorner
|
||||
{
|
||||
Style = Application.Current.Resources[typeof(Controls.DropPreviewAdorner)] as Style
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
using Lattice.Core.DragDrop.Abstractions;
|
||||
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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис интеграции Drag & Drop с WinUI приложением.
|
||||
/// </summary>
|
||||
public sealed class WinUIDragDropIntegrationService : IDisposable
|
||||
{
|
||||
#region Вложенные типы
|
||||
|
||||
private sealed class DragSourceAdapter : IDragSource
|
||||
{
|
||||
private readonly FrameworkElement _element;
|
||||
private readonly Func<DragInfo> _dragInfoFactory;
|
||||
private readonly IDragDropService _dragDropService;
|
||||
private Point _dragStartPosition;
|
||||
private bool _isDragging;
|
||||
private bool _disposed;
|
||||
|
||||
public DragSourceAdapter(
|
||||
FrameworkElement element,
|
||||
Func<DragInfo> dragInfoFactory,
|
||||
IDragDropService dragDropService)
|
||||
{
|
||||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||||
_dragInfoFactory = dragInfoFactory ?? throw new ArgumentNullException(nameof(dragInfoFactory));
|
||||
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
|
||||
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
private void SubscribeToEvents()
|
||||
{
|
||||
_element.PointerPressed += OnPointerPressed;
|
||||
_element.PointerMoved += OnPointerMoved;
|
||||
_element.PointerReleased += OnPointerReleased;
|
||||
_element.PointerCanceled += OnPointerCanceled;
|
||||
_element.PointerCaptureLost += OnPointerCaptureLost;
|
||||
}
|
||||
|
||||
private void UnsubscribeFromEvents()
|
||||
{
|
||||
_element.PointerPressed -= OnPointerPressed;
|
||||
_element.PointerMoved -= OnPointerMoved;
|
||||
_element.PointerReleased -= OnPointerReleased;
|
||||
_element.PointerCanceled -= OnPointerCanceled;
|
||||
_element.PointerCaptureLost -= OnPointerCaptureLost;
|
||||
}
|
||||
|
||||
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (_isDragging || _disposed) return;
|
||||
|
||||
var point = e.GetCurrentPoint(_element);
|
||||
_dragStartPosition = new Point(point.Position.X, point.Position.Y);
|
||||
}
|
||||
|
||||
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (_isDragging || _disposed) return;
|
||||
|
||||
var currentPoint = e.GetCurrentPoint(_element);
|
||||
var currentPosition = new Point(currentPoint.Position.X, currentPoint.Position.Y);
|
||||
|
||||
var distance = CalculateDistance(_dragStartPosition, currentPosition);
|
||||
if (distance > _dragDropService.DragStartThreshold)
|
||||
{
|
||||
StartDragOperation(currentPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartDragOperation(Point position)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dragInfo = _dragInfoFactory();
|
||||
|
||||
// Обновляем позицию в dragInfo
|
||||
var screenPosition = GetScreenPosition(position);
|
||||
dragInfo = new DragInfo(
|
||||
dragInfo.Data,
|
||||
dragInfo.AllowedEffects,
|
||||
screenPosition,
|
||||
dragInfo.Source
|
||||
);
|
||||
|
||||
_isDragging = await _dragDropService.StartDragAsync(this, screenPosition);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error starting drag: {ex.Message}");
|
||||
_isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
private Point GetScreenPosition(Point relativePosition)
|
||||
{
|
||||
var transform = _element.TransformToVisual(null);
|
||||
var point = transform.TransformPoint(new Windows.Foundation.Point(relativePosition.X, relativePosition.Y));
|
||||
return new Point(point.X, point.Y);
|
||||
}
|
||||
|
||||
private double CalculateDistance(Point p1, Point p2)
|
||||
{
|
||||
var dx = p2.X - p1.X;
|
||||
var dy = p2.Y - p1.Y;
|
||||
return Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
private async void OnPointerReleased(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (!_isDragging || _disposed) return;
|
||||
|
||||
await _dragDropService.EndDragAsync();
|
||||
_isDragging = false;
|
||||
}
|
||||
|
||||
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (!_isDragging || _disposed) return;
|
||||
|
||||
_dragDropService.CancelDragAsync();
|
||||
_isDragging = false;
|
||||
}
|
||||
|
||||
private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (!_isDragging || _disposed) return;
|
||||
|
||||
_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();
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Поля
|
||||
|
||||
private readonly IDragDropService _dragDropService;
|
||||
private readonly IDragVisualProvider _dragVisualProvider;
|
||||
private readonly IDragDropHost _dragDropHost;
|
||||
private readonly Dictionary<FrameworkElement, DragSourceAdapter> _dragSources = new();
|
||||
private readonly Dictionary<FrameworkElement, WinUIDropTargetBehavior> _dropTargets = new();
|
||||
private bool _disposed;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Конструктор
|
||||
|
||||
public WinUIDragDropIntegrationService(
|
||||
IDragDropService dragDropService,
|
||||
IDragVisualProvider dragVisualProvider,
|
||||
IDragDropHost dragDropHost)
|
||||
{
|
||||
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
|
||||
_dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider));
|
||||
_dragDropHost = dragDropHost ?? throw new ArgumentNullException(nameof(dragDropHost));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Публичные методы
|
||||
|
||||
/// <summary>
|
||||
/// Регистрирует элемент как источник перетаскивания.
|
||||
/// </summary>
|
||||
public void RegisterDragSource(FrameworkElement element, Func<DragInfo> dragInfoFactory)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
|
||||
ArgumentNullException.ThrowIfNull(element);
|
||||
ArgumentNullException.ThrowIfNull(dragInfoFactory);
|
||||
|
||||
if (_dragSources.ContainsKey(element)) return;
|
||||
|
||||
var adapter = new DragSourceAdapter(element, dragInfoFactory, _dragDropService);
|
||||
_dragSources[element] = adapter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Регистрирует элемент как источник перетаскивания с данными.
|
||||
/// </summary>
|
||||
public void RegisterDragSource(FrameworkElement element, object dragData)
|
||||
{
|
||||
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(FrameworkElement element)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
|
||||
ArgumentNullException.ThrowIfNull(element);
|
||||
|
||||
if (_dropTargets.ContainsKey(element)) return;
|
||||
|
||||
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(FrameworkElement element)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
|
||||
|
||||
if (_dragSources.Remove(element, out var adapter))
|
||||
{
|
||||
adapter.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет регистрацию элемента как цели.
|
||||
/// </summary>
|
||||
public void UnregisterDropTarget(FrameworkElement element)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
|
||||
|
||||
if (_dropTargets.Remove(element, out var behavior))
|
||||
{
|
||||
behavior.Detach();
|
||||
element.AllowDrop = false;
|
||||
element.DragEnter -= OnDragEnter;
|
||||
element.DragOver -= OnDragOver;
|
||||
element.DragLeave -= OnDragLeave;
|
||||
element.Drop -= OnDrop;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
foreach (var adapter in _dragSources.Values)
|
||||
{
|
||||
adapter.Dispose();
|
||||
}
|
||||
_dragSources.Clear();
|
||||
|
||||
foreach (var behavior in _dropTargets.Values)
|
||||
{
|
||||
behavior.Detach();
|
||||
}
|
||||
_dropTargets.Clear();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Методы расширения для преобразования координат.
|
||||
/// </summary>
|
||||
internal static class PointExtensions
|
||||
{
|
||||
public static Point ToCorePoint(this Windows.Foundation.Point point)
|
||||
{
|
||||
return new Point(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
using Lattice.UI.DragDrop.WinUI.Controls;
|
||||
using Lattice.UI.DragDrop.WinUI.Extensions;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис для настройки перетаскивания с поддержкой различных сценариев.
|
||||
/// </summary>
|
||||
public class DragDropConfigurationService
|
||||
{
|
||||
private readonly Dictionary<UIElement, Configuration> _configurations = new();
|
||||
private readonly DragDropOverlay _overlay;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр класса <see cref="DragDropConfigurationService"/>.
|
||||
/// </summary>
|
||||
public DragDropConfigurationService()
|
||||
{
|
||||
_overlay = new DragDropOverlay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Настройка элемента как источника перетаскивания.
|
||||
/// </summary>
|
||||
public void ConfigureAsDragSource(UIElement element, DragSourceConfiguration config)
|
||||
{
|
||||
element.MakeDragSource(config.Data);
|
||||
|
||||
if (config.AllowedEffects.HasValue)
|
||||
{
|
||||
element.SetAllowedEffects(config.AllowedEffects.Value);
|
||||
}
|
||||
|
||||
if (config.VisualOffset.HasValue)
|
||||
{
|
||||
element.SetDragVisualOffset(config.VisualOffset.Value.X, config.VisualOffset.Value.Y);
|
||||
}
|
||||
|
||||
_configurations[element] = new Configuration { DragSourceConfig = config };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Настройка элемента как цели сброса.
|
||||
/// </summary>
|
||||
public void ConfigureAsDropTarget(UIElement element, DropTargetConfiguration config)
|
||||
{
|
||||
element.MakeDropTarget(
|
||||
config.AcceptedTypes,
|
||||
config.Handler,
|
||||
config.ShowVisualFeedback,
|
||||
config.FeedbackStyle);
|
||||
|
||||
if (!_configurations.ContainsKey(element))
|
||||
{
|
||||
_configurations[element] = new Configuration();
|
||||
}
|
||||
|
||||
_configurations[element].DropTargetConfig = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Настройка элемента для переупорядочивания в контейнере.
|
||||
/// </summary>
|
||||
public void ConfigureForReorder(UIElement element, Panel container, ReorderConfiguration config)
|
||||
{
|
||||
ConfigureAsDragSource(element, new DragSourceConfiguration
|
||||
{
|
||||
Data = element,
|
||||
AllowedEffects = Core.DragDrop.Enums.DragDropEffects.Move,
|
||||
VisualOffset = new Windows.Foundation.Point(-20, -20)
|
||||
});
|
||||
|
||||
ConfigureAsDropTarget(container, new DropTargetConfiguration
|
||||
{
|
||||
AcceptedTypes = new[] { typeof(UIElement) },
|
||||
ShowVisualFeedback = config.ShowVisualFeedback,
|
||||
FeedbackStyle = config.FeedbackStyle
|
||||
});
|
||||
|
||||
// Настраиваем логику переупорядочивания
|
||||
container.Drop += (sender, e) => HandleReorderDrop(sender as Panel, element, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Подключает оверлей к указанному контейнеру.
|
||||
/// </summary>
|
||||
public void AttachOverlayTo(Panel container)
|
||||
{
|
||||
if (container.Children.Contains(_overlay))
|
||||
return;
|
||||
|
||||
container.Children.Add(_overlay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отключает все настройки перетаскивания для элемента.
|
||||
/// </summary>
|
||||
public void DisableDragDrop(UIElement element)
|
||||
{
|
||||
element.RemoveDragSource();
|
||||
element.RemoveDropTarget();
|
||||
|
||||
_configurations.Remove(element);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает все настройки.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var element in _configurations.Keys)
|
||||
{
|
||||
element.RemoveDragSource();
|
||||
element.RemoveDropTarget();
|
||||
}
|
||||
|
||||
_configurations.Clear();
|
||||
}
|
||||
|
||||
private void HandleReorderDrop(Panel? container, UIElement draggedElement, Microsoft.UI.Xaml.DragEventArgs e)
|
||||
{
|
||||
if (container == null) return;
|
||||
|
||||
var position = e.GetPosition(container);
|
||||
int insertIndex = CalculateInsertIndex(container, position);
|
||||
|
||||
if (insertIndex >= 0 && insertIndex < container.Children.Count)
|
||||
{
|
||||
container.Children.Remove(draggedElement);
|
||||
container.Children.Insert(insertIndex, draggedElement);
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculateInsertIndex(Panel container, Windows.Foundation.Point position)
|
||||
{
|
||||
for (int i = 0; i < container.Children.Count; i++)
|
||||
{
|
||||
var child = container.Children[i];
|
||||
if (child is FrameworkElement element)
|
||||
{
|
||||
var childBounds = new Windows.Foundation.Rect(
|
||||
Canvas.GetLeft(element),
|
||||
Canvas.GetTop(element),
|
||||
element.ActualWidth,
|
||||
element.ActualHeight);
|
||||
|
||||
if (position.Y < childBounds.Y + childBounds.Height / 2)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return container.Children.Count;
|
||||
}
|
||||
|
||||
private class Configuration
|
||||
{
|
||||
public DragSourceConfiguration? DragSourceConfig { get; set; }
|
||||
public DropTargetConfiguration? DropTargetConfig { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конфигурация источника перетаскивания.
|
||||
/// </summary>
|
||||
public class DragSourceConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Данные для перетаскивания.
|
||||
/// </summary>
|
||||
public required object Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Разрешенные эффекты.
|
||||
/// </summary>
|
||||
public Core.DragDrop.Enums.DragDropEffects? AllowedEffects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Смещение визуального элемента.
|
||||
/// </summary>
|
||||
public Windows.Foundation.Point? VisualOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Пользовательский обработчик.
|
||||
/// </summary>
|
||||
public Action<UIElement>? OnDragStarted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Пользовательский обработчик завершения.
|
||||
/// </summary>
|
||||
public Action<UIElement, Core.DragDrop.Enums.DragDropEffects>? OnDragCompleted { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конфигурация цели сброса.
|
||||
/// </summary>
|
||||
public class DropTargetConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Принимаемые типы данных.
|
||||
/// </summary>
|
||||
public Type[]? AcceptedTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик сброса.
|
||||
/// </summary>
|
||||
public Core.DragDrop.Abstractions.IDropTarget? Handler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Показывать визуальную обратную связь.
|
||||
/// </summary>
|
||||
public bool ShowVisualFeedback { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Стиль визуальной обратной связи.
|
||||
/// </summary>
|
||||
public Style? FeedbackStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Пользовательский обработчик валидации.
|
||||
/// </summary>
|
||||
public Func<object, bool>? CustomValidation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Пользовательский обработчик сброса.
|
||||
/// </summary>
|
||||
public Action<object>? OnDrop { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конфигурация переупорядочивания.
|
||||
/// </summary>
|
||||
public class ReorderConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Показывать визуальную обратную связь.
|
||||
/// </summary>
|
||||
public bool ShowVisualFeedback { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Стиль визуальной обратной связи.
|
||||
/// </summary>
|
||||
public Style? FeedbackStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Включать анимацию при переупорядочивании.
|
||||
/// </summary>
|
||||
public bool EnableAnimation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Порог для начала перетаскивания (в пикселях).
|
||||
/// </summary>
|
||||
public double DragThreshold { get; set; } = 5.0;
|
||||
}
|
||||
@@ -7,14 +7,53 @@ using System;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI.Services;
|
||||
|
||||
public class WinUIDragDropHost : IDragDropHost
|
||||
/// <summary>
|
||||
/// Хост для управления визуальными элементами перетаскивания в окне WinUI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот класс отвечает за отображение и управление визуальными элементами
|
||||
/// во время операций перетаскивания, включая:
|
||||
/// - Drag-визуализацию (элемент, следующий за курсором)
|
||||
/// - Drop-превью (подсветка областей сброса)
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Хост создает оверлейный слой поверх всего содержимого окна для корректного
|
||||
/// отображения визуальных элементов поверх других UI-компонентов.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class WinUIDragDropHost : IDragDropHost, IDisposable
|
||||
{
|
||||
private readonly DragDropOverlay _overlay;
|
||||
private readonly Window _window;
|
||||
private DragDropOverlay? _overlay;
|
||||
private Window? _window;
|
||||
private bool _disposed;
|
||||
|
||||
public WinUIDragDropHost(Window window)
|
||||
/// <summary>
|
||||
/// Инициализирует хост для работы с указанным окном.
|
||||
/// </summary>
|
||||
/// <param name="window">Окно, в котором будет работать перетаскивание.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="window"/> равен null.
|
||||
/// </exception>
|
||||
/// <exception cref="ObjectDisposedException">
|
||||
/// Выбрасывается, если хост уже был удален.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот метод создает оверлейный слой и добавляет его в визуальное дерево окна.
|
||||
/// Если содержимое окна не является <see cref="Panel"/>, создается контейнер <see cref="Grid"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Метод должен быть вызван один раз перед использованием других методов хоста.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public void Initialize(Window window)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropHost));
|
||||
|
||||
_window = window ?? throw new ArgumentNullException(nameof(window));
|
||||
|
||||
// Создаем оверлей
|
||||
_overlay = new DragDropOverlay();
|
||||
|
||||
// Добавляем оверлей в окно
|
||||
@@ -22,33 +61,58 @@ public class WinUIDragDropHost : IDragDropHost
|
||||
{
|
||||
panel.Children.Add(_overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Если контент не Panel, создаем Grid
|
||||
var grid = new Grid();
|
||||
grid.Children.Add(_window.Content as UIElement ?? new Grid());
|
||||
grid.Children.Add(_overlay);
|
||||
_window.Content = grid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отображает визуальное представление перетаскиваемого элемента.
|
||||
/// </summary>
|
||||
/// <param name="dragVisual">Визуальный элемент для отображения.</param>
|
||||
/// <param name="position">Позиция отображения в координатах экрана.</param>
|
||||
/// <remarks>
|
||||
/// Визуальный элемент будет отображен на оверлейном слое в указанной позиции
|
||||
/// и будет следовать за курсором при обновлении через <see cref="UpdateDragVisualPosition"/>.
|
||||
/// </remarks>
|
||||
public void ShowDragVisual(object dragVisual, Point position)
|
||||
{
|
||||
if (_overlay == null || _disposed) return;
|
||||
|
||||
if (dragVisual is UIElement element)
|
||||
{
|
||||
_overlay.ShowDragVisual(element, position.X, position.Y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UpdateDragVisualPosition(object dragVisual, Point position)
|
||||
{
|
||||
if (_overlay == null || _disposed) return;
|
||||
|
||||
if (dragVisual is UIElement element)
|
||||
{
|
||||
_overlay.UpdateDragVisualPosition(element, position.X, position.Y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void HideDragVisual(object dragVisual)
|
||||
{
|
||||
if (_overlay == null || _disposed) return;
|
||||
|
||||
if (dragVisual is UIElement element)
|
||||
{
|
||||
_overlay.HideDragVisual(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Скрываем все, если передан null
|
||||
// Скрываем все визуальные элементы
|
||||
var current = _overlay.GetCurrentDragVisual();
|
||||
if (current != null)
|
||||
{
|
||||
@@ -57,16 +121,40 @@ public class WinUIDragDropHost : IDragDropHost
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ShowDropAdorner(IDropVisualAdorner adorner)
|
||||
{
|
||||
if (_overlay == null || _disposed) return;
|
||||
|
||||
if (adorner is DropPreviewAdorner dropAdorner)
|
||||
{
|
||||
// TODO: Показываем превью сброса
|
||||
// Для WinUI пока просто игнорируем
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void HideDropAdorner(IDropVisualAdorner adorner)
|
||||
{
|
||||
if (_overlay == null || _disposed) return;
|
||||
|
||||
_overlay.HideAllDropPreviews();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
if (_overlay != null && _window?.Content is Panel panel)
|
||||
{
|
||||
panel.Children.Remove(_overlay);
|
||||
_overlay.ClearAllVisuals();
|
||||
}
|
||||
|
||||
_overlay = null;
|
||||
_window = null;
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
using Lattice.Core.DragDrop.Services;
|
||||
using Lattice.UI.DragDrop.Abstractions;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис интеграции Drag & Drop с WinUI приложением.
|
||||
/// </summary>
|
||||
public class WinUIDragDropIntegrationService : IDisposable
|
||||
{
|
||||
private readonly IDragDropService _dragDropService;
|
||||
private readonly IDragVisualProvider _dragVisualProvider;
|
||||
private readonly Canvas _overlayCanvas;
|
||||
private readonly Window _window;
|
||||
private object? _currentDragVisual;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр класса <see cref="WinUIDragDropIntegrationService"/>.
|
||||
/// </summary>
|
||||
public WinUIDragDropIntegrationService(
|
||||
Window window,
|
||||
IDragDropService dragDropService,
|
||||
IDragVisualProvider dragVisualProvider)
|
||||
{
|
||||
_window = window ?? throw new ArgumentNullException(nameof(window));
|
||||
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
|
||||
_dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider));
|
||||
|
||||
// Создаем оверлейный Canvas для визуальных элементов
|
||||
_overlayCanvas = new Canvas
|
||||
{
|
||||
IsHitTestVisible = false,
|
||||
Background = null
|
||||
};
|
||||
|
||||
// Подписываемся на события перетаскивания
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Встраивает оверлей в указанный контейнер.
|
||||
/// </summary>
|
||||
public void AttachToContainer(Panel container)
|
||||
{
|
||||
if (container == null)
|
||||
throw new ArgumentNullException(nameof(container));
|
||||
|
||||
// Убеждаемся, что оверлей находится поверх всех элементов
|
||||
Canvas.SetZIndex(_overlayCanvas, int.MaxValue);
|
||||
container.Children.Add(_overlayCanvas);
|
||||
}
|
||||
|
||||
private void SubscribeToEvents()
|
||||
{
|
||||
_dragDropService.DragStarted += OnDragStarted;
|
||||
_dragDropService.DragUpdated += OnDragUpdated;
|
||||
_dragDropService.DragCompleted += OnDragCompleted;
|
||||
_dragDropService.DragCancelled += OnDragCancelled;
|
||||
}
|
||||
|
||||
private void UnsubscribeFromEvents()
|
||||
{
|
||||
_dragDropService.DragStarted -= OnDragStarted;
|
||||
_dragDropService.DragUpdated -= OnDragUpdated;
|
||||
_dragDropService.DragCompleted -= OnDragCompleted;
|
||||
_dragDropService.DragCancelled -= OnDragCancelled;
|
||||
}
|
||||
|
||||
private void OnDragStarted(object? sender, DragStartedEventArgs e)
|
||||
{
|
||||
// Создаем визуальное представление
|
||||
_currentDragVisual = _dragVisualProvider.CreateDragVisual(
|
||||
e.DragInfo,
|
||||
e.StartPosition);
|
||||
|
||||
// Добавляем на оверлей
|
||||
if (_currentDragVisual is UIElement element)
|
||||
{
|
||||
_overlayCanvas.Children.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDragUpdated(object? sender, DragUpdatedEventArgs e)
|
||||
{
|
||||
if (_currentDragVisual != null)
|
||||
{
|
||||
_dragVisualProvider.UpdateDragVisualPosition(_currentDragVisual, e.Position);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDragCompleted(object? sender, DragCompletedEventArgs e)
|
||||
{
|
||||
CleanupDragVisual();
|
||||
}
|
||||
|
||||
private void OnDragCancelled(object? sender, DragCancelledEventArgs e)
|
||||
{
|
||||
CleanupDragVisual();
|
||||
}
|
||||
|
||||
private void CleanupDragVisual()
|
||||
{
|
||||
if (_currentDragVisual != null)
|
||||
{
|
||||
_dragVisualProvider.ReleaseDragVisual(_currentDragVisual);
|
||||
_currentDragVisual = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
UnsubscribeFromEvents();
|
||||
CleanupDragVisual();
|
||||
|
||||
if (_overlayCanvas.Parent is Panel parent)
|
||||
{
|
||||
parent.Children.Remove(_overlayCanvas);
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
274
Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs
Normal file
274
Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
using Lattice.Core.DragDrop.Services;
|
||||
using Lattice.Core.Geometry;
|
||||
using Lattice.UI.DragDrop.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Центральный менеджер для управления операциями drag-and-drop в WinUI приложении.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот класс реализует шаблон Singleton и предоставляет единую точку для
|
||||
/// настройки и управления всеми операциями перетаскивания в приложении.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Менеджер отвечает за:
|
||||
/// - Инициализацию системы перетаскивания
|
||||
/// - Регистрацию и отслеживание источников и целей перетаскивания
|
||||
/// - Создание и управление визуальной обратной связью
|
||||
/// - Координацию между поведением элементов и базовым сервисом перетаскивания
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Для использования необходимо вызвать <see cref="Initialize"/> при запуске приложения
|
||||
/// и использовать attached properties или методы расширения для настройки элементов.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class WinUIDragDropManager : IDisposable
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
private static WinUIDragDropManager? _instance;
|
||||
private static readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Получает единственный экземпляр менеджера.
|
||||
/// </summary>
|
||||
public static WinUIDragDropManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_instance ??= new WinUIDragDropManager();
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Поля
|
||||
|
||||
private readonly DragDropService _dragDropService;
|
||||
private readonly WinUIDragDropHost _host;
|
||||
private readonly Dictionary<FrameworkElement, Behaviors.WinUIDragSourceBehavior> _dragSources = new();
|
||||
private readonly Dictionary<FrameworkElement, Behaviors.WinUIDropTargetBehavior> _dropTargets = new();
|
||||
private DragAdorner? _currentDragVisual;
|
||||
private bool _disposed;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Свойства
|
||||
|
||||
/// <summary>
|
||||
/// Получает основной сервис перетаскивания.
|
||||
/// </summary>
|
||||
public IDragDropService DragDropService => _dragDropService;
|
||||
|
||||
/// <summary>
|
||||
/// Получает или задает смещение визуального элемента перетаскивания относительно курсора.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Точка, определяющая смещение по осям X и Y. Значение по умолчанию: (-20, -20).
|
||||
/// Отрицательные значения поднимают визуальный элемент вверх и влево относительно курсора.
|
||||
/// </value>
|
||||
public Point DragVisualOffset { get; set; } = new Point(-20, -20);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Конструктор
|
||||
|
||||
private WinUIDragDropManager()
|
||||
{
|
||||
_dragDropService = new DragDropService();
|
||||
_host = new WinUIDragDropHost();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Публичные методы
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует систему перетаскивания для указанного окна.
|
||||
/// </summary>
|
||||
/// <param name="window">Основное окно приложения, в котором будет работать перетаскивание.</param>
|
||||
/// <exception cref="ObjectDisposedException">
|
||||
/// Выбрасывается, если менеджер был удален.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Этот метод должен быть вызван один раз при запуске приложения, обычно в методе
|
||||
/// <see cref="Application.OnLaunched"/>.
|
||||
/// </remarks>
|
||||
public void Initialize(Window window)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropManager));
|
||||
|
||||
_host.Initialize(window);
|
||||
|
||||
// Подписываемся на события
|
||||
_dragDropService.DragStarted += OnDragStarted;
|
||||
_dragDropService.DragUpdated += OnDragUpdated;
|
||||
_dragDropService.DragCompleted += OnDragCompleted;
|
||||
_dragDropService.DragCancelled += OnDragCancelled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Делает указанный элемент источником перетаскивания.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, который станет источником перетаскивания.</param>
|
||||
/// <param name="dragData">Данные, которые будут перетаскиваться. Если не указано, используются
|
||||
/// DataContext или Tag элемента.</param>
|
||||
/// <remarks>
|
||||
/// Если элемент уже зарегистрирован как источник перетаскивания, метод не выполняет действий.
|
||||
/// </remarks>
|
||||
public void MakeDragSource(FrameworkElement element, object? dragData = null)
|
||||
{
|
||||
if (_disposed || _dragSources.ContainsKey(element)) return;
|
||||
|
||||
var behavior = new Behaviors.WinUIDragSourceBehavior(_dragDropService, _host);
|
||||
behavior.Attach(element, dragData);
|
||||
_dragSources[element] = behavior;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Делает указанный элемент целью сброса.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, который станет целью сброса.</param>
|
||||
/// <remarks>
|
||||
/// Если элемент уже зарегистрирован как цель сброса, метод не выполняет действий.
|
||||
/// </remarks>
|
||||
public void MakeDropTarget(FrameworkElement element)
|
||||
{
|
||||
if (_disposed || _dropTargets.ContainsKey(element)) return;
|
||||
|
||||
var behavior = new Behaviors.WinUIDropTargetBehavior(_dragDropService, _host);
|
||||
behavior.Attach(element);
|
||||
_dropTargets[element] = behavior;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет возможность перетаскивания.
|
||||
/// </summary>
|
||||
public void RemoveDragSource(FrameworkElement element)
|
||||
{
|
||||
if (_dragSources.Remove(element, out var behavior))
|
||||
{
|
||||
behavior.Detach();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет возможность сброса.
|
||||
/// </summary>
|
||||
public void RemoveDropTarget(FrameworkElement element)
|
||||
{
|
||||
if (_dropTargets.Remove(element, out var behavior))
|
||||
{
|
||||
behavior.Detach();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает все регистрации.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var behavior in _dragSources.Values)
|
||||
{
|
||||
behavior.Detach();
|
||||
}
|
||||
_dragSources.Clear();
|
||||
|
||||
foreach (var behavior in _dropTargets.Values)
|
||||
{
|
||||
behavior.Detach();
|
||||
}
|
||||
_dropTargets.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Обработчики событий
|
||||
|
||||
private void OnDragStarted(object? sender, DragStartedEventArgs e)
|
||||
{
|
||||
// Создаем визуальное представление
|
||||
_currentDragVisual = new DragAdorner
|
||||
{
|
||||
DragData = e.DragInfo.Data,
|
||||
Opacity = 0.8
|
||||
};
|
||||
|
||||
var position = new Point(
|
||||
e.Position.X + DragVisualOffset.X,
|
||||
e.Position.Y + DragVisualOffset.Y
|
||||
);
|
||||
|
||||
_currentDragVisual.UpdatePosition(position);
|
||||
_host.ShowDragVisual(_currentDragVisual, position);
|
||||
}
|
||||
|
||||
private void OnDragUpdated(object? sender, DragUpdatedEventArgs e)
|
||||
{
|
||||
if (_currentDragVisual != null)
|
||||
{
|
||||
var position = new Point(
|
||||
e.Position.X + DragVisualOffset.X,
|
||||
e.Position.Y + DragVisualOffset.Y
|
||||
);
|
||||
|
||||
_currentDragVisual.UpdatePosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDragCompleted(object? sender, DragCompletedEventArgs e)
|
||||
{
|
||||
CleanupDragVisual();
|
||||
}
|
||||
|
||||
private void OnDragCancelled(object? sender, DragCancelledEventArgs e)
|
||||
{
|
||||
CleanupDragVisual();
|
||||
}
|
||||
|
||||
private void CleanupDragVisual()
|
||||
{
|
||||
if (_currentDragVisual != null)
|
||||
{
|
||||
_currentDragVisual.Hide();
|
||||
_currentDragVisual = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
Clear();
|
||||
|
||||
// Отписываемся от событий
|
||||
_dragDropService.DragStarted -= OnDragStarted;
|
||||
_dragDropService.DragUpdated -= OnDragUpdated;
|
||||
_dragDropService.DragCompleted -= OnDragCompleted;
|
||||
_dragDropService.DragCancelled -= OnDragCancelled;
|
||||
|
||||
_dragDropService.Dispose();
|
||||
_host.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
<!--
|
||||
DragAdorner.xaml - Стили для визуального элемента перетаскивания.
|
||||
Содержит шаблон элемента DragAdorner с поддержкой теней, скруглений и анимаций.
|
||||
-->
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
<!--
|
||||
DragDropStyles.xaml - Базовые стили для элементов с поддержкой перетаскивания.
|
||||
Содержит стили для источников перетаскивания и целей сброса с различными
|
||||
визуальными состояниями (Normal, Dragging, DragOver и т.д.).
|
||||
-->
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!--
|
||||
DropPreviewAdorner.xaml - Ñòèëè äëÿ ýëåìåíòà ïîäñâåòêè îáëàñòè ñáðîñà.
|
||||
Ñîäåðæèò øàáëîí ñ íàñòðàèâàåìûìè öâåòàìè è òîëùèíîé ãðàíèöû.
|
||||
-->
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!--
|
||||
Generic.xaml - Основной словарь ресурсов для компонентов drag-and-drop.
|
||||
Объединяет все стили и предоставляет алиасы для обратной совместимости.
|
||||
-->
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
Reference in New Issue
Block a user