Files
Lattice/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs

274 lines
9.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}