378 lines
13 KiB
Markdown
378 lines
13 KiB
Markdown
# 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. Установка
|
||
|
||
```csharp
|
||
// В методе ConfigureServices вашего приложения
|
||
services.AddLatticeDragDrop();
|
||
```
|
||
|
||
### 2. Регистрация платформенных реализаций
|
||
|
||
```csharp
|
||
// Для WPF
|
||
services.AddDragVisualProvider<WpfDragVisualProvider>();
|
||
services.AddDropVisualAdorner<WpfDropVisualAdorner>();
|
||
services.AddDragDropHost<WpfDragDropHost>();
|
||
|
||
// Для Avalonia
|
||
services.AddDragVisualProvider<AvaloniaDragVisualProvider>();
|
||
services.AddDropVisualAdorner<AvaloniaDropVisualAdorner>();
|
||
services.AddDragDropHost<AvaloniaDragDropHost>();
|
||
```
|
||
|
||
### 3. Создание поведения источника перетаскивания
|
||
|
||
```csharp
|
||
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. Создание поведения цели сброса
|
||
|
||
```csharp
|
||
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
|
||
|
||
```csharp
|
||
public static class WpfDragDropExtensions
|
||
{
|
||
public static IServiceCollection AddWpfDragDrop(this IServiceCollection services)
|
||
{
|
||
return services
|
||
.AddLatticeDragDrop()
|
||
.AddDragVisualProvider<WpfDragVisualProvider>()
|
||
.AddDropVisualAdorner<WpfDropVisualAdorner>()
|
||
.AddDragDropHost<WpfDragDropHost>();
|
||
}
|
||
}
|
||
```
|
||
|
||
### Avalonia
|
||
|
||
```csharp
|
||
public static class AvaloniaDragDropExtensions
|
||
{
|
||
public static IServiceCollection AddAvaloniaDragDrop(this IServiceCollection services)
|
||
{
|
||
return services
|
||
.AddLatticeDragDrop()
|
||
.AddDragVisualProvider<AvaloniaDragVisualProvider>()
|
||
.AddDropVisualAdorner<AvaloniaDropVisualAdorner>()
|
||
.AddDragDropHost<AvaloniaDragDropHost>();
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🎨 Визуальная обратная связь
|
||
|
||
### Создание кастомного визуального представления
|
||
|
||
```csharp
|
||
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();
|
||
}
|
||
}
|
||
```
|
||
|
||
### Создание кастомной обратной связи
|
||
|
||
```csharp
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## ⚙️ Конфигурация
|
||
|
||
### Настройка сервисов
|
||
|
||
```csharp
|
||
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
|
||
|
||
```csharp
|
||
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
|
||
|
||
1. **Обновление интерфейсов**:
|
||
- Методы переименованы в соответствии с Core
|
||
- Добавлена поддержка CancellationToken
|
||
|
||
2. **Изменения в базовых классах**:
|
||
- `DragSourceBehaviorBase` теперь реализует новый интерфейс `IDragSource`
|
||
- `DropTargetBehaviorBase` теперь реализует новый интерфейс `IDropTarget`
|
||
|
||
3. **Регистрация сервисов**:
|
||
- Метод `AddLatticeDragDrop` теперь регистрирует только базовые сервисы
|
||
- Для платформенных реализаций используйте методы `AddDragVisualProvider`, `AddDropVisualAdorner`, `AddDragDropHost`
|
||
|
||
## 📄 Лицензия
|
||
|
||
Библиотека распространяется под лицензией MIT. Подробности см. в файле LICENSE.
|
||
|
||
## 🤝 Вклад в разработку
|
||
|
||
Для интеграции с новой UI-платформой необходимо:
|
||
1. Реализовать интерфейсы `IDragDropHost`, `IDragVisualProvider`, `IDropVisualAdorner`
|
||
2. Создать производные классы от `DragSourceBehaviorBase` и `DropTargetBehaviorBase`
|
||
3. Предоставить метод расширения для регистрации всех компонентов
|
||
|
||
## 🐛 Отчеты об ошибках
|
||
|
||
Для сообщения об ошибках используйте Issues на GitHub. Пожалуйста, указывайте:
|
||
- Платформу и версию UI-фреймворка
|
||
- Шаги для воспроизведения
|
||
- Ожидаемое и фактическое поведение
|
||
- Пример кода для демонстрации проблемы |