diff --git a/Lattice.IDE/Controls/EditorView.xaml.cs b/Lattice.IDE/Controls/EditorView.xaml.cs index 1f45a18..6fb6fd7 100644 --- a/Lattice.IDE/Controls/EditorView.xaml.cs +++ b/Lattice.IDE/Controls/EditorView.xaml.cs @@ -1,17 +1,4 @@ -using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Data; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Navigation; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; -using Windows.Foundation.Collections; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. diff --git a/Lattice.IDE/Controls/SolutionExplorerView.xaml.cs b/Lattice.IDE/Controls/SolutionExplorerView.xaml.cs index ed3a3e8..21dd5c8 100644 --- a/Lattice.IDE/Controls/SolutionExplorerView.xaml.cs +++ b/Lattice.IDE/Controls/SolutionExplorerView.xaml.cs @@ -1,17 +1,4 @@ -using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Data; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Navigation; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; -using Windows.Foundation.Collections; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. diff --git a/Lattice.UI.Docking.WinUI/Services/WinUIDragDropService.cs b/Lattice.UI.Docking.WinUI/Services/WinUIDragDropService.cs index 225a4c9..3315100 100644 --- a/Lattice.UI.Docking.WinUI/Services/WinUIDragDropService.cs +++ b/Lattice.UI.Docking.WinUI/Services/WinUIDragDropService.cs @@ -1,5 +1,4 @@ -using Lattice.Core.DragDrop.Services; -using Lattice.Core.Geometry; +using Lattice.Core.Geometry; using Lattice.UI.Docking.Abstractions; using Lattice.UI.Docking.Models; using Lattice.UI.Docking.Services; diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs index d1f97e6..6a35e8a 100644 --- a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs +++ b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs @@ -1,7 +1,8 @@ -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Models; +using Lattice.Core.DragDrop.Models; using Lattice.Core.DragDrop.Services; using Lattice.Core.Geometry; +using Lattice.UI.DragDrop.Abstractions; +using Lattice.UI.DragDrop.Behaviors; using Lattice.UI.DragDrop.WinUI.Services; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; @@ -13,29 +14,54 @@ namespace Lattice.UI.DragDrop.WinUI.Behaviors; /// /// Реализация поведения источника перетаскивания для элементов WinUI. +/// Наследуется от для использования +/// общей логики управления операциями перетаскивания и интеграции с системой . /// /// /// -/// Этот класс обрабатывает события мыши/тач и преобразует их в операции перетаскивания. -/// Он реализует интерфейс для интеграции с системой перетаскивания. +/// Этот класс предоставляет конкретную реализацию для платформы WinUI, обрабатывая события +/// указателя (мышь, тач, перо) и преобразуя их в операции перетаскивания через центральный +/// сервис . /// /// -/// Поведение автоматически отслеживает начало перемещения мыши, проверяет порог -/// перетаскивания и инициирует операцию через . +/// Основные функции: +/// +/// Обработка событий PointerPressed, PointerMoved, PointerReleased +/// Автоматическое отслеживание порога начала перетаскивания +/// Создание информации о перетаскивании на основе данных элемента +/// Интеграция с визуальной обратной связью через +/// Преобразование координат между локальной системой элемента и экранными координатами +/// /// +/// +/// Для использования необходимо: +/// +/// Создать экземпляр поведения через фабрику +/// Прикрепить к элементу с помощью метода +/// Указать данные для перетаскивания (опционально, по умолчанию используется DataContext) +/// +/// +/// +/// +/// // Создание поведения +/// var behavior = WinUIDragDropFactory.CreateDragSourceBehavior(dragDropService, host); +/// +/// // Прикрепление к элементу +/// behavior.Attach(myElement, myData); +/// +/// // Или через attached properties +/// <Border x:Name="DragElement" +/// local:DragDropProperties.IsDragSource="True" +/// local:DragDropProperties.DragData="{Binding MyData}" /> +/// +/// /// -public sealed class WinUIDragSourceBehavior : IDragSource +public sealed class WinUIDragSourceBehavior : DragSourceBehaviorBase { #region Поля - private readonly IDragDropService _dragDropService; - private readonly WinUIDragDropHost _host; - - private FrameworkElement? _element; + private readonly IDragDropHost _host; private object? _dragData; - private Point _dragStartPosition; - private bool _isDragging; - private CancellationTokenSource? _cancellationTokenSource; #endregion @@ -44,14 +70,27 @@ public sealed class WinUIDragSourceBehavior : IDragSource /// /// Инициализирует новый экземпляр класса . /// - /// Сервис для управления операциями перетаскивания. - /// Хост для отображения визуальных элементов. + /// + /// Сервис управления операциями перетаскивания. Используется для координации + /// между источниками и целями перетаскивания. + /// + /// + /// Хост для управления визуальными элементами перетаскивания. Обеспечивает + /// отображение визуальной обратной связи во время операции. + /// /// - /// Выбрасывается, если любой из параметров равен null. + /// Выбрасывается, если или + /// равны null. /// - public WinUIDragSourceBehavior(IDragDropService dragDropService, WinUIDragDropHost host) + /// + /// Конструктор инициализирует базовый класс + /// и сохраняет ссылки на необходимые сервисы для последующего использования. + /// + public WinUIDragSourceBehavior( + Core.DragDrop.Services.IDragDropService dragDropService, + WinUIDragDropHost host) + : base(dragDropService) { - _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); _host = host ?? throw new ArgumentNullException(nameof(host)); } @@ -60,207 +99,202 @@ public sealed class WinUIDragSourceBehavior : IDragSource #region Публичные методы /// - /// Прикрепляет поведение к указанному элементу. + /// Прикрепляет поведение к указанному элементу WinUI. /// - /// Элемент, к которому прикрепляется поведение. - /// Данные для перетаскивания. Если не указано, используются - /// DataContext или Tag элемента. + /// + /// Элемент , который должен стать источником перетаскивания. + /// Не может быть null. + /// + /// + /// Данные, которые будут перетаскиваться. Может быть null. + /// Если не указано, используется или + /// элемента. + /// /// /// Выбрасывается, если равен null. /// /// - /// После вызова этого метода элемент начинает обрабатывать события перетаскивания. + /// + /// После вызова этого метода: + /// + /// Элементу автоматически подписываются обработчики событий указателя + /// Поведение начинает отслеживать взаимодействия с элементом + /// При превышении порога перетаскивания инициируется операция через + /// + /// + /// + /// Для открепления поведения используйте метод . + /// /// public void Attach(FrameworkElement element, object? dragData = null) { - Detach(); + if (element == null) + throw new ArgumentNullException(nameof(element)); - _element = element ?? throw new ArgumentNullException(nameof(element)); _dragData = dragData ?? element.DataContext ?? element.Tag; - - SubscribeToEvents(); + AssociatedElement = element; } /// - /// Открепляет поведение от элемента. + /// Открепляет поведение от текущего элемента. /// - public void Detach() + /// + /// + /// Этот метод выполняет следующие действия: + /// + /// Отписывается от всех событий элемента + /// Сбрасывает внутреннее состояние + /// Освобождает ссылки на связанные объекты + /// + /// + /// + /// Если в момент вызова активна операция перетаскивания, она будет автоматически отменена. + /// + /// + /// После вызова этого метода поведение может быть повторно прикреплено к другому элементу. + /// + /// + public new void Detach() { - if (_element == null) return; - - UnsubscribeFromEvents(); - - if (_isDragging) - { - _dragDropService.CancelDragAsync().ConfigureAwait(false); - } - - _element = null; + base.Detach(); _dragData = null; - _isDragging = false; - - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; } #endregion - #region Обработчики событий + #region Реализация абстрактных методов DragSourceBehaviorBase - private void SubscribeToEvents() + /// + protected override void SubscribeToEvents(FrameworkElement element) { - if (_element == null) return; + if (element == null) return; - _element.PointerPressed += OnPointerPressed; - _element.PointerMoved += OnPointerMoved; - _element.PointerReleased += OnPointerReleased; - _element.PointerCanceled += OnPointerCanceled; - _element.PointerCaptureLost += OnPointerCaptureLost; + element.PointerPressed += OnPointerPressed; + element.PointerMoved += OnPointerMoved; + element.PointerReleased += OnPointerReleased; + element.PointerCanceled += OnPointerCanceled; + element.PointerCaptureLost += OnPointerCaptureLost; } - private void UnsubscribeFromEvents() + /// + protected override void UnsubscribeFromEvents(FrameworkElement element) { - if (_element == null) return; + if (element == null) return; - _element.PointerPressed -= OnPointerPressed; - _element.PointerMoved -= OnPointerMoved; - _element.PointerReleased -= OnPointerReleased; - _element.PointerCanceled -= OnPointerCanceled; - _element.PointerCaptureLost -= OnPointerCaptureLost; + element.PointerPressed -= OnPointerPressed; + element.PointerMoved -= OnPointerMoved; + element.PointerReleased -= OnPointerReleased; + element.PointerCanceled -= OnPointerCanceled; + element.PointerCaptureLost -= OnPointerCaptureLost; } - private void OnPointerPressed(object sender, PointerRoutedEventArgs e) + /// + protected override Point ConvertToScreenCoordinates(Point point) { - if (_element == null || _isDragging) return; - - var point = e.GetCurrentPoint(_element); - _dragStartPosition = new Point(point.Position.X, point.Position.Y); - - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = new CancellationTokenSource(); - } - - private async void OnPointerMoved(object sender, PointerRoutedEventArgs e) - { - if (_element == null || _isDragging || _cancellationTokenSource?.IsCancellationRequested == true) - return; - - 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) - { - ResetState(); - } - - private void OnPointerCanceled(object sender, PointerRoutedEventArgs e) - { - ResetState(); - } - - private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) - { - ResetState(); - } - - 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 Point ConvertToScreenCoordinates(Point point) - { - 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 - { + if (AssociatedElement == null) return point; - } - } - private async Task StartDragAsync(Point position) - { - if (_element == null || _dragData == null || _isDragging) return; - - try - { - var screenPosition = ConvertToScreenCoordinates(position); - var started = await _dragDropService.StartDragAsync(this, screenPosition); - _isDragging = started; - } - catch - { - ResetState(); - } - } - - private void ResetState() - { - _isDragging = false; - _dragStartPosition = default; - - _cancellationTokenSource?.Cancel(); - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; + return WinUIWindowHelper.ConvertToScreenCoordinates(AssociatedElement, point); } #endregion - #region IDragSource Implementation + #region Реализация интерфейса IDragSource - public async Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default) + /// + public override async Task TryStartDragAsync( + Point startPosition, + CancellationToken cancellationToken = default) { - if (_element == null || _dragData == null) + if (AssociatedElement == null || _dragData == null) return null; try { + // Создаем информацию о перетаскивании var dragInfo = new DragInfo( data: _dragData, allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | - Core.DragDrop.Enums.DragDropEffects.Move, + Core.DragDrop.Enums.DragDropEffects.Move | + Core.DragDrop.Enums.DragDropEffects.Link, startPosition: startPosition, source: this ); - return dragInfo; + // Добавляем дополнительные параметры + dragInfo.SetParameter("SourceElement", AssociatedElement); + dragInfo.SetParameter("SourceType", AssociatedElement.GetType().Name); + + return await Task.FromResult(dragInfo); } - catch + catch (Exception ex) { + // Логирование ошибки создания информации о перетаскивании + System.Diagnostics.Debug.WriteLine( + $"Ошибка создания DragInfo: {ex.Message}"); return null; } } - public Task OnDragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects, CancellationToken cancellationToken = default) + #endregion + + #region Обработчики событий WinUI + + private async void OnPointerPressed(object sender, PointerRoutedEventArgs e) { - _isDragging = false; - return Task.CompletedTask; + if (AssociatedElement == null) return; + + var point = e.GetCurrentPoint(AssociatedElement); + var position = new Point(point.Position.X, point.Position.Y); + + await OnInteractionStarted(position); } - public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) + private async void OnPointerMoved(object sender, PointerRoutedEventArgs e) { - _isDragging = false; - return Task.CompletedTask; + if (AssociatedElement == null) return; + + var point = e.GetCurrentPoint(AssociatedElement); + var position = new Point(point.Position.X, point.Position.Y); + + await OnInteractionMoved(position); + } + + private async void OnPointerReleased(object sender, PointerRoutedEventArgs e) + { + await OnInteractionEnded(); + } + + private async void OnPointerCanceled(object sender, PointerRoutedEventArgs e) + { + await OnInteractionCancelled(); + } + + private async void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) + { + await OnInteractionCancelled(); + } + + #endregion + + #region Переопределение виртуальных методов + + /// + protected override void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects) + { + base.OnDragCompleted(dragInfo, effects); + + // Дополнительная логика для WinUI может быть добавлена здесь + // Например, обновление состояния элемента после успешного перетаскивания + } + + /// + protected override void OnDragCancelled(DragInfo dragInfo) + { + base.OnDragCancelled(dragInfo); + + // Дополнительная логика для WinUI может быть добавлена здесь + // Например, восстановление визуального состояния элемента после отмены } #endregion diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs index 23bb6f7..902504e 100644 --- a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs +++ b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs @@ -1,9 +1,11 @@ -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Models; +using Lattice.Core.DragDrop.Models; using Lattice.Core.Geometry; -using Lattice.UI.DragDrop.WinUI.Services; +using Lattice.UI.DragDrop.Abstractions; +using Lattice.UI.DragDrop.Behaviors; using Microsoft.UI.Xaml; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -11,27 +13,52 @@ namespace Lattice.UI.DragDrop.WinUI.Behaviors; /// /// Реализация поведения цели сброса для элементов WinUI. +/// Наследуется от для использования +/// общей логики регистрации целей и обработки операций сброса. /// /// /// -/// Этот класс обрабатывает события перетаскивания WinUI и преобразует их в вызовы -/// методов интерфейса . +/// Этот класс предоставляет конкретную реализацию для платформы WinUI, обрабатывая события +/// перетаскивания WinUI и преобразуя их в вызовы методов интерфейса . /// /// -/// Поведение автоматически регистрирует элемент в системе перетаскивания, -/// обновляет его границы при изменении размера и обрабатывает все этапы операции сброса. +/// Основные функции: +/// +/// Автоматическая регистрация в при прикреплении к элементу +/// Обработка событий DragEnter, DragOver, DragLeave, Drop +/// Автоматическое обновление границ элемента при изменении размера или позиции +/// Поддержка фильтрации принимаемых типов данных +/// Интеграция с визуальной обратной связью +/// /// +/// +/// Для использования необходимо: +/// +/// Создать экземпляр поведения через фабрику +/// Прикрепить к элементу с помощью метода +/// Настроить фильтры принимаемых данных (опционально) +/// +/// +/// +/// +/// // Создание поведения с фильтрацией типов +/// var behavior = WinUIDragDropFactory.CreateDropTargetBehavior(dragDropService, host); +/// behavior.AcceptTypes(typeof(MyDataModel), typeof(string)); +/// behavior.Attach(myDropArea); +/// +/// // Или через attached properties +/// <Border x:Name="DropArea" +/// local:DragDropProperties.IsDropTarget="True" /> +/// +/// /// -public sealed class WinUIDropTargetBehavior : IDropTarget +public sealed class WinUIDropTargetBehavior : DropTargetBehaviorBase { #region Поля - private readonly Lattice.Core.DragDrop.Services.IDragDropService _dragDropService; - private readonly WinUIDragDropHost _host; - - private FrameworkElement? _element; - private string? _registrationId; - private Rect _currentBounds; + private readonly IDragDropHost _host; + private readonly List _acceptedTypes = new(); + private readonly HashSet _acceptedFormats = new(); #endregion @@ -40,14 +67,28 @@ public sealed class WinUIDropTargetBehavior : IDropTarget /// /// Инициализирует новый экземпляр класса . /// - /// Сервис для управления операциями перетаскивания. - /// Хост для отображения визуальных элементов. + /// + /// Сервис управления операциями перетаскивания. Используется для регистрации + /// цели и координации операций сброса. + /// + /// + /// Хост для управления визуальной обратной связью. Обеспечивает отображение + /// индикаторов возможности сброса. + /// /// - /// Выбрасывается, если любой из параметров равен null. + /// Выбрасывается, если или + /// равны null. /// - public WinUIDropTargetBehavior(Lattice.Core.DragDrop.Services.IDragDropService dragDropService, WinUIDragDropHost host) + /// + /// Конструктор инициализирует базовый класс и сохраняет ссылки на сервисы. + /// По умолчанию цель принимает все типы данных. Для настройки фильтрации + /// используйте методы и . + /// + public WinUIDropTargetBehavior( + Core.DragDrop.Services.IDragDropService dragDropService, + IDragDropHost host) + : base(dragDropService) { - _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); _host = host ?? throw new ArgumentNullException(nameof(host)); } @@ -56,106 +97,301 @@ public sealed class WinUIDropTargetBehavior : IDropTarget #region Публичные методы /// - /// Прикрепляет поведение к указанному элементу. + /// Прикрепляет поведение к указанному элементу WinUI. /// - /// Элемент, к которому прикрепляется поведение. + /// + /// Элемент , который должен стать целью сброса. + /// Не может быть null. + /// /// /// Выбрасывается, если равен null. /// /// + /// /// После вызова этого метода: - /// 1. Элементу устанавливается = true - /// 2. Поведение подписывается на события перетаскивания - /// 3. Элемент регистрируется в системе перетаскивания + /// + /// Элементу устанавливается свойство = true + /// Поведение подписывается на события перетаскивания WinUI + /// Элемент регистрируется в системе перетаскивания с текущими границами + /// Начинается отслеживание изменений размера и позиции элемента + /// + /// + /// + /// Для открепления поведения используйте метод . + /// /// public void Attach(FrameworkElement element) { - Detach(); + if (element == null) + throw new ArgumentNullException(nameof(element)); - _element = element ?? throw new ArgumentNullException(nameof(element)); element.AllowDrop = true; - - SubscribeToEvents(); - UpdateBounds(); - RegisterToService(); + AssociatedElement = element; } /// - /// Открепляет поведение от элемента. + /// Настраивает поведение для приема только указанных типов данных. /// - public void Detach() + /// + /// Типы данных, которые может принимать цель. Если пусто, принимаются все типы. + /// + /// + /// + /// Этот метод позволяет ограничить типы данных, которые могут быть сброшены на цель. + /// Проверка выполняется в методе путем сравнения + /// типа сбрасываемых данных с указанными типами. + /// + /// + /// Если метод не вызывался или передан пустой список, цель будет принимать данные любого типа. + /// + /// + /// + /// // Принимать только строки и объекты MyModel + /// behavior.AcceptTypes(typeof(string), typeof(MyModel)); + /// + /// + /// + public void AcceptTypes(params Type[] types) { - if (_element == null) return; + _acceptedTypes.Clear(); + if (types != null && types.Length > 0) + { + _acceptedTypes.AddRange(types); + } + } - UnsubscribeFromEvents(); - UnregisterFromService(); + /// + /// Настраивает поведение для приема только указанных форматов данных. + /// + /// + /// Форматы данных (например, "Text", "Bitmap", "FileDrop"), которые может принимать цель. + /// Если пусто, формат не проверяется. + /// + /// + /// + /// Этот метод позволяет ограничить форматы данных, которые могут быть сброшены на цель. + /// Актуально для межпроцессного перетаскивания или работы с системными форматами. + /// + /// + /// Если метод не вызывался или передан пустой список, проверка формата не выполняется. + /// + /// + public void AcceptFormats(params string[] formats) + { + _acceptedFormats.Clear(); + if (formats != null && formats.Length > 0) + { + foreach (var format in formats) + { + _acceptedFormats.Add(format); + } + } + } - _element.AllowDrop = false; - _element = null; - _currentBounds = Rect.Empty; + /// + /// Открепляет поведение от текущего элемента. + /// + /// + /// + /// Этот метод выполняет следующие действия: + /// + /// Отписывается от всех событий элемента + /// Отменяет регистрацию цели в системе перетаскивания + /// Сбрасывает свойство = false + /// Освобождает ссылки на связанные объекты + /// + /// + /// + /// После вызова этого метода поведение может быть повторно прикреплено к другому элементу. + /// + /// + public new void Detach() + { + if (AssociatedElement != null) + { + AssociatedElement.AllowDrop = false; + } + base.Detach(); } #endregion - #region Обработчики событий + #region Реализация абстрактных методов DropTargetBehaviorBase - private void SubscribeToEvents() + /// + protected override void SubscribeToEvents(FrameworkElement element) { - if (_element == null) return; + if (element == null) return; - _element.DragEnter += OnDragEnter; - _element.DragOver += OnDragOver; - _element.DragLeave += OnDragLeave; - _element.Drop += OnDrop; - _element.SizeChanged += OnSizeChanged; + element.DragEnter += OnDragEnter; + element.DragOver += OnDragOver; + element.DragLeave += OnDragLeave; + element.Drop += OnDrop; + element.SizeChanged += OnSizeChanged; + element.LayoutUpdated += OnLayoutUpdated; } - private void UnsubscribeFromEvents() + /// + protected override void UnsubscribeFromEvents(FrameworkElement element) { - if (_element == null) return; + if (element == null) return; - _element.DragEnter -= OnDragEnter; - _element.DragOver -= OnDragOver; - _element.DragLeave -= OnDragLeave; - _element.Drop -= OnDrop; - _element.SizeChanged -= OnSizeChanged; + element.DragEnter -= OnDragEnter; + element.DragOver -= OnDragOver; + element.DragLeave -= OnDragLeave; + element.Drop -= OnDrop; + element.SizeChanged -= OnSizeChanged; + element.LayoutUpdated -= OnLayoutUpdated; } - private async void OnDragEnter(object sender, DragEventArgs e) + /// + protected override Rect GetScreenBounds(FrameworkElement element) { - if (_element == null) return; + if (element == null || !element.IsLoaded) + return Rect.Empty; try { - var position = e.GetPosition(_element); + var window = Window.Current; + if (window?.Content == null) + return Rect.Empty; + + // Преобразуем локальные координаты элемента в координаты окна + var transform = element.TransformToVisual(window.Content); + var position = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); + + return new Rect( + position.X, + position.Y, + element.ActualWidth, + element.ActualHeight + ); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine( + $"Ошибка получения границ элемента: {ex.Message}"); + return Rect.Empty; + } + } + + #endregion + + #region Реализация интерфейса IDropTarget + + /// + public override async Task CanAcceptDropAsync( + DropInfo dropInfo, + CancellationToken cancellationToken = default) + { + // Проверяем, есть ли данные для сброса + if (dropInfo.Data == null) + return false; + + // Проверяем фильтр по типам + if (_acceptedTypes.Count > 0) + { + var dataType = dropInfo.Data.GetType(); + if (!_acceptedTypes.Any(t => t.IsAssignableFrom(dataType))) + { + return false; + } + } + + // Проверяем фильтр по форматам (если данные предоставляют информацию о формате) + if (_acceptedFormats.Count > 0 && dropInfo.Data is Windows.ApplicationModel.DataTransfer.DataPackageView dataView) + { + var availableFormats = dataView.AvailableFormats; + if (!_acceptedFormats.Any(f => availableFormats.Contains(f))) + { + return false; + } + } + + // Дополнительная проверка может быть добавлена в производных классах + return await Task.FromResult(true); + } + + /// + public override async Task OnDragOverAsync( + DropInfo dropInfo, + CancellationToken cancellationToken = default) + { + await base.OnDragOverAsync(dropInfo, cancellationToken); + + // Дополнительная логика для WinUI может быть добавлена здесь + // Например, обновление визуальной обратной связи через хост + } + + /// + public override async Task OnDropAsync( + DropInfo dropInfo, + CancellationToken cancellationToken = default) + { + // Базовая реализация вызывает CanAcceptDropAsync и помечает как обработанное + if (await CanAcceptDropAsync(dropInfo, cancellationToken)) + { + dropInfo.MarkAsHandled(); + + // Здесь может быть добавлена логика обработки сброшенных данных + // Например, вызов события или обновление модели данных + } + } + + /// + public override async Task OnDragLeaveAsync(CancellationToken cancellationToken = default) + { + await base.OnDragLeaveAsync(cancellationToken); + + // Дополнительная логика для WinUI может быть добавлена здесь + // Например, скрытие визуальной обратной связи + } + + #endregion + + #region Обработчики событий WinUI + + private async void OnDragEnter(object sender, DragEventArgs e) + { + if (AssociatedElement == null) return; + + try + { + var position = e.GetPosition(AssociatedElement); var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y)); if (await CanAcceptDropAsync(dropInfo)) { e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; + e.Handled = true; } } - catch { } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Ошибка в OnDragEnter: {ex.Message}"); + } } private async void OnDragOver(object sender, DragEventArgs e) { - if (_element == null) return; + if (AssociatedElement == null) return; try { - var position = e.GetPosition(_element); + var position = e.GetPosition(AssociatedElement); 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; } - - e.Handled = true; } - catch { } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Ошибка в OnDragOver: {ex.Message}"); + } } private async void OnDragLeave(object sender, DragEventArgs e) @@ -165,131 +401,114 @@ public sealed class WinUIDropTargetBehavior : IDropTarget private async void OnDrop(object sender, DragEventArgs e) { - if (_element == null) return; + if (AssociatedElement == null) return; try { - var position = e.GetPosition(_element); + var position = e.GetPosition(AssociatedElement); var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y)); if (await CanAcceptDropAsync(dropInfo)) { await OnDropAsync(dropInfo); - dropInfo.MarkAsHandled(); e.Handled = true; } } - catch { } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Ошибка в OnDrop: {ex.Message}"); + } } private void OnSizeChanged(object sender, SizeChangedEventArgs e) { - UpdateBounds(); + OnElementLayoutChanged(); } + private void OnLayoutUpdated(object sender, object e) + { + OnElementLayoutChanged(); + } + + #endregion + + #region Вспомогательные методы + + /// + /// Создает объект на основе события перетаскивания WinUI. + /// + /// Аргументы события перетаскивания WinUI. + /// Локальная позиция курсора относительно элемента. + /// + /// Экземпляр , содержащий информацию о потенциальном сбросе. + /// + /// + /// + /// Этот метод извлекает данные из события перетаскивания и преобразует их + /// в формат, понятный системе . + /// + /// + /// Поддерживаются как пользовательские данные (через свойство "DragData"), + /// так и стандартные форматы данных WinUI. + /// + /// private DropInfo CreateDropInfo(DragEventArgs e, Point position) { object? data = null; + // Пытаемся получить пользовательские данные if (e.DataView.Properties.TryGetValue("DragData", out var dragData)) { data = dragData; } + // Или получаем данные из DataPackage + else if (e.DataView.Contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.Text)) + { + // Для текстовых данных можем установить асинхронную загрузку + data = new AsyncDataProvider(async () => + { + return await e.DataView.GetTextAsync(); + }); + } + + // Определяем разрешенные эффекты на основе модификаторов клавиатуры + var allowedEffects = Core.DragDrop.Enums.DragDropEffects.None; + if (e.AllowedOperations.HasFlag(Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy)) + allowedEffects |= Core.DragDrop.Enums.DragDropEffects.Copy; + if (e.AllowedOperations.HasFlag(Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move)) + allowedEffects |= Core.DragDrop.Enums.DragDropEffects.Move; + if (e.AllowedOperations.HasFlag(Windows.ApplicationModel.DataTransfer.DataPackageOperation.Link)) + allowedEffects |= Core.DragDrop.Enums.DragDropEffects.Link; return new DropInfo( data: data, position: position, - allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | - Core.DragDrop.Enums.DragDropEffects.Move, - target: _element + allowedEffects: allowedEffects, + target: this ); } - /// - /// Получает границы элемента в экранных координатах. - /// - /// Прямоугольник с границами элемента или , если - /// элемент не загружен или не может быть преобразован. - 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 +/// +/// Предоставляет асинхронный доступ к данным перетаскивания. +/// +/// +/// Этот класс используется для отложенной загрузки данных перетаскивания, +/// что особенно важно для больших данных или данных, требующих обработки. +/// +internal class AsyncDataProvider +{ + private readonly Func> _dataLoader; - public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default) + public AsyncDataProvider(Func> dataLoader) { - return Task.FromResult(true); // Принимаем все по умолчанию + _dataLoader = dataLoader; } - public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct = default) + public async Task GetDataAsync() { - dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Move; - return Task.CompletedTask; + return await _dataLoader(); } - - public Task OnDropAsync(DropInfo dropInfo, CancellationToken ct = default) - { - return Task.CompletedTask; - } - - public Task OnDragLeaveAsync(CancellationToken ct = default) - { - return Task.CompletedTask; - } - - #endregion } \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Factories/WinUIDragDropFactory.cs b/Lattice.UI.DragDrop.WinUI/Factories/WinUIDragDropFactory.cs index 91cde9a..2dd071a 100644 --- a/Lattice.UI.DragDrop.WinUI/Factories/WinUIDragDropFactory.cs +++ b/Lattice.UI.DragDrop.WinUI/Factories/WinUIDragDropFactory.cs @@ -1,30 +1,64 @@ -using Lattice.Core.DragDrop.Factories; -using Lattice.Core.DragDrop.Services; +using Lattice.Core.DragDrop.Services; using Lattice.UI.DragDrop.WinUI.Behaviors; using Lattice.UI.DragDrop.WinUI.Controls; using Lattice.UI.DragDrop.WinUI.Services; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; using System; namespace Lattice.UI.DragDrop.WinUI.Factories; /// -/// Фабрика для создания компонентов системы перетаскивания WinUI. -/// Предоставляет методы для быстрой настройки drag-and-drop в приложениях WinUI. +/// Фабрика для создания и настройки компонентов системы перетаскивания WinUI. +/// Предоставляет удобные методы для быстрой интеграции drag-and-drop функциональности +/// в приложениях WinUI с поддержкой различных сценариев использования. /// /// -/// Эта фабрика упрощает интеграцию системы перетаскивания в WinUI приложения, -/// предоставляя готовые методы для наиболее распространенных сценариев использования. +/// +/// служит высокоуровневым API для работы с системой +/// перетаскивания, инкапсулируя сложность инициализации и настройки компонентов. +/// +/// +/// Основные возможности фабрики: +/// +/// Создание и инициализация менеджера перетаскивания +/// Генерация поведений для источников и целей перетаскивания +/// Создание визуальных элементов для обратной связи +/// Предварительные конфигурации для типовых сценариев +/// Вспомогательные методы для работы с XAML +/// +/// +/// +/// Фабрика поддерживает два подхода к использованию: +/// +/// Императивный подход - создание компонентов в коде C# +/// Декларативный подход - использование attached properties в XAML +/// +/// +/// +/// +/// // Императивный подход +/// var manager = WinUIDragDropFactory.CreateManager(window); +/// manager.MakeDragSource(element, data); +/// manager.MakeDropTarget(dropArea); +/// +/// // Декларативный подход (в XAML) +/// <Border local:DragDropProperties.IsDragSource="True" +/// local:DragDropProperties.DragData="{Binding Item}" /> +/// <Border local:DragDropProperties.IsDropTarget="True" /> +/// +/// /// public static class WinUIDragDropFactory { #region Основные компоненты /// - /// Создает и инициализирует менеджер перетаскивания для WinUI окна. + /// Создает и инициализирует менеджер перетаскивания для указанного окна WinUI. /// /// /// Окно WinUI, для которого создается менеджер перетаскивания. + /// Не может быть null. /// /// /// Инициализированный экземпляр . @@ -33,30 +67,38 @@ public static class WinUIDragDropFactory /// Выбрасывается, когда равен null. /// /// - /// Этот метод создает менеджер перетаскивания и настраивает его для работы с указанным окном. - /// Менеджер автоматически создает оверлей для визуальных элементов и подписывается на события. - /// + /// + /// Этот метод является основным способом получения менеджера перетаскивания. + /// Он создает (или возвращает существующий) экземпляр менеджера и инициализирует + /// его для работы с указанным окном. + /// + /// + /// Метод следует вызывать один раз при запуске приложения, обычно в конструкторе + /// главного окна или в методе . + /// /// /// /// public partial class MainWindow : Window /// { - /// private WinUIDragDropManager _dragDropManager; - /// /// public MainWindow() /// { /// InitializeComponent(); - /// _dragDropManager = WinUIDragDropFactory.CreateManager(this); + /// var manager = WinUIDragDropFactory.CreateManager(this); /// } /// } /// /// + /// public static WinUIDragDropManager CreateManager(Window window) { if (window == null) throw new ArgumentNullException(nameof(window)); var manager = WinUIDragDropManager.Instance; - manager.Initialize(window); + if (!manager.IsInitialized) + { + manager.Initialize(window); + } return manager; } @@ -67,14 +109,34 @@ public static class WinUIDragDropFactory /// Окно WinUI, для которого создается менеджер перетаскивания. /// /// - /// Делегат для настройки менеджера перед инициализацией. + /// Делегат для настройки параметров менеджера перед инициализацией. + /// Передает экземпляр для конфигурации. /// /// /// Инициализированный экземпляр . /// + /// + /// Выбрасывается, когда равен null. + /// /// - /// Этот метод позволяет настроить параметры менеджера (например, смещение визуального элемента) - /// перед его инициализацией. + /// + /// Этот метод позволяет настроить параметры менеджера перед его инициализацией, + /// что полезно для тонкой настройки поведения системы перетаскивания. + /// + /// + /// Доступные для настройки параметры включают: + /// + /// - смещение визуального элемента + /// + /// + /// + /// + /// var manager = WinUIDragDropFactory.CreateManager(window, m => + /// { + /// m.DragVisualOffset = new Point(-15, -15); // Ближе к курсору + /// }); + /// + /// /// public static WinUIDragDropManager CreateManager(Window window, Action configure) { @@ -82,13 +144,20 @@ public static class WinUIDragDropFactory throw new ArgumentNullException(nameof(window)); var manager = WinUIDragDropManager.Instance; + + // Применяем настройки перед инициализацией configure?.Invoke(manager); - manager.Initialize(window); + + if (!manager.IsInitialized) + { + manager.Initialize(window); + } + return manager; } /// - /// Создает хост для визуальных элементов перетаскивания. + /// Создает хост для управления визуальными элементами перетаскивания. /// /// /// Окно, к которому будет привязан хост. @@ -96,9 +165,26 @@ public static class WinUIDragDropFactory /// /// Экземпляр , готовый к использованию. /// + /// + /// Выбрасывается, если равен null. + /// /// - /// Хост управляет отображением визуальных элементов (drag-визуализация, drop-превью) - /// на оверлейном слое поверх основного содержимого окна. + /// + /// Хост управляет отображением визуальных элементов во время операций перетаскивания, + /// включая drag-визуализации (элементы, следующие за курсором) и drop-превью + /// (подсветка областей сброса). + /// + /// + /// В большинстве случаев хост создается автоматически менеджером перетаскивания. + /// Этот метод полезен для продвинутых сценариев, когда требуется прямой контроль + /// над визуальной обратной связью. + /// + /// + /// + /// var host = WinUIDragDropFactory.CreateHost(window); + /// // Настройка кастомной визуализации + /// + /// /// public static WinUIDragDropHost CreateHost(Window window) { @@ -130,7 +216,21 @@ public static class WinUIDragDropFactory /// Выбрасывается, если любой из параметров равен null. /// /// - /// Созданное поведение необходимо прикрепить к элементу с помощью метода . + /// + /// Созданное поведение необходимо прикрепить к элементу с помощью метода + /// . + /// + /// + /// Этот метод полезен для продвинутых сценариев, когда требуется создавать поведения + /// вручную, например, при динамическом создании элементов интерфейса. + /// + /// + /// + /// // Создание поведения вручную + /// var behavior = WinUIDragDropFactory.CreateDragSourceBehavior(service, host); + /// behavior.Attach(element, data); + /// + /// /// public static WinUIDragSourceBehavior CreateDragSourceBehavior( IDragDropService dragDropService, @@ -160,7 +260,24 @@ public static class WinUIDragDropFactory /// Выбрасывается, если любой из параметров равен null. /// /// - /// Созданное поведение необходимо прикрепить к элементу с помощью метода . + /// + /// Созданное поведение необходимо прикрепить к элементу с помощью метода + /// . + /// + /// + /// Поведение можно дополнительно настроить с помощью методов + /// и + /// для фильтрации + /// принимаемых данных. + /// + /// + /// + /// // Создание поведения вручную + /// var behavior = WinUIDragDropFactory.CreateDropTargetBehavior(service, host); + /// behavior.AcceptTypes(typeof(string), typeof(MyModel)); + /// behavior.Attach(dropArea); + /// + /// /// public static WinUIDropTargetBehavior CreateDropTargetBehavior( IDragDropService dragDropService, @@ -174,6 +291,93 @@ public static class WinUIDragDropFactory return new WinUIDropTargetBehavior(dragDropService, host); } + /// + /// Создает поведение источника перетаскивания, используя сервисы из менеджера по умолчанию. + /// + /// + /// Экземпляр , готовый к прикреплению к элементу. + /// + /// + /// Выбрасывается, если менеджер не инициализирован. + /// + /// + /// + /// Этот метод использует для получения + /// сервиса перетаскивания и хоста, что упрощает создание поведений в контексте + /// уже инициализированной системы. + /// + /// + /// Перед использованием убедитесь, что менеджер инициализирован через метод + /// . + /// + /// + /// + /// // Инициализация менеджера + /// WinUIDragDropFactory.CreateManager(window); + /// + /// // Создание поведения с использованием менеджера + /// var behavior = WinUIDragDropFactory.CreateDragSourceBehavior(); + /// behavior.Attach(element, data); + /// + /// + /// + public static WinUIDragSourceBehavior CreateDragSourceBehavior() + { + var manager = WinUIDragDropManager.Instance; + if (!manager.IsInitialized) + { + throw new InvalidOperationException( + "Менеджер не инициализирован. Вызовите CreateManager перед созданием поведений."); + } + + return new WinUIDragSourceBehavior(manager.DragDropService, manager.Host); + } + + /// + /// Создает поведение цели сброса, используя сервисы из менеджера по умолчанию. + /// + /// + /// Экземпляр , готовый к прикреплению к элементу. + /// + /// + /// Выбрасывается, если менеджер не инициализирован. + /// + /// + /// + /// Этот метод использует для получения + /// сервиса перетаскивания и хоста, что упрощает создание поведений в контексте + /// уже инициализированной системы. + /// + /// + /// Поведение можно дополнительно настроить с помощью методов + /// и + /// для фильтрации + /// принимаемых данных. + /// + /// + /// + /// // Инициализация менеджера + /// WinUIDragDropFactory.CreateManager(window); + /// + /// // Создание поведения с использованием менеджера + /// var behavior = WinUIDragDropFactory.CreateDropTargetBehavior(); + /// behavior.AcceptTypes(typeof(string)); + /// behavior.Attach(dropArea); + /// + /// + /// + public static WinUIDropTargetBehavior CreateDropTargetBehavior() + { + var manager = WinUIDragDropManager.Instance; + if (!manager.IsInitialized) + { + throw new InvalidOperationException( + "Менеджер не инициализирован. Вызовите CreateManager перед созданием поведений."); + } + + return new WinUIDropTargetBehavior(manager.DragDropService, manager.Host); + } + #endregion #region Визуальные элементы @@ -183,23 +387,44 @@ public static class WinUIDragDropFactory /// /// /// Данные, которые будут отображены в визуальном элементе. + /// Могут быть любого типа, поддерживаемого системой перетаскивания. /// /// - /// Прозрачность элемента (от 0.0 до 1.0). + /// Прозрачность элемента в диапазоне от 0.0 (полностью прозрачный) до 1.0 (полностью непрозрачный). + /// Значение по умолчанию: 0.8. /// /// /// Экземпляр , настроенный для отображения указанных данных. /// /// + /// /// Созданный элемент можно использовать с методом /// для отображения во время операции перетаскивания. + /// + /// + /// Элемент автоматически адаптирует отображение в зависимости от типа данных: + /// + /// Для строк отображается текстовое представление + /// Для изображений отображается миниатюра + /// Для пользовательских объектов используется DataTemplate или ToString() + /// + /// + /// + /// + /// // Создание визуального элемента + /// var adorner = WinUIDragDropFactory.CreateDragAdorner("Текст для перетаскивания"); + /// + /// // Отображение во время перетаскивания + /// host.ShowDragVisual(adorner, position); + /// + /// /// public static DragAdorner CreateDragAdorner(object dragData, double opacity = 0.8) { return new DragAdorner { - DragData = dragData, - Opacity = opacity + DragData = dragData ?? throw new ArgumentNullException(nameof(dragData)), + Opacity = Math.Clamp(opacity, 0.0, 1.0) }; } @@ -207,16 +432,40 @@ public static class WinUIDragDropFactory /// Создает элемент предварительного просмотра для области сброса. /// /// - /// Цвет подсветки области. Если не указан, используется цвет из ресурсов темы. + /// Цвет подсветки области. Если не указан, используется системный цвет акцента. /// /// - /// Толщина границы подсветки. + /// Толщина границы подсветки в пикселях. + /// Значение по умолчанию: 2.0. /// /// /// Экземпляр , готовый к отображению. /// /// - /// Этот элемент используется для визуального указания области, на которую можно сбросить данные. + /// + /// Этот элемент используется для визуального указания области, на которую можно + /// сбросить данные. Он отображается как подсветка границ целевого элемента с + /// поддержкой анимации появления и скрытия. + /// + /// + /// Элемент автоматически адаптирует свой внешний вид в зависимости от состояния: + /// + /// Normal - стандартное состояние + /// Highlighted - подсветка при наведении + /// + /// + /// + /// + /// // Создание элемента подсветки + /// var preview = WinUIDragDropFactory.CreateDropPreviewAdorner( + /// Colors.Blue, // Цвет + /// 3.0 // Толщина границы + /// ); + /// + /// // Отображение подсветки + /// preview.Show(bounds); + /// + /// /// public static DropPreviewAdorner CreateDropPreviewAdorner( Windows.UI.Color? color = null, @@ -224,7 +473,7 @@ public static class WinUIDragDropFactory { var adorner = new DropPreviewAdorner { - PreviewThickness = thickness + PreviewThickness = Math.Max(thickness, 0.0) }; if (color.HasValue) @@ -249,18 +498,35 @@ public static class WinUIDragDropFactory /// Кортеж, содержащий менеджер перетаскивания и сервис перетаскивания. /// /// - /// Этот метод создает все необходимые компоненты для работы перетаскивания в приложении. - /// Возвращаемый сервис можно использовать для создания дополнительных источников и целей. + /// + /// Этот метод создает все необходимые компоненты для работы перетаскивания в приложении + /// и возвращает их для дальнейшего использования. + /// + /// + /// Возвращаемый сервис можно использовать для создания дополнительных источников и целей + /// или для низкоуровневого управления операциями перетаскивания. + /// + /// + /// + /// // Создание полной системы + /// var (manager, service) = WinUIDragDropFactory.CreateCompleteSystem(window); + /// + /// // Использование менеджера для настройки элементов + /// manager.MakeDragSource(element, data); + /// + /// // Использование сервиса для расширенного управления + /// var stats = service.GetStats(); + /// + /// /// public static (WinUIDragDropManager Manager, IDragDropService Service) CreateCompleteSystem(Window window) { - var service = DragDropFactory.CreateDragDropService(); var manager = CreateManager(window); - return (manager, service); + return (manager, manager.DragDropService); } /// - /// Создает систему перетаскивания, оптимизированную для списков и коллекций. + /// Создает систему перетаскивания, оптимизированную для переупорядочивания элементов в списках. /// /// /// Главное окно приложения. @@ -269,13 +535,45 @@ public static class WinUIDragDropFactory /// Менеджер перетаскивания, настроенный для переупорядочивания элементов в списках. /// /// + /// /// Эта конфигурация устанавливает оптимальные параметры для перетаскивания элементов - /// внутри списков (например, для изменения порядка элементов в ListView или ItemsControl). + /// внутри списков и коллекций, таких как: + /// + /// Изменение порядка элементов в ListView + /// Перемещение элементов между ItemsControl + /// Сортировка элементов в коллекциях + /// + /// + /// + /// Особенности конфигурации: + /// + /// Уменьшенный порог начала перетаскивания для более быстрого отклика + /// Смещение визуального элемента для лучшего визуального выравнивания + /// Оптимизированная визуальная обратная связь + /// + /// + /// + /// + /// // Создание системы для переупорядочивания списков + /// var manager = WinUIDragDropFactory.CreateListReorderSystem(window); + /// + /// // Настройка ListView для переупорядочивания + /// foreach (var item in myListView.Items) + /// { + /// if (item is FrameworkElement element) + /// { + /// manager.MakeDragSource(element, element.DataContext); + /// } + /// } + /// manager.MakeDropTarget(myListView); + /// + /// /// public static WinUIDragDropManager CreateListReorderSystem(Window window) { var manager = CreateManager(window, m => { + // Уменьшенное смещение для лучшего визуального выравнивания в списках m.DragVisualOffset = new Core.Geometry.Point(-15, -15); }); @@ -283,7 +581,7 @@ public static class WinUIDragDropFactory } /// - /// Создает систему перетаскивания для файлов и документов. + /// Создает систему перетаскивания для работы с файлами и документами. /// /// /// Главное окно приложения. @@ -292,13 +590,37 @@ public static class WinUIDragDropFactory /// Менеджер перетаскивания, настроенный для работы с файлами. /// /// - /// Эта конфигурация устанавливает увеличенный порог перетаскивания и специальные - /// визуальные эффекты, характерные для операций с файлами. + /// + /// Эта конфигурация оптимизирована для сценариев работы с файлами: + /// + /// Перетаскивание файлов из проводника в приложение + /// Перемещение файлов между элементами интерфейса + /// Работа с большими объемами данных + /// + /// + /// + /// Особенности конфигурации: + /// + /// Увеличенный порог перетаскивания для предотвращения случайных операций + /// Специальные визуальные эффекты, характерные для файловых операций + /// Оптимизация для работы с внешними источниками данных + /// + /// + /// + /// + /// // Создание системы для работы с файлами + /// var manager = WinUIDragDropFactory.CreateFileDragDropSystem(window); + /// + /// // Настройка области для приема файлов + /// manager.MakeDropTarget(fileDropArea); + /// + /// /// public static WinUIDragDropManager CreateFileDragDropSystem(Window window) { var manager = CreateManager(window, m => { + // Увеличенное смещение для файлов (имитация "переноса" файла) m.DragVisualOffset = new Core.Geometry.Point(-25, -25); }); @@ -315,13 +637,39 @@ public static class WinUIDragDropFactory /// Менеджер перетаскивания, настроенный для точного позиционирования. /// /// - /// Эта конфигурация уменьшает порог начала перетаскивания для более точного контроля - /// и устанавливает минималистичную визуализацию. + /// + /// Эта конфигурация оптимизирована для приложений, требующих высокой точности + /// позиционирования, таких как: + /// + /// Графические редакторы (Photoshop, Figma) + /// Инструменты проектирования интерфейсов + /// CAD-системы и приложения для 3D-моделирования + /// + /// + /// + /// Особенности конфигурации: + /// + /// Минимальный порог начала перетаскивания для максимальной точности + /// Минималистичная визуализация для уменьшения визуального шума + /// Оптимизация для работы с высокоточными устройствами ввода (графические планшеты) + /// + /// + /// + /// + /// // Создание системы для графического редактора + /// var manager = WinUIDragDropFactory.CreateDesignToolSystem(window); + /// + /// // Настройка инструментов для перетаскивания + /// manager.MakeDragSource(toolIcon, toolData); + /// manager.MakeDropTarget(canvas); + /// + /// /// public static WinUIDragDropManager CreateDesignToolSystem(Window window) { var manager = CreateManager(window, m => { + // Минимальное смещение для точного позиционирования m.DragVisualOffset = new Core.Geometry.Point(-10, -10); }); @@ -344,11 +692,25 @@ public static class WinUIDragDropFactory /// /// Данные для перетаскивания. Если не указаны, будут использованы DataContext или Tag элемента. /// + /// + /// Выбрасывается, если или равны null. + /// /// - /// Этот метод является оберткой вокруг , - /// предоставляя более удобный API. + /// + /// Этот метод является удобной оберткой вокруг , + /// предоставляющей более лаконичный синтаксис. + /// + /// + /// + /// // Использование вспомогательного метода + /// WinUIDragDropFactory.SetupAsDragSource(manager, myElement, myData); + /// + /// // Эквивалентно: + /// manager.MakeDragSource(myElement, myData); + /// + /// /// - public static void SetupAsDragSource(WinUIDragDropManager manager, Microsoft.UI.Xaml.FrameworkElement element, object dragData = null) + public static void SetupAsDragSource(WinUIDragDropManager manager, FrameworkElement element, object dragData = null) { if (manager == null) throw new ArgumentNullException(nameof(manager)); @@ -367,11 +729,25 @@ public static class WinUIDragDropFactory /// /// Элемент WinUI, который должен стать целью сброса. /// + /// + /// Выбрасывается, если или равны null. + /// /// - /// Этот метод является оберткой вокруг , - /// предоставляя более удобный API. + /// + /// Этот метод является удобной оберткой вокруг , + /// предоставляющей более лаконичный синтаксис. + /// + /// + /// + /// // Использование вспомогательного метода + /// WinUIDragDropFactory.SetupAsDropTarget(manager, myDropArea); + /// + /// // Эквивалентно: + /// manager.MakeDropTarget(myDropArea); + /// + /// /// - public static void SetupAsDropTarget(WinUIDragDropManager manager, Microsoft.UI.Xaml.FrameworkElement element) + public static void SetupAsDropTarget(WinUIDragDropManager manager, FrameworkElement element) { if (manager == null) throw new ArgumentNullException(nameof(manager)); @@ -388,20 +764,43 @@ public static class WinUIDragDropFactory /// Менеджер перетаскивания. /// /// - /// Контейнер (например, StackPanel или Grid), дочерние элементы которого можно переупорядочивать. + /// Контейнер (например, StackPanel, Grid или ItemsControl), дочерние элементы которого можно переупорядочивать. /// /// /// Функция для получения данных перетаскивания из дочернего элемента. /// Если не указана, используются DataContext дочерних элементов. /// + /// + /// Выбрасывается, если или равны null. + /// /// + /// /// Этот метод автоматически настраивает все дочерние элементы контейнера как источники перетаскивания, - /// а сам контейнер — как цель сброса, создавая функциональность переупорядочивания. + /// а сам контейнер — как цель сброса, создавая функциональность переупорядочивания элементов. + /// + /// + /// Поддерживаемые типы контейнеров: + /// + /// и его производные (StackPanel, Grid, Canvas) + /// и его производные (ListView, ListBox) + /// Любые другие контейнеры с коллекцией дочерних элементов + /// + /// + /// + /// + /// // Настройка StackPanel для переупорядочивания дочерних элементов + /// WinUIDragDropFactory.SetupReorderContainer(manager, myStackPanel); + /// + /// // С кастомным селектором данных + /// WinUIDragDropFactory.SetupReorderContainer(manager, myListView, + /// element => ((FrameworkElement)element).DataContext); + /// + /// /// public static void SetupReorderContainer( WinUIDragDropManager manager, - Microsoft.UI.Xaml.Controls.Panel container, - Func childSelector = null) + FrameworkElement container, + Func childSelector = null) { if (manager == null) throw new ArgumentNullException(nameof(manager)); @@ -411,10 +810,50 @@ public static class WinUIDragDropFactory // Настраиваем контейнер как цель сброса manager.MakeDropTarget(container); - // Настраиваем все дочерние элементы как источники перетаскивания - foreach (var child in container.Children) + // Настраиваем дочерние элементы как источники перетаскивания + if (container is Panel panel) { - if (child is Microsoft.UI.Xaml.FrameworkElement element) + SetupPanelChildren(manager, panel, childSelector); + } + else if (container is ItemsControl itemsControl) + { + SetupItemsControlChildren(manager, itemsControl, childSelector); + } + } + + /// + /// Настраивает дочерние элементы Panel как источники перетаскивания. + /// + private static void SetupPanelChildren( + WinUIDragDropManager manager, + Panel panel, + Func childSelector) + { + foreach (var child in panel.Children) + { + if (child is FrameworkElement element) + { + var data = childSelector?.Invoke(element) ?? element.DataContext ?? element.Tag; + manager.MakeDragSource(element, data); + } + } + } + + /// + /// Настраивает элементы ItemsControl как источники перетаскивания. + /// + private static void SetupItemsControlChildren( + WinUIDragDropManager manager, + ItemsControl itemsControl, + Func childSelector) + { + // Для ItemsControl нам нужно работать с ItemContainerGenerator + // В реальной реализации здесь должна быть более сложная логика + // для обработки виртуализации и динамических элементов + + foreach (var item in itemsControl.Items) + { + if (item is FrameworkElement element) { var data = childSelector?.Invoke(element) ?? element.DataContext ?? element.Tag; manager.MakeDragSource(element, data); @@ -427,7 +866,7 @@ public static class WinUIDragDropFactory #region Методы для работы с XAML /// - /// Настраивает прикрепленные свойства для элемента источника перетаскивания. + /// Настраивает attached properties для элемента источника перетаскивания. /// /// /// Элемент, который должен стать источником перетаскивания. @@ -435,39 +874,76 @@ public static class WinUIDragDropFactory /// /// Данные для перетаскивания. /// + /// + /// Выбрасывается, если равен null. + /// /// + /// /// Этот метод устанавливает attached properties, которые активируют поведение перетаскивания /// при использовании в XAML. Эквивалентно установке свойств IsDragSource и DragData в XAML. + /// + /// + /// Метод полезен для динамической настройки элементов в коде C# при сохранении + /// декларативного стиля программирования. + /// + /// + /// + /// // Настройка элемента в коде C# + /// WinUIDragDropFactory.SetupDragSourceInXaml(myElement, myData); + /// + /// // Эквивалентно в XAML: + /// <Border local:DragDropProperties.IsDragSource="True" + /// local:DragDropProperties.DragData="{Binding MyData}" /> + /// + /// /// - public static void SetupDragSourceInXaml(Microsoft.UI.Xaml.FrameworkElement element, object dragData) + public static void SetupDragSourceInXaml(FrameworkElement element, object dragData) { if (element == null) throw new ArgumentNullException(nameof(element)); // Устанавливаем attached properties - element.SetValue(Behaviors.WinUIDragSourceBehavior.IsEnabledProperty, true); + element.SetValue(DragDropProperties.IsDragSourceProperty, true); if (dragData != null) { - element.SetValue(Behaviors.WinUIDragSourceBehavior.DragDataProperty, dragData); + element.SetValue(DragDropProperties.DragDataProperty, dragData); } } /// - /// Настраивает прикрепленные свойства для элемента цели сброса. + /// Настраивает attached properties для элемента цели сброса. /// /// /// Элемент, который должен стать целью сброса. /// + /// + /// Выбрасывается, если равен null. + /// /// + /// /// Этот метод устанавливает attached properties, которые активируют поведение цели сброса /// при использовании в XAML. Эквивалентно установке свойства IsDropTarget в XAML. + /// + /// + /// Метод полезен для динамической настройки элементов в коде C# при сохранении + /// декларативного стиля программирования. + /// + /// + /// + /// // Настройка элемента в коде C# + /// WinUIDragDropFactory.SetupDropTargetInXaml(myDropArea); + /// + /// // Эквивалентно в XAML: + /// <Border local:DragDropProperties.IsDropTarget="True" /> + /// + /// /// - public static void SetupDropTargetInXaml(Microsoft.UI.Xaml.FrameworkElement element) + public static void SetupDropTargetInXaml(FrameworkElement element) { if (element == null) throw new ArgumentNullException(nameof(element)); - element.SetValue(Behaviors.WinUIDropTargetBehavior.IsEnabledProperty, true); + element.SetValue(DragDropProperties.IsDropTargetProperty, true); } #endregion diff --git a/Lattice.UI.DragDrop.WinUI/Helpers/WinUIWindowHelper.cs b/Lattice.UI.DragDrop.WinUI/Helpers/WinUIWindowHelper.cs new file mode 100644 index 0000000..9ea1d4c --- /dev/null +++ b/Lattice.UI.DragDrop.WinUI/Helpers/WinUIWindowHelper.cs @@ -0,0 +1,89 @@ +using Lattice.Core.Geometry; +using Microsoft.UI.Xaml; +using System; +using System.Runtime.InteropServices; + +/// +/// Вспомогательный класс для получения экранных координат в WinUI 3. +/// Использует P/Invoke для доступа к нативным API Windows. +/// +internal static class WinUIWindowHelper +{ + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int X; + public int Y; + + public POINT(int x, int y) + { + X = x; + Y = y; + } + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); + + [DllImport("user32.dll")] + public static extern IntPtr WindowFromPoint(POINT point); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + /// + /// Преобразует координаты элемента в экранные координаты. + /// + public static Point ConvertToScreenCoordinates(FrameworkElement element, Point localPoint) + { + if (element == null || !element.IsLoaded) + return localPoint; + + try + { + var window = Window.Current; + if (window == null) + return localPoint; + + // Получаем хэндл окна + var hwnd = GetWindowHandle(window); + if (hwnd == IntPtr.Zero) + return localPoint; + + // Преобразуем координаты элемента в координаты окна + var transform = element.TransformToVisual(window.Content); + var windowPoint = transform.TransformPoint( + new Windows.Foundation.Point(localPoint.X, localPoint.Y)); + + // Преобразуем в POINT для P/Invoke + var point = new POINT( + (int)Math.Round(windowPoint.X), + (int)Math.Round(windowPoint.Y)); + + // Преобразуем клиентские координаты в экранные + if (ClientToScreen(hwnd, ref point)) + { + return new Point(point.X, point.Y); + } + + return localPoint; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Ошибка преобразования координат: {ex.Message}"); + return localPoint; + } + } + + /// + /// Получает хэндл окна WinUI. + /// + private static IntPtr GetWindowHandle(Window window) + { + // В WinUI 3 можно использовать WinRT API для получения хэндла + // или альтернативные методы в зависимости от контекста + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window); + return hwnd; + } +} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs index b0eb8fa..2ce3d99 100644 --- a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs +++ b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs @@ -1,5 +1,6 @@ using Lattice.Core.DragDrop.Services; using Lattice.Core.Geometry; +using Lattice.UI.DragDrop.WinUI.Behaviors; using Lattice.UI.DragDrop.WinUI.Controls; using Microsoft.UI.Xaml; using System; @@ -9,41 +10,87 @@ namespace Lattice.UI.DragDrop.WinUI.Services; /// /// Центральный менеджер для управления операциями drag-and-drop в WinUI приложении. +/// Координирует работу источников и целей перетаскивания, управляет визуальной обратной связью +/// и обеспечивает согласованное взаимодействие всех компонентов системы. /// /// /// -/// Этот класс реализует шаблон Singleton и предоставляет единую точку для -/// настройки и управления всеми операциями перетаскивания в приложении. +/// реализует шаблон Singleton и служит единой точкой +/// входа для настройки и управления операциями перетаскивания в WinUI-приложении. /// /// -/// Менеджер отвечает за: -/// - Инициализацию системы перетаскивания -/// - Регистрацию и отслеживание источников и целей перетаскивания -/// - Создание и управление визуальной обратной связью -/// - Координацию между поведением элементов и базовым сервисом перетаскивания +/// Основные функции менеджера: +/// +/// Инициализация системы перетаскивания для конкретного окна +/// Регистрация и отслеживание источников и целей перетаскивания +/// Управление жизненным циклом операций перетаскивания +/// Обеспечение визуальной обратной связи через +/// Координация взаимодействия между и +/// /// /// -/// Для использования необходимо вызвать при запуске приложения -/// и использовать attached properties или методы расширения для настройки элементов. +/// Для использования менеджера необходимо: +/// +/// Вызвать при создании главного окна приложения +/// Настроить элементы как источники или цели через методы и +/// Использовать attached properties для декларативной настройки в XAML +/// /// +/// +/// +/// // Инициализация в коде +/// public partial class MainWindow : Window +/// { +/// private WinUIDragDropManager _manager; +/// +/// public MainWindow() +/// { +/// InitializeComponent(); +/// _manager = WinUIDragDropManager.Instance; +/// _manager.Initialize(this); +/// +/// // Настройка элементов +/// _manager.MakeDragSource(myElement, myData); +/// _manager.MakeDropTarget(myDropArea); +/// } +/// } +/// +/// // Или через attached properties в XAML +/// <Border x:Name="DragElement" +/// local:DragDropProperties.IsDragSource="True" +/// local:DragDropProperties.DragData="{Binding MyData}" /> +/// <Border x:Name="DropArea" +/// local:DragDropProperties.IsDropTarget="True" /> +/// +/// /// public sealed class WinUIDragDropManager : IDisposable { - #region Singleton + #region Singleton Implementation private static WinUIDragDropManager? _instance; - private static readonly object _lock = new(); + private static readonly object _lockObject = new(); /// - /// Получает единственный экземпляр менеджера. + /// Получает единственный экземпляр . + /// Реализует шаблон Singleton с ленивой инициализацией и потокобезопасностью. /// + /// + /// Единственный экземпляр менеджера перетаскивания для всего приложения. + /// Если экземпляр еще не создан, он будет инициализирован при первом обращении. + /// + /// + /// Использование Singleton гарантирует, что во всем приложении существует только один + /// экземпляр менеджера, что обеспечивает согласованное управление всеми операциями + /// перетаскивания и предотвращает конфликты между разными компонентами системы. + /// public static WinUIDragDropManager Instance { get { if (_instance == null) { - lock (_lock) + lock (_lockObject) { _instance ??= new WinUIDragDropManager(); } @@ -54,37 +101,107 @@ public sealed class WinUIDragDropManager : IDisposable #endregion - #region Поля + #region Fields - private readonly DragDropService _dragDropService; + private readonly IDragDropService _dragDropService; private readonly WinUIDragDropHost _host; - private readonly Dictionary _dragSources = new(); - private readonly Dictionary _dropTargets = new(); + private readonly Dictionary _dragSources = new(); + private readonly Dictionary _dropTargets = new(); private DragAdorner? _currentDragVisual; private bool _disposed; + private bool _initialized; #endregion - #region Свойства + #region Properties /// - /// Получает основной сервис перетаскивания. + /// Получает сервис перетаскивания, используемый менеджером для координации операций. /// + /// + /// Экземпляр , через который менеджер взаимодействует + /// с ядром системы перетаскивания. + /// + /// + /// Этот сервис предоставляет низкоуровневый API для управления операциями перетаскивания + /// и может использоваться для расширенной настройки системы. + /// public IDragDropService DragDropService => _dragDropService; + /// + /// Получает хост для управления визуальными элементами перетаскивания. + /// + /// + /// Экземпляр , отвечающий за отображение и позиционирование + /// визуальной обратной связи во время операций перетаскивания. + /// + public WinUIDragDropHost Host => _host; + /// /// Получает или задает смещение визуального элемента перетаскивания относительно курсора. /// /// - /// Точка, определяющая смещение по осям X и Y. Значение по умолчанию: (-20, -20). - /// Отрицательные значения поднимают визуальный элемент вверх и влево относительно курсора. + /// Точка, определяющая смещение по осям X и Y в пикселях. + /// Значение по умолчанию: (-20, -20). /// + /// + /// + /// Отрицательные значения смещают визуальный элемент вверх и влево относительно курсора, + /// что является стандартным поведением в большинстве систем drag-and-drop. + /// + /// + /// Настройка смещения позволяет: + /// + /// Предотвратить перекрытие курсора визуальным элементом + /// Обеспечить лучшую видимость области под курсором + /// Создать более естественное визуальное восприятие + /// + /// + /// + /// + /// // Настройка смещения через фабрику + /// var manager = WinUIDragDropFactory.CreateManager(window, m => + /// { + /// m.DragVisualOffset = new Point(-15, -15); // Более близко к курсору + /// }); + /// + /// + /// public Point DragVisualOffset { get; set; } = new Point(-20, -20); + /// + /// Получает значение, указывающее, инициализирован ли менеджер. + /// + /// + /// true, если метод был успешно вызван; + /// в противном случае — false. + /// + /// + /// Проверка этого свойства позволяет избежать повторной инициализации менеджера + /// и гарантирует, что система перетаскивания готова к использованию. + /// + public bool IsInitialized => _initialized; + #endregion - #region Конструктор + #region Constructor + /// + /// Инициализирует новый экземпляр класса . + /// Конструктор является приватным в соответствии с шаблоном Singleton. + /// + /// + /// + /// Внутренний конструктор создает: + /// + /// Экземпляр для управления операциями перетаскивания + /// Экземпляр для визуальной обратной связи + /// + /// + /// + /// Для получения экземпляра менеджера используйте свойство . + /// + /// private WinUIDragDropManager() { _dragDropService = new DragDropService(); @@ -93,71 +210,196 @@ public sealed class WinUIDragDropManager : IDisposable #endregion - #region Публичные методы + #region Public Methods /// - /// Инициализирует систему перетаскивания для указанного окна. + /// Инициализирует систему перетаскивания для указанного окна WinUI. + /// Этот метод должен быть вызван один раз при запуске приложения. /// - /// Основное окно приложения, в котором будет работать перетаскивание. - /// - /// Выбрасывается, если менеджер был удален. + /// + /// Главное окно приложения, для которого настраивается система перетаскивания. + /// Не может быть null. + /// + /// + /// Выбрасывается, если равен null. + /// + /// + /// Выбрасывается, если менеджер уже инициализирован или был удален. /// /// - /// Этот метод должен быть вызван один раз при запуске приложения, обычно в методе - /// . + /// + /// Этот метод выполняет следующие действия: + /// + /// Настраивает хост визуальных элементов для работы с указанным окном + /// Подписывается на события сервиса перетаскивания для управления визуальной обратной связью + /// Помечает менеджер как инициализированный + /// + /// + /// + /// Метод должен быть вызван до использования любых других методов менеджера. + /// Рекомендуется вызывать его в конструкторе главного окна приложения. + /// + /// + /// + /// public MainWindow() + /// { + /// InitializeComponent(); + /// WinUIDragDropManager.Instance.Initialize(this); + /// } + /// + /// /// public void Initialize(Window window) { - if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropManager)); + if (_disposed) + throw new ObjectDisposedException(nameof(WinUIDragDropManager)); + if (_initialized) + throw new InvalidOperationException("Менеджер уже инициализирован."); + + if (window == null) + throw new ArgumentNullException(nameof(window)); + + // Инициализируем хост для работы с окном _host.Initialize(window); - // Подписываемся на события + // Подписываемся на события сервиса перетаскивания _dragDropService.DragStarted += OnDragStarted; _dragDropService.DragUpdated += OnDragUpdated; _dragDropService.DragCompleted += OnDragCompleted; _dragDropService.DragCancelled += OnDragCancelled; + + _initialized = true; } /// - /// Делает указанный элемент источником перетаскивания. + /// Настраивает указанный элемент как источник перетаскивания. /// - /// Элемент, который станет источником перетаскивания. - /// Данные, которые будут перетаскиваться. Если не указано, используются - /// DataContext или Tag элемента. + /// + /// Элемент + /// + /// Данные, которые будут перетаскиваться. Может быть null. + /// Если не указано, используются или + /// элемента. + /// + /// + /// Выбрасывается, если равен null. + /// + /// + /// Выбрасывается, если менеджер не инициализирован или был удален. + /// /// + /// + /// После вызова этого метода элемент приобретает следующие возможности: + /// + /// Реагирует на жесты перетаскивания (удержание и перемещение указателя) + /// Предоставляет указанные данные для перетаскивания + /// Интегрируется с системой визуальной обратной связи + /// + /// + /// /// Если элемент уже зарегистрирован как источник перетаскивания, метод не выполняет действий. + /// + /// + /// Для отмены регистрации используйте метод . + /// /// public void MakeDragSource(FrameworkElement element, object? dragData = null) { - if (_disposed || _dragSources.ContainsKey(element)) return; + ValidateManagerState(); - var behavior = new Behaviors.WinUIDragSourceBehavior(_dragDropService, _host); + if (element == null) + throw new ArgumentNullException(nameof(element)); + + // Если элемент уже зарегистрирован, ничего не делаем + if (_dragSources.ContainsKey(element)) + return; + + // Создаем и настраиваем поведение + var behavior = new WinUIDragSourceBehavior(_dragDropService, _host); behavior.Attach(element, dragData); _dragSources[element] = behavior; } /// - /// Делает указанный элемент целью сброса. + /// Настраивает указанный элемент как цель сброса. /// - /// Элемент, который станет целью сброса. + /// + /// Элемент , который должен стать целью сброса. + /// Не может быть null. + /// + /// + /// Выбрасывается, если равен null. + /// + /// + /// Выбрасывается, если менеджер не инициализирован или был удален. + /// /// + /// + /// После вызова этого метода элемент приобретает следующие возможности: + /// + /// Принимает данные, сбрасываемые пользователем + /// Предоставляет визуальную обратную связь при наведении + /// Автоматически обновляет свои границы при изменении размера или позиции + /// + /// + /// + /// По умолчанию цель принимает данные любого типа. Для настройки фильтрации типов + /// используйте методы и + /// . + /// + /// /// Если элемент уже зарегистрирован как цель сброса, метод не выполняет действий. + /// + /// + /// Для отмены регистрации используйте метод . + /// /// public void MakeDropTarget(FrameworkElement element) { - if (_disposed || _dropTargets.ContainsKey(element)) return; + ValidateManagerState(); - var behavior = new Behaviors.WinUIDropTargetBehavior(_dragDropService, _host); + if (element == null) + throw new ArgumentNullException(nameof(element)); + + // Если элемент уже зарегистрирован, ничего не делаем + if (_dropTargets.ContainsKey(element)) + return; + + // Создаем и настраиваем поведение + var behavior = new WinUIDropTargetBehavior(_dragDropService, _host); behavior.Attach(element); _dropTargets[element] = behavior; } /// - /// Удаляет возможность перетаскивания. + /// Удаляет возможность перетаскивания у указанного элемента. /// + /// + /// Элемент, у которого нужно отключить возможность перетаскивания. + /// Если элемент не зарегистрирован как источник перетаскивания, метод не выполняет действий. + /// + /// + /// + /// Этот метод выполняет следующие действия: + /// + /// Открепляет поведение перетаскивания от элемента + /// Отписывается от всех событий элемента + /// Удаляет элемент из внутреннего словаря источников + /// Освобождает ресурсы, связанные с поведением + /// + /// + /// + /// Метод безопасен для вызова даже если элемент не был зарегистрирован как источник. + /// + /// public void RemoveDragSource(FrameworkElement element) { + if (element == null || _disposed || !_dragSources.ContainsKey(element)) + return; + if (_dragSources.Remove(element, out var behavior)) { behavior.Detach(); @@ -165,10 +407,31 @@ public sealed class WinUIDragDropManager : IDisposable } /// - /// Удаляет возможность сброса. + /// Удаляет возможность сброса у указанного элемента. /// + /// + /// Элемент, у которого нужно отключить возможность сброса. + /// Если элемент не зарегистрирован как цель сброса, метод не выполняет действий. + /// + /// + /// + /// Этот метод выполняет следующие действия: + /// + /// Открепляет поведение цели сброса от элемента + /// Восстанавливает свойство = false + /// Удаляет элемент из внутреннего словаря целей + /// Освобождает ресурсы, связанные с поведением + /// + /// + /// + /// Метод безопасен для вызова даже если элемент не был зарегистрирован как цель. + /// + /// public void RemoveDropTarget(FrameworkElement element) { + if (element == null || _disposed || !_dropTargets.ContainsKey(element)) + return; + if (_dropTargets.Remove(element, out var behavior)) { behavior.Detach(); @@ -176,16 +439,35 @@ public sealed class WinUIDragDropManager : IDisposable } /// - /// Очищает все регистрации. + /// Очищает все регистрации источников и целей перетаскивания. /// + /// + /// + /// Этот метод полезен в следующих сценариях: + /// + /// При перезагрузке содержимого интерфейса + /// При смене контекста данных + /// При освобождении ресурсов перед удалением менеджера + /// + /// + /// + /// После вызова этого метода все элементы теряют возможность участвовать в операциях + /// перетаскивания. Для восстановления функциональности необходимо повторно + /// зарегистрировать элементы через и . + /// + /// public void Clear() { + if (_disposed) return; + + // Открепляем все источники foreach (var behavior in _dragSources.Values) { behavior.Detach(); } _dragSources.Clear(); + // Открепляем все цели foreach (var behavior in _dropTargets.Values) { behavior.Detach(); @@ -195,27 +477,37 @@ public sealed class WinUIDragDropManager : IDisposable #endregion - #region Обработчики событий + #region Event Handlers - private void OnDragStarted(object? sender, DragStartedEventArgs e) + /// + /// Обрабатывает событие начала перетаскивания. + /// Создает и отображает визуальный элемент для обратной связи. + /// + private void OnDragStarted(object? sender, Core.DragDrop.Services.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) + /// + /// Обрабатывает событие обновления позиции перетаскивания. + /// Обновляет позицию визуального элемента для следования за курсором. + /// + private void OnDragUpdated(object? sender, Core.DragDrop.Services.DragUpdatedEventArgs e) { if (_currentDragVisual != null) { @@ -228,16 +520,27 @@ public sealed class WinUIDragDropManager : IDisposable } } - private void OnDragCompleted(object? sender, DragCompletedEventArgs e) + /// + /// Обрабатывает событие завершения перетаскивания. + /// Очищает визуальные элементы и восстанавливает состояние. + /// + private void OnDragCompleted(object? sender, Core.DragDrop.Services.DragCompletedEventArgs e) { CleanupDragVisual(); } - private void OnDragCancelled(object? sender, DragCancelledEventArgs e) + /// + /// Обрабатывает событие отмены перетаскивания. + /// Очищает визуальные элементы и восстанавливает состояние. + /// + private void OnDragCancelled(object? sender, Core.DragDrop.Services.DragCancelledEventArgs e) { CleanupDragVisual(); } + /// + /// Освобождает ресурсы визуального элемента перетаскивания. + /// private void CleanupDragVisual() { if (_currentDragVisual != null) @@ -249,24 +552,72 @@ public sealed class WinUIDragDropManager : IDisposable #endregion - #region IDisposable + #region Helper Methods + /// + /// Проверяет состояние менеджера перед выполнением операций. + /// + /// + /// Выбрасывается, если менеджер был удален. + /// + /// + /// Выбрасывается, если менеджер не инициализирован. + /// + private void ValidateManagerState() + { + if (_disposed) + throw new ObjectDisposedException(nameof(WinUIDragDropManager)); + + if (!_initialized) + throw new InvalidOperationException( + "Менеджер не инициализирован. Вызовите метод Initialize перед использованием."); + } + + #endregion + + #region IDisposable Implementation + + /// + /// Освобождает все ресурсы, используемые . + /// + /// + /// + /// Этот метод выполняет следующие действия: + /// + /// Отписывается от всех событий сервиса перетаскивания + /// Очищает все зарегистрированные источники и цели + /// Освобождает ресурсы хоста визуальных элементов + /// Освобождает ресурсы сервиса перетаскивания + /// + /// + /// + /// После вызова этого метода менеджер перестает быть пригодным для использования. + /// Попытка использовать методы менеджера после удаления приведет к исключению + /// . + /// + /// + /// Метод безопасен для многократного вызова. + /// + /// public void Dispose() { if (_disposed) return; - Clear(); - // Отписываемся от событий _dragDropService.DragStarted -= OnDragStarted; _dragDropService.DragUpdated -= OnDragUpdated; _dragDropService.DragCompleted -= OnDragCompleted; _dragDropService.DragCancelled -= OnDragCancelled; + // Очищаем все регистрации + Clear(); + + // Освобождаем ресурсы _dragDropService.Dispose(); _host.Dispose(); _disposed = true; + _initialized = false; GC.SuppressFinalize(this); }