Убраны синхронные методы
This commit is contained in:
@@ -1,402 +1,345 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Lattice.Core.DragDrop.Models;
|
||||
using Lattice.Core.Geometry;
|
||||
using Lattice.UI.DragDrop.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// Поведение цели сброса для WinUI UIElement.
|
||||
/// </summary>
|
||||
public static class WinUIDropTargetBehavior
|
||||
namespace Lattice.UI.DragDrop.WinUI.Behaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор свойства для включения цели сброса.
|
||||
/// Поведение цели сброса для элементов WinUI.
|
||||
/// Позволяет элементам принимать данные при операции перетаскивания.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsEnabledProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsEnabled",
|
||||
typeof(bool),
|
||||
typeof(WinUIDropTargetBehavior),
|
||||
new PropertyMetadata(false, OnIsEnabledChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор свойства для фильтрации принимаемых данных.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty AcceptsDataTypesProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"AcceptsDataTypes",
|
||||
typeof(Type[]),
|
||||
typeof(WinUIDropTargetBehavior),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор свойства для обработчика сброса.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DropHandlerProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"DropHandler",
|
||||
typeof(Core.DragDrop.Abstractions.IDropTarget),
|
||||
typeof(WinUIDropTargetBehavior),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор свойства для отображения визуальной обратной связи.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowVisualFeedbackProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"ShowVisualFeedback",
|
||||
typeof(bool),
|
||||
typeof(WinUIDropTargetBehavior),
|
||||
new PropertyMetadata(true));
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор свойства для стиля визуальной обратной связи.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FeedbackStyleProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"FeedbackStyle",
|
||||
typeof(Style),
|
||||
typeof(WinUIDropTargetBehavior),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор свойства для обработчика входа перетаскивания.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DragEnterHandlerProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"DragEnterHandler",
|
||||
typeof(Action<UIElement>),
|
||||
typeof(WinUIDropTargetBehavior),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор свойства для обработчика выхода перетаскивания.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DragLeaveHandlerProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"DragLeaveHandler",
|
||||
typeof(Action<UIElement>),
|
||||
typeof(WinUIDropTargetBehavior),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор свойства для обработчика сброса.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DropHandlerActionProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"DropHandlerAction",
|
||||
typeof(Action<UIElement, object>),
|
||||
typeof(WinUIDropTargetBehavior),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
// Словарь для отслеживания текущих перетаскиваемых элементов
|
||||
private static readonly Dictionary<UIElement, bool> _dragOverElements = new();
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение свойства IsEnabled для указанного элемента.
|
||||
/// </summary>
|
||||
public static bool GetIsEnabled(UIElement element) =>
|
||||
(bool)element.GetValue(IsEnabledProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение свойства IsEnabled для указанного элемента.
|
||||
/// </summary>
|
||||
public static void SetIsEnabled(UIElement element, bool value) =>
|
||||
element.SetValue(IsEnabledProperty, value);
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение свойства AcceptsDataTypes для указанного элемента.
|
||||
/// </summary>
|
||||
public static Type[] GetAcceptsDataTypes(UIElement element) =>
|
||||
(Type[])element.GetValue(AcceptsDataTypesProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение свойства AcceptsDataTypes для указанного элемента.
|
||||
/// </summary>
|
||||
public static void SetAcceptsDataTypes(UIElement element, Type[] value) =>
|
||||
element.SetValue(AcceptsDataTypesProperty, value);
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение свойства DropHandler для указанного элемента.
|
||||
/// </summary>
|
||||
public static Core.DragDrop.Abstractions.IDropTarget GetDropHandler(UIElement element) =>
|
||||
(Core.DragDrop.Abstractions.IDropTarget)element.GetValue(DropHandlerProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение свойства DropHandler для указанного элемента.
|
||||
/// </summary>
|
||||
public static void SetDropHandler(UIElement element, Core.DragDrop.Abstractions.IDropTarget value) =>
|
||||
element.SetValue(DropHandlerProperty, value);
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение свойства ShowVisualFeedback для указанного элемента.
|
||||
/// </summary>
|
||||
public static bool GetShowVisualFeedback(UIElement element) =>
|
||||
(bool)element.GetValue(ShowVisualFeedbackProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение свойства ShowVisualFeedback для указанного элемента.
|
||||
/// </summary>
|
||||
public static void SetShowVisualFeedback(UIElement element, bool value) =>
|
||||
element.SetValue(ShowVisualFeedbackProperty, value);
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение свойства FeedbackStyle для указанного элемента.
|
||||
/// </summary>
|
||||
public static Style GetFeedbackStyle(UIElement element) =>
|
||||
(Style)element.GetValue(FeedbackStyleProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает значение свойства FeedbackStyle для указанного элемента.
|
||||
/// </summary>
|
||||
public static void SetFeedbackStyle(UIElement element, Style value) =>
|
||||
element.SetValue(FeedbackStyleProperty, value);
|
||||
|
||||
/// <summary>
|
||||
/// Получает обработчик входа перетаскивания.
|
||||
/// </summary>
|
||||
public static Action<UIElement> GetDragEnterHandler(UIElement element) =>
|
||||
(Action<UIElement>)element.GetValue(DragEnterHandlerProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает обработчик входа перетаскивания.
|
||||
/// </summary>
|
||||
public static void SetDragEnterHandler(UIElement element, Action<UIElement> value) =>
|
||||
element.SetValue(DragEnterHandlerProperty, value);
|
||||
|
||||
/// <summary>
|
||||
/// Получает обработчик выхода перетаскивания.
|
||||
/// </summary>
|
||||
public static Action<UIElement> GetDragLeaveHandler(UIElement element) =>
|
||||
(Action<UIElement>)element.GetValue(DragLeaveHandlerProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает обработчик выхода перетаскивания.
|
||||
/// </summary>
|
||||
public static void SetDragLeaveHandler(UIElement element, Action<UIElement> value) =>
|
||||
element.SetValue(DragLeaveHandlerProperty, value);
|
||||
|
||||
/// <summary>
|
||||
/// Получает обработчик сброса.
|
||||
/// </summary>
|
||||
public static Action<UIElement, object> GetDropHandlerAction(UIElement element) =>
|
||||
(Action<UIElement, object>)element.GetValue(DropHandlerActionProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает обработчик сброса.
|
||||
/// </summary>
|
||||
public static void SetDropHandlerAction(UIElement element, Action<UIElement, object> value) =>
|
||||
element.SetValue(DropHandlerActionProperty, value);
|
||||
|
||||
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Это поведение должно быть прикреплено к <see cref="FrameworkElement"/>, который должен выступать в качестве цели сброса.
|
||||
/// Поведение автоматически регистрирует элемент в системе перетаскивания и обрабатывает все аспекты операции сброса.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Для использования необходимо:
|
||||
/// 1. Создать экземпляр поведения с помощью <see cref="Attach"/> или через DI.
|
||||
/// 2. Переопределить методы <see cref="CanAcceptDrop"/> и <see cref="Drop"/> для реализации логики принятия данных.
|
||||
/// 3. При необходимости переопределить <see cref="DragOver"/> для настройки визуальной обратной связи.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class WinUIDropTargetBehavior : DropTargetBehaviorBase<FrameworkElement>
|
||||
{
|
||||
if (d is UIElement element)
|
||||
private static readonly ConcurrentDictionary<FrameworkElement, WinUIDropTargetBehavior> _attachedBehaviors = new();
|
||||
private readonly WeakReference<FrameworkElement>? _weakElement;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр класса <see cref="WinUIDropTargetBehavior"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">Провайдер сервисов.</param>
|
||||
/// <remarks>
|
||||
/// Конструктор создает экземпляр поведения, но не прикрепляет его к элементу.
|
||||
/// Для прикрепления используйте метод <see cref="Attach(FrameworkElement, IServiceProvider)"/>.
|
||||
/// </remarks>
|
||||
public WinUIDropTargetBehavior(IServiceProvider serviceProvider)
|
||||
: base(serviceProvider)
|
||||
{
|
||||
if ((bool)e.NewValue)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр класса <see cref="WinUIDropTargetBehavior"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">Провайдер сервисов.</param>
|
||||
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
|
||||
/// <remarks>
|
||||
/// Конструктор создает экземпляр поведения и сразу прикрепляет его к указанному элементу.
|
||||
/// </remarks>
|
||||
public WinUIDropTargetBehavior(IServiceProvider serviceProvider, FrameworkElement element)
|
||||
: base(serviceProvider)
|
||||
{
|
||||
AssociatedElement = element ?? throw new ArgumentNullException(nameof(element));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Прикрепляет поведение к указанному элементу.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
|
||||
/// <param name="serviceProvider">Провайдер сервисов.</param>
|
||||
/// <returns>
|
||||
/// Экземпляр поведения, прикрепленного к элементу. Если к элементу уже прикреплено поведение,
|
||||
/// возвращает существующий экземпляр.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, когда <paramref name="element"/> или <paramref name="serviceProvider"/> равны null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот метод обеспечивает, что к каждому элементу прикреплен только один экземпляр поведения.
|
||||
/// Если метод вызывается повторно для того же элемента, возвращается существующий экземпляр.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Прикрепленное поведение автоматически отслеживает изменения макета элемента и обновляет
|
||||
/// его границы в системе перетаскивания.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static WinUIDropTargetBehavior Attach(FrameworkElement element, IServiceProvider serviceProvider)
|
||||
{
|
||||
if (element == null) throw new ArgumentNullException(nameof(element));
|
||||
if (serviceProvider == null) throw new ArgumentNullException(nameof(serviceProvider));
|
||||
|
||||
return _attachedBehaviors.GetOrAdd(element, key =>
|
||||
{
|
||||
EnableDrop(element);
|
||||
var behavior = new WinUIDropTargetBehavior(serviceProvider, key);
|
||||
|
||||
// Подписка на события жизненного цикла элемента
|
||||
key.Unloaded += OnElementUnloaded;
|
||||
return behavior;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Открепляет поведение от указанного элемента.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, от которого открепляется поведение.</param>
|
||||
/// <returns>
|
||||
/// true, если поведение было успешно откреплено; false, если поведение не было прикреплено к элементу.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Этот метод освобождает все ресурсы, связанные с поведением, и отписывается от событий элемента.
|
||||
/// После вызова этого метода элемент перестает быть целью сброса.
|
||||
/// </remarks>
|
||||
public static bool Detach(FrameworkElement element)
|
||||
{
|
||||
if (element == null) return false;
|
||||
|
||||
if (_attachedBehaviors.TryRemove(element, out var behavior))
|
||||
{
|
||||
element.Unloaded -= OnElementUnloaded;
|
||||
behavior.Detach();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает поведение, прикрепленное к указанному элементу.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, для которого требуется получить поведение.</param>
|
||||
/// <returns>
|
||||
/// Экземпляр поведения, прикрепленного к элементу, или null, если поведение не прикреплено.
|
||||
/// </returns>
|
||||
public static WinUIDropTargetBehavior? GetAttachedBehavior(FrameworkElement element)
|
||||
{
|
||||
_attachedBehaviors.TryGetValue(element, out var behavior);
|
||||
return behavior;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Подписывается на события элемента.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот метод подписывается на следующие события:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item><see cref="FrameworkElement.LayoutUpdated"/> - для отслеживания изменений макета</item>
|
||||
/// <item><see cref="FrameworkElement.SizeChanged"/> - для отслеживания изменений размера</item>
|
||||
/// <item><see cref="FrameworkElement.Loaded"/> - для инициализации при загрузке элемента</item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// Переопределите этот метод, чтобы добавить подписку на дополнительные события.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected override void SubscribeToEvents(FrameworkElement element)
|
||||
{
|
||||
if (element == null) return;
|
||||
|
||||
element.LayoutUpdated += OnLayoutUpdated;
|
||||
element.SizeChanged += OnSizeChanged;
|
||||
element.Loaded += OnLoaded;
|
||||
|
||||
// Если элемент уже загружен, сразу обновляем границы
|
||||
if (element.IsLoaded)
|
||||
{
|
||||
DisableDrop(element);
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnableDrop(UIElement element)
|
||||
{
|
||||
element.AllowDrop = true;
|
||||
element.DragEnter += OnDragEnter;
|
||||
element.DragOver += OnDragOver;
|
||||
element.DragLeave += OnDragLeave;
|
||||
element.Drop += OnDrop;
|
||||
}
|
||||
|
||||
private static void DisableDrop(UIElement element)
|
||||
{
|
||||
element.AllowDrop = false;
|
||||
element.DragEnter -= OnDragEnter;
|
||||
element.DragOver -= OnDragOver;
|
||||
element.DragLeave -= OnDragLeave;
|
||||
element.Drop -= OnDrop;
|
||||
}
|
||||
|
||||
private static void OnDragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
var element = sender as UIElement;
|
||||
if (element == null) return;
|
||||
|
||||
// Проверяем, можно ли принять данные
|
||||
if (CanAcceptData(element, e.DataView))
|
||||
/// <summary>
|
||||
/// Отписывается от событий элемента.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, от которого отписывается поведение.</param>
|
||||
/// <remarks>
|
||||
/// Этот метод отписывается от всех событий, на которые подписался <see cref="SubscribeToEvents"/>.
|
||||
/// </remarks>
|
||||
protected override void UnsubscribeFromEvents(FrameworkElement element)
|
||||
{
|
||||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
|
||||
if (element == null) return;
|
||||
|
||||
// Визуальная обратная связь
|
||||
ShowVisualFeedback(element, true);
|
||||
|
||||
// Вызываем пользовательский обработчик
|
||||
GetDragEnterHandler(element)?.Invoke(element);
|
||||
|
||||
// Добавляем в словарь
|
||||
_dragOverElements[element] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None;
|
||||
element.LayoutUpdated -= OnLayoutUpdated;
|
||||
element.SizeChanged -= OnSizeChanged;
|
||||
element.Loaded -= OnLoaded;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static void OnDragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
var element = sender as UIElement;
|
||||
if (element == null) return;
|
||||
|
||||
if (CanAcceptData(element, e.DataView))
|
||||
/// <summary>
|
||||
/// Получает границы элемента в экранных координатах.
|
||||
/// </summary>
|
||||
/// <param name="element">Элемент, границы которого нужно получить.</param>
|
||||
/// <returns>
|
||||
/// Прямоугольник, описывающий границы элемента в экранных координатах.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Метод использует преобразование координат через <see cref="UIElement.TransformToVisual"/>
|
||||
/// для получения глобальных координат элемента.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Если элемент не прикреплен к визуальному дереву или его границы не могут быть вычислены,
|
||||
/// возвращается пустой прямоугольник.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected override Rect GetScreenBounds(FrameworkElement element)
|
||||
{
|
||||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
|
||||
if (element == null || !element.IsLoaded)
|
||||
return Rect.Empty;
|
||||
|
||||
// Обновляем визуальную обратную связь на основе позиции
|
||||
var position = e.GetPosition(element);
|
||||
UpdateVisualFeedback(element, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static void OnDragLeave(object sender, DragEventArgs e)
|
||||
{
|
||||
var element = sender as UIElement;
|
||||
if (element == null) return;
|
||||
|
||||
// Скрываем визуальную обратную связь
|
||||
ShowVisualFeedback(element, false);
|
||||
|
||||
// Вызываем пользовательский обработчик
|
||||
GetDragLeaveHandler(element)?.Invoke(element);
|
||||
|
||||
// Удаляем из словаря
|
||||
_dragOverElements.Remove(element);
|
||||
}
|
||||
|
||||
private static void OnDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
var element = sender as UIElement;
|
||||
if (element == null) return;
|
||||
|
||||
// Скрываем визуальную обратную связь
|
||||
ShowVisualFeedback(element, false);
|
||||
|
||||
// Получаем данные
|
||||
var data = ExtractData(e.DataView);
|
||||
|
||||
// Получаем обработчик или используем встроенный
|
||||
var handler = GetDropHandler(element);
|
||||
if (handler != null)
|
||||
{
|
||||
// Создаем DropInfo для обработчика
|
||||
var dropInfo = CreateDropInfo(element, e, data);
|
||||
handler.Drop(dropInfo);
|
||||
}
|
||||
|
||||
// Вызываем пользовательский обработчик
|
||||
var actionHandler = GetDropHandlerAction(element);
|
||||
if (actionHandler != null && data != null)
|
||||
{
|
||||
actionHandler.Invoke(element, data);
|
||||
}
|
||||
|
||||
// Удаляем из словаря
|
||||
_dragOverElements.Remove(element);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static bool CanAcceptData(UIElement element, Windows.ApplicationModel.DataTransfer.DataPackageView dataView)
|
||||
{
|
||||
// Проверяем фильтр типов данных
|
||||
var acceptedTypes = GetAcceptsDataTypes(element);
|
||||
if (acceptedTypes != null && acceptedTypes.Length > 0)
|
||||
{
|
||||
// В реальной реализации нужно проверять доступные форматы данных
|
||||
// Здесь упрощенная проверка
|
||||
return dataView.AvailableFormats.Count > 0;
|
||||
}
|
||||
|
||||
// Если нет фильтра, принимаем все
|
||||
return true;
|
||||
}
|
||||
|
||||
private static object? ExtractData(Windows.ApplicationModel.DataTransfer.DataPackageView dataView)
|
||||
{
|
||||
// Упрощенная реализация извлечения данных
|
||||
// В реальном приложении нужно обрабатывать разные форматы данных
|
||||
try
|
||||
{
|
||||
if (dataView.Contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.Text))
|
||||
{
|
||||
// В WinUI 3 нужно использовать async/await, но это упрощенный пример
|
||||
// В реальном коде нужно использовать async методы
|
||||
return "Text data from drag";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Игнорируем ошибки извлечения данных
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ShowVisualFeedback(UIElement element, bool show)
|
||||
{
|
||||
if (!GetShowVisualFeedback(element)) return;
|
||||
|
||||
// Для элементов, которые являются Control, используем VisualStateManager
|
||||
if (element is Control control)
|
||||
{
|
||||
// Пытаемся перейти к состоянию "DragOver" или "Normal"
|
||||
try
|
||||
{
|
||||
VisualStateManager.GoToState(control, show ? "DragOver" : "Normal", true);
|
||||
// Получаем корневой элемент окна
|
||||
var rootVisual = element.XamlRoot?.Content as UIElement;
|
||||
if (rootVisual == null)
|
||||
return Rect.Empty;
|
||||
|
||||
// Преобразуем границы элемента в координаты корневого элемента
|
||||
var transform = element.TransformToVisual(rootVisual);
|
||||
var position = transform.TransformPoint(new Windows.Foundation.Point(0, 0));
|
||||
|
||||
return new Rect(
|
||||
position.X,
|
||||
position.Y,
|
||||
element.ActualWidth,
|
||||
element.ActualHeight);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Если состояния не определены, меняем свойства напрямую
|
||||
control.Background = show ?
|
||||
new Microsoft.UI.Xaml.Media.SolidColorBrush(Windows.UI.Color.FromArgb(30, 0, 120, 215)) :
|
||||
null;
|
||||
// В случае ошибки возвращаем пустой прямоугольник
|
||||
return Rect.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
/// <summary>
|
||||
/// Определяет, может ли элемент принять сбрасываемые данные.
|
||||
/// </summary>
|
||||
/// <param name="dropInfo">Информация о сбросе.</param>
|
||||
/// <returns>
|
||||
/// true, если элемент может принять данные; в противном случае — false.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот метод является абстрактным и должен быть переопределен в производных классах
|
||||
/// для реализации логики принятия данных.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Базовая реализация всегда возвращает false. Переопределите этот метод, чтобы определить,
|
||||
/// какие типы данных может принимать ваш элемент и при каких условиях.
|
||||
/// </para>
|
||||
/// <example>
|
||||
/// Пример реализации:
|
||||
/// <code>
|
||||
/// public override bool CanAcceptDrop(DropInfo dropInfo)
|
||||
/// {
|
||||
/// // Принимаем только строковые данные
|
||||
/// return dropInfo.Data is string;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
public override async Task<bool> CanAcceptDropAsync(DropInfo dropInfo)
|
||||
{
|
||||
// Для обычных UIElement меняем свойства напрямую
|
||||
element.Opacity = show ? 0.8 : 1.0;
|
||||
// Базовая реализация - не принимает никакие данные.
|
||||
// Переопределите этот метод в производных классах.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateVisualFeedback(UIElement element, Windows.Foundation.Point position)
|
||||
{
|
||||
// Можно добавить логику для различных зон сброса
|
||||
// Например, подсветка разных частей элемента
|
||||
}
|
||||
/// <summary>
|
||||
/// Обрабатывает сброс данных на элемент.
|
||||
/// </summary>
|
||||
/// <param name="dropInfo">Информация о сбросе.</param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот метод вызывается, когда пользователь отпускает кнопку мыши над элементом,
|
||||
/// и данные должны быть приняты.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Базовая реализация ничего не делает. Переопределите этот метод, чтобы реализовать
|
||||
/// логику обработки принятых данных.
|
||||
/// </para>
|
||||
/// <example>
|
||||
/// Пример реализации:
|
||||
/// <code>
|
||||
/// public override void Drop(DropInfo dropInfo)
|
||||
/// {
|
||||
/// if (dropInfo.Data is string text)
|
||||
/// {
|
||||
/// // Обработка текстовых данных
|
||||
/// AssociatedElement.SetValue(TextBlock.TextProperty, text);
|
||||
/// dropInfo.MarkAsHandled();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
public override async Task DropAsync(DropInfo dropInfo)
|
||||
{
|
||||
// Базовая реализация ничего не делает.
|
||||
// Переопределите этот метод в производных классах.
|
||||
}
|
||||
|
||||
private static Core.DragDrop.Models.DropInfo CreateDropInfo(UIElement element, DragEventArgs e, object? data)
|
||||
{
|
||||
var position = e.GetPosition(element);
|
||||
var screenPosition = element.TransformToVisual(null).TransformPoint(position);
|
||||
/// <summary>
|
||||
/// Освобождает ресурсы, связанные с поведением.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Этот метод отписывается от всех событий, отменяет регистрацию в сервисе перетаскивания
|
||||
/// и очищает все ресурсы.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// После вызова этого метода поведение больше не может быть использовано.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public override void Detach()
|
||||
{
|
||||
if (AssociatedElement != null && _attachedBehaviors.TryGetValue(AssociatedElement, out _))
|
||||
{
|
||||
_attachedBehaviors.TryRemove(AssociatedElement, out _);
|
||||
}
|
||||
|
||||
return new Core.DragDrop.Models.DropInfo(
|
||||
data: data,
|
||||
position: new Core.Geometry.Point(screenPosition.X, screenPosition.Y),
|
||||
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move,
|
||||
target: GetDropHandler(element)
|
||||
);
|
||||
base.Detach();
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnLayoutUpdated(object? sender, object e)
|
||||
{
|
||||
OnElementLayoutChanged();
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
OnElementLayoutChanged();
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
private static void OnElementUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
{
|
||||
Detach(element);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user