13 KiB
Lattice.UI.DragDrop
UI-слой библиотеки перетаскивания (drag-and-drop) для платформ .NET.
📋 Обзор
Lattice.UI.DragDrop предоставляет абстракции и базовые реализации для интеграции системы перетаскивания Lattice.Core.DragDrop с различными UI-фреймворками (WPF, Avalonia, MAUI и т.д.).
✨ Основные возможности
- ✅ Кросс-платформенные абстракции - единый API для всех UI-фреймворков
- ✅ Готовые базовые классы для быстрой реализации поведения перетаскивания
- ✅ Поддержка визуальной обратной связи через плагинную архитектуру
- ✅ Интеграция с DI-контейнерами через Microsoft.Extensions.DependencyInjection
- ✅ Безопасные реализации по умолчанию для упрощения разработки
- ✅ Расширяемость через наследование и переопределение
🏗️ Архитектура
Основные компоненты
1. IDragDropHost
Абстракция для управления визуальными элементами перетаскивания на уровне платформы.
2. IDragVisualProvider
Поставщик визуального представления для перетаскиваемых элементов.
3. IDropVisualAdorner
Элемент визуальной обратной связи при наведении на цель сброса.
4. DragSourceBehaviorBase<TElement>
Базовый класс для реализации поведения источника перетаскивания.
5. DropTargetBehaviorBase<TElement>
Базовый класс для реализации поведения цели сброса.
🚀 Быстрый старт
1. Установка
// В методе ConfigureServices вашего приложения
services.AddLatticeDragDrop();
2. Регистрация платформенных реализаций
// Для WPF
services.AddDragVisualProvider<WpfDragVisualProvider>();
services.AddDropVisualAdorner<WpfDropVisualAdorner>();
services.AddDragDropHost<WpfDragDropHost>();
// Для Avalonia
services.AddDragVisualProvider<AvaloniaDragVisualProvider>();
services.AddDropVisualAdorner<AvaloniaDropVisualAdorner>();
services.AddDragDropHost<AvaloniaDragDropHost>();
3. Создание поведения источника перетаскивания
public class MyDragSourceBehavior : DragSourceBehaviorBase<Control>
{
public MyDragSourceBehavior(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
protected override void SubscribeToEvents(Control element)
{
element.MouseDown += OnMouseDown;
element.MouseMove += OnMouseMove;
element.MouseUp += OnMouseUp;
}
protected override void UnsubscribeFromEvents(Control element)
{
element.MouseDown -= OnMouseDown;
element.MouseMove -= OnMouseMove;
element.MouseUp -= OnMouseUp;
}
public override async Task<DragInfo?> TryStartDragAsync(Point startPosition, CancellationToken ct)
{
// Проверяем, можно ли начать перетаскивание
if (!CanDrag())
return null;
// Создаем информацию о перетаскивании
return new DragInfo(
data: GetDragData(),
allowedEffects: DragDropEffects.Copy | DragDropEffects.Move,
startPosition: startPosition,
source: this
);
}
// ... остальная реализация
}
4. Создание поведения цели сброса
public class MyDropTargetBehavior : DropTargetBehaviorBase<Panel>
{
public MyDropTargetBehavior(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
protected override void SubscribeToEvents(Panel element)
{
element.SizeChanged += OnSizeChanged;
element.LayoutUpdated += OnLayoutUpdated;
}
protected override void UnsubscribeFromEvents(Panel element)
{
element.SizeChanged -= OnSizeChanged;
element.LayoutUpdated -= OnLayoutUpdated;
}
public override async Task<bool> CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct)
{
// Проверяем тип данных
if (dropInfo.Data is not MyDataType data)
return false;
// Проверяем бизнес-правила
return await ValidateDropAsync(data, ct);
}
public override async Task OnDropAsync(DropInfo dropInfo, CancellationToken ct)
{
var data = (MyDataType)dropInfo.Data;
await ProcessDropAsync(data, ct);
dropInfo.MarkAsHandled();
}
// ... остальная реализация
}
🔌 Интеграция с UI-фреймворками
WPF
public static class WpfDragDropExtensions
{
public static IServiceCollection AddWpfDragDrop(this IServiceCollection services)
{
return services
.AddLatticeDragDrop()
.AddDragVisualProvider<WpfDragVisualProvider>()
.AddDropVisualAdorner<WpfDropVisualAdorner>()
.AddDragDropHost<WpfDragDropHost>();
}
}
Avalonia
public static class AvaloniaDragDropExtensions
{
public static IServiceCollection AddAvaloniaDragDrop(this IServiceCollection services)
{
return services
.AddLatticeDragDrop()
.AddDragVisualProvider<AvaloniaDragVisualProvider>()
.AddDropVisualAdorner<AvaloniaDropVisualAdorner>()
.AddDragDropHost<AvaloniaDragDropHost>();
}
}
🎨 Визуальная обратная связь
Создание кастомного визуального представления
public class CustomDragVisualProvider : IDragVisualProvider
{
public object CreateDragVisual(DragInfo dragInfo, Point initialPosition)
{
var border = new Border
{
Background = new SolidColorBrush(Colors.LightBlue),
BorderBrush = new SolidColorBrush(Colors.Blue),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(4),
Padding = new Thickness(8),
Child = new TextBlock
{
Text = $"Dragging: {dragInfo.Data}",
Foreground = new SolidColorBrush(Colors.Black)
},
Opacity = 0.8
};
return border;
}
public void UpdateDragVisualPosition(object dragVisual, Point position)
{
if (dragVisual is FrameworkElement element)
{
element.SetValue(Canvas.LeftProperty, position.X);
element.SetValue(Canvas.TopProperty, position.Y);
}
}
public void ReleaseDragVisual(object dragVisual)
{
if (dragVisual is IDisposable disposable)
disposable.Dispose();
}
}
Создание кастомной обратной связи
public class CustomDropVisualAdorner : IDropVisualAdorner
{
private Border? _adorner;
public void Show(DropInfo dropInfo, Rect targetBounds)
{
_adorner = new Border
{
Background = new SolidColorBrush(Colors.Transparent),
BorderBrush = new SolidColorBrush(Colors.Green),
BorderThickness = new Thickness(2),
CornerRadius = new CornerRadius(4)
};
// Установка позиции и размера
Canvas.SetLeft(_adorner, targetBounds.X);
Canvas.SetTop(_adorner, targetBounds.Y);
_adorner.Width = targetBounds.Width;
_adorner.Height = targetBounds.Height;
// Добавление в визуальное дерево
AdornerLayer.GetAdornerLayer(targetElement)?.Add(_adorner);
}
public void Update(DropInfo dropInfo)
{
if (_adorner != null)
{
// Обновление стиля в зависимости от эффекта
var brush = dropInfo.SuggestedEffects.HasFlag(DragDropEffects.Move)
? Colors.Green
: Colors.Blue;
_adorner.BorderBrush = new SolidColorBrush(brush);
}
}
public void Hide()
{
if (_adorner != null)
{
var layer = AdornerLayer.GetAdornerLayer(_adorner);
layer?.Remove(_adorner);
_adorner = null;
}
}
}
⚙️ Конфигурация
Настройка сервисов
public void ConfigureServices(IServiceCollection services)
{
// Базовая регистрация
services.AddLatticeDragDrop();
// Платформенные реализации
services.AddDragVisualProvider<PlatformDragVisualProvider>();
services.AddDropVisualAdorner<PlatformDropVisualAdorner>();
services.AddDragDropHost<PlatformDragDropHost>();
// Регистрация поведений
services.AddTransient<DragSourceBehaviorBase<UIElement>, MyDragSourceBehavior>();
services.AddTransient<DropTargetBehaviorBase<Panel>, MyDropTargetBehavior>();
}
Использование с ViewModel
public class MainViewModel
{
private readonly IServiceProvider _serviceProvider;
public MainViewModel(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void AttachDragBehavior(UIElement element)
{
var behavior = _serviceProvider.GetRequiredService<DragSourceBehaviorBase<UIElement>>();
behavior.AssociatedElement = element;
}
public void AttachDropBehavior(Panel panel)
{
var behavior = _serviceProvider.GetRequiredService<DropTargetBehaviorBase<Panel>>();
behavior.AssociatedElement = panel;
}
}
📝 Best Practices
1. Эффективность визуального представления
- Создавайте легковесные визуальные элементы для плавной анимации
- Используйте кэширование для часто используемых представлений
- Избегайте сложных преобразований и эффектов
2. Обработка событий
- Всегда отписывайтесь от событий при откреплении поведения
- Используйте слабые ссылки для предотвращения утечек памяти
- Обрабатывайте исключения в обработчиках событий
3. Ресурсы
- Освобождайте графические ресурсы в методах Release/Dispose
- Используйте пулы объектов для часто создаваемых элементов
- Мониторьте использование памяти при активном перетаскивании
4. Пользовательский опыт
- Предоставляйте понятную визуальную обратную связь
- Поддерживайте настраиваемые стили и темы
- Обеспечивайте плавность анимации даже на слабых устройствах
🔄 Миграция
С версии 1.x на 2.0
-
Обновление интерфейсов:
- Методы переименованы в соответствии с Core
- Добавлена поддержка CancellationToken
-
Изменения в базовых классах:
DragSourceBehaviorBaseтеперь реализует новый интерфейсIDragSourceDropTargetBehaviorBaseтеперь реализует новый интерфейсIDropTarget
-
Регистрация сервисов:
- Метод
AddLatticeDragDropтеперь регистрирует только базовые сервисы - Для платформенных реализаций используйте методы
AddDragVisualProvider,AddDropVisualAdorner,AddDragDropHost
- Метод
📄 Лицензия
Библиотека распространяется под лицензией MIT. Подробности см. в файле LICENSE.
🤝 Вклад в разработку
Для интеграции с новой UI-платформой необходимо:
- Реализовать интерфейсы
IDragDropHost,IDragVisualProvider,IDropVisualAdorner - Создать производные классы от
DragSourceBehaviorBaseиDropTargetBehaviorBase - Предоставить метод расширения для регистрации всех компонентов
🐛 Отчеты об ошибках
Для сообщения об ошибках используйте Issues на GitHub. Пожалуйста, указывайте:
- Платформу и версию UI-фреймворка
- Шаги для воспроизведения
- Ожидаемое и фактическое поведение
- Пример кода для демонстрации проблемы