Доработара WinUI реализация.

This commit is contained in:
2026-01-25 05:36:28 +03:00
parent bbb20edb03
commit 6ad7b5dcdb
20 changed files with 1089 additions and 1801 deletions

View 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
}