Доработара WinUI реализация.
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user