402 lines
16 KiB
C#
402 lines
16 KiB
C#
using Microsoft.UI.Xaml;
|
||
using Microsoft.UI.Xaml.Controls;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
|
||
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
|
||
|
||
/// <summary>
|
||
/// Поведение цели сброса для WinUI UIElement.
|
||
/// </summary>
|
||
public static class WinUIDropTargetBehavior
|
||
{
|
||
/// <summary>
|
||
/// Идентификатор свойства для включения цели сброса.
|
||
/// </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)
|
||
{
|
||
if (d is UIElement element)
|
||
{
|
||
if ((bool)e.NewValue)
|
||
{
|
||
EnableDrop(element);
|
||
}
|
||
else
|
||
{
|
||
DisableDrop(element);
|
||
}
|
||
}
|
||
}
|
||
|
||
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))
|
||
{
|
||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
|
||
|
||
// Визуальная обратная связь
|
||
ShowVisualFeedback(element, true);
|
||
|
||
// Вызываем пользовательский обработчик
|
||
GetDragEnterHandler(element)?.Invoke(element);
|
||
|
||
// Добавляем в словарь
|
||
_dragOverElements[element] = true;
|
||
}
|
||
else
|
||
{
|
||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None;
|
||
}
|
||
|
||
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))
|
||
{
|
||
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
|
||
|
||
// Обновляем визуальную обратную связь на основе позиции
|
||
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);
|
||
}
|
||
catch
|
||
{
|
||
// Если состояния не определены, меняем свойства напрямую
|
||
control.Background = show ?
|
||
new Microsoft.UI.Xaml.Media.SolidColorBrush(Windows.UI.Color.FromArgb(30, 0, 120, 215)) :
|
||
null;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Для обычных UIElement меняем свойства напрямую
|
||
element.Opacity = show ? 0.8 : 1.0;
|
||
}
|
||
}
|
||
|
||
private static void UpdateVisualFeedback(UIElement element, Windows.Foundation.Point position)
|
||
{
|
||
// Можно добавить логику для различных зон сброса
|
||
// Например, подсветка разных частей элемента
|
||
}
|
||
|
||
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);
|
||
|
||
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)
|
||
);
|
||
}
|
||
} |