313 lines
12 KiB
Markdown
313 lines
12 KiB
Markdown
# Lattice.Core.DragDrop
|
||
|
||
Библиотека для реализации drag-and-drop (перетаскивания) в приложениях на .NET.
|
||
|
||
## 📋 Обзор
|
||
|
||
Lattice.Core.DragDrop предоставляет полнофункциональную, асинхронную и потокобезопасную систему для реализации операций перетаскивания в пользовательских интерфейсах. Библиотека построена на принципах разделения ответственности и поддерживает сложные сценарии перетаскивания с минимальными усилиями со стороны разработчика.
|
||
|
||
### ✨ Основные возможности
|
||
|
||
- ✅ **Полностью асинхронный API** - все операции поддерживают async/await
|
||
- ✅ **Потокобезопасность** - безопасная работа в многопоточных средах
|
||
- ✅ **Расширяемая архитектура** - легко добавлять новые типы источников и целей
|
||
- ✅ **Подробные события** - полный контроль над жизненным циклом операций
|
||
- ✅ **Статистика и мониторинг** - встроенный сбор метрик использования
|
||
- ✅ **Поддержка CancellationToken** - корректная отмена длительных операций
|
||
- ✅ **Независимость от UI-фреймворков** - может использоваться с любым представлением
|
||
|
||
## 🏗️ Архитектура
|
||
|
||
### Основные компоненты
|
||
|
||
#### 1. **IDragSource**
|
||
Интерфейс для объектов, которые могут быть источником данных при перетаскивании. Определяет:
|
||
- Возможность начала перетаскивания
|
||
- Подготовку данных для передачи
|
||
- Реакцию на завершение или отмену операции
|
||
|
||
#### 2. **IDropTarget**
|
||
Интерфейс для объектов, которые могут принимать сброшенные данные. Определяет:
|
||
- Проверку совместимости данных
|
||
- Визуальную обратную связь при наведении
|
||
- Обработку сброшенных данных
|
||
|
||
#### 3. **DragDropService**
|
||
Центральный сервис, координирующий все операции. Отвечает за:
|
||
- Регистрацию и управление целями сброса
|
||
- Оркестрацию жизненного цикла операций
|
||
- Распространение событий между компонентами
|
||
- Сбор статистики и обработку ошибок
|
||
|
||
#### 4. **Модели данных**
|
||
- **DragInfo** - информация о начале перетаскивания
|
||
- **DropInfo** - информация о потенциальном сбросе
|
||
- **DragDropEffects** - перечисление возможных эффектов
|
||
|
||
## 🚀 Быстрый старт
|
||
|
||
### 1. Установка
|
||
|
||
```csharp
|
||
// Пример регистрации в DI-контейнере
|
||
services.AddSingleton<IDragDropService, DragDropService>();
|
||
```
|
||
|
||
### 2. Создание источника перетаскивания
|
||
|
||
```csharp
|
||
public class ItemDragSource : IDragSource
|
||
{
|
||
private readonly Item _item;
|
||
|
||
public ItemDragSource(Item item)
|
||
{
|
||
_item = item;
|
||
}
|
||
|
||
public async Task<DragInfo?> TryStartDragAsync(Point startPosition, CancellationToken ct)
|
||
{
|
||
// Проверяем, можно ли начать перетаскивание
|
||
if (!_item.CanBeDragged)
|
||
return null;
|
||
|
||
// Создаем информацию о перетаскивании
|
||
return new DragInfo(
|
||
data: _item,
|
||
allowedEffects: DragDropEffects.Copy | DragDropEffects.Move,
|
||
startPosition: startPosition,
|
||
source: this
|
||
);
|
||
}
|
||
|
||
public async Task OnDragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken ct)
|
||
{
|
||
if (effects == DragDropEffects.Move)
|
||
{
|
||
// Удаляем элемент при перемещении
|
||
await _repository.DeleteAsync(_item.Id, ct);
|
||
}
|
||
}
|
||
|
||
public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken ct)
|
||
{
|
||
// Очистка ресурсов при отмене
|
||
return Task.CompletedTask;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. Создание цели сброса
|
||
|
||
```csharp
|
||
public class ContainerDropTarget : IDropTarget
|
||
{
|
||
private readonly ObservableCollection<Item> _items;
|
||
|
||
public async Task<bool> CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct)
|
||
{
|
||
// Проверяем тип данных
|
||
if (dropInfo.Data is not Item item)
|
||
return false;
|
||
|
||
// Проверяем бизнес-правила
|
||
return await _validator.CanAddItemAsync(item, ct);
|
||
}
|
||
|
||
public async Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct)
|
||
{
|
||
// Обновляем визуальную обратную связь
|
||
dropInfo.SuggestedEffects = DragDropEffects.Move;
|
||
dropInfo.ShowVisualFeedback = true;
|
||
}
|
||
|
||
public async Task OnDropAsync(DropInfo dropInfo, CancellationToken ct)
|
||
{
|
||
var item = (Item)dropInfo.Data;
|
||
await _items.AddAsync(item, ct);
|
||
|
||
// Помечаем как обработанное
|
||
dropInfo.MarkAsHandled();
|
||
}
|
||
|
||
public Task OnDragLeaveAsync(CancellationToken ct)
|
||
{
|
||
// Очищаем визуальную обратную связь
|
||
return Task.CompletedTask;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. Регистрация и использование сервиса
|
||
|
||
```csharp
|
||
public class MainViewModel
|
||
{
|
||
private readonly IDragDropService _dragDropService;
|
||
|
||
public MainViewModel(IDragDropService dragDropService)
|
||
{
|
||
_dragDropService = dragDropService;
|
||
|
||
// Регистрация цели сброса
|
||
_targetId = _dragDropService.RegisterDropTarget(
|
||
target: new ContainerDropTarget(_items),
|
||
bounds: new Rect(0, 0, 400, 300),
|
||
priority: 0,
|
||
group: "main-container"
|
||
);
|
||
|
||
// Подписка на события
|
||
_dragDropService.DragStarted += OnDragStarted;
|
||
_dragDropService.DragCompleted += OnDragCompleted;
|
||
_dragDropService.ErrorOccurred += OnError;
|
||
}
|
||
|
||
// Обработка мышиных событий
|
||
public async Task OnMouseDown(Point position)
|
||
{
|
||
var source = new ItemDragSource(selectedItem);
|
||
await _dragDropService.StartDragAsync(source, position);
|
||
}
|
||
|
||
public async Task OnMouseMove(Point position)
|
||
{
|
||
await _dragDropService.UpdateDragAsync(position);
|
||
}
|
||
|
||
public async Task OnMouseUp(Point position)
|
||
{
|
||
var effects = await _dragDropService.EndDragAsync(position);
|
||
// Обработка результатов
|
||
}
|
||
}
|
||
```
|
||
|
||
## 📊 Статистика и мониторинг
|
||
|
||
```csharp
|
||
// Получение статистики использования
|
||
var stats = _dragDropService.GetStats();
|
||
|
||
Console.WriteLine($"Всего операций: {stats.TotalDragOperations}");
|
||
Console.WriteLine($"Успешных сбросов: {stats.SuccessfulDrops}");
|
||
Console.WriteLine($"Отменено операций: {stats.CancelledOperations}");
|
||
Console.WriteLine($"Среднее время операции: {stats.AverageOperationTime}");
|
||
```
|
||
|
||
## ⚙️ Конфигурация
|
||
|
||
### Параметры сервиса
|
||
|
||
```csharp
|
||
// Настройка через свойства сервиса
|
||
_dragDropService.DragStartThreshold = 5.0; // Порог в пикселях
|
||
_dragDropService.EnableAsyncOperations = true;
|
||
_dragDropService.AsyncOperationTimeout = 3000; // 3 секунды
|
||
```
|
||
|
||
### Константы по умолчанию
|
||
|
||
Все значения по умолчанию определены в классе `DragDropConstants`:
|
||
- `DefaultDragThreshold`: 3.0 пикселей
|
||
- `DefaultAsyncTimeout`: 5000 миллисекунд
|
||
- `TargetLifetimeMinutes`: 10 минут
|
||
|
||
## 🔧 Расширенные сценарии
|
||
|
||
### Группировка целей сброса
|
||
|
||
```csharp
|
||
// Регистрация группы целей
|
||
_dragDropService.RegisterDropTarget(target1, bounds1, group: "panel");
|
||
_dragDropService.RegisterDropTarget(target2, bounds2, group: "panel");
|
||
|
||
// Массовая отмена регистрации
|
||
_dragDropService.UnregisterDropTargetsInGroup("panel");
|
||
```
|
||
|
||
### Обработка ошибок
|
||
|
||
```csharp
|
||
private void OnError(object sender, DragDropErrorEventArgs e)
|
||
{
|
||
_logger.LogError(e.Exception,
|
||
"Ошибка в операции {Operation}",
|
||
e.Operation);
|
||
|
||
// Уведомление пользователя
|
||
_notificationService.ShowError("Ошибка при перетаскивании");
|
||
}
|
||
```
|
||
|
||
### Кастомизация эффектов
|
||
|
||
```csharp
|
||
// Использование расширений для работы с эффектами
|
||
if (effects.CanCopy())
|
||
{
|
||
// Логика для копирования
|
||
}
|
||
|
||
if (effects.CanMove())
|
||
{
|
||
// Логика для перемещения
|
||
}
|
||
|
||
// Определение эффекта по модификаторам клавиш
|
||
var effect = DragDropEffectsExtensions.GetEffectFromKeys(
|
||
controlKey: Keyboard.IsControlDown,
|
||
shiftKey: Keyboard.IsShiftDown,
|
||
altKey: Keyboard.IsAltDown
|
||
);
|
||
```
|
||
|
||
## 📝 Best Practices
|
||
|
||
1. **Производительность**
|
||
- Методы `CanAcceptDropAsync` и `OnDragOverAsync` вызываются часто, оптимизируйте их
|
||
- Избегайте синхронных операций в обработчиках
|
||
- Используйте кэширование при проверке типов данных
|
||
|
||
2. **Безопасность**
|
||
- Всегда проверяйте тип данных в `CanAcceptDropAsync`
|
||
- Валидируйте бизнес-правила перед обработкой сброса
|
||
- Используйте CancellationToken для отмены длительных операций
|
||
|
||
3. **Пользовательский опыт**
|
||
- Предоставляйте визуальную обратную связь через `DropInfo.ShowVisualFeedback`
|
||
- Используйте `DropInfo.VisualFeedbackData` для кастомизации отображения
|
||
- Обрабатывайте отмену операций для очистки временных данных
|
||
|
||
## 🔄 Миграция
|
||
|
||
### С версии 1.x на 2.0
|
||
|
||
1. **Интерфейсы переименованы:**
|
||
- `CanStartDragAsync` → `TryStartDragAsync`
|
||
- `DragCompletedAsync` → `OnDragCompletedAsync`
|
||
- `DragCancelledAsync` → `OnDragCancelledAsync`
|
||
|
||
2. **Классы EventArgs объединены:**
|
||
- Все события наследуются от `DragEventArgs`
|
||
- Упрощена иерархия классов событий
|
||
|
||
3. **Удалены устаревшие компоненты:**
|
||
- `AsyncDragDropUtilities` удален
|
||
- `ServiceCollectionExtensions` удален
|
||
|
||
## 📄 Лицензия
|
||
|
||
Библиотека распространяется под лицензией MIT. Подробности см. в файле LICENSE.
|
||
|
||
## 🤝 Вклад в разработку
|
||
|
||
Мы приветствуем вклад в развитие библиотеки. Перед отправкой pull request ознакомьтесь с руководством по контрибьютингу.
|
||
|
||
## 🐛 Отчеты об ошибках
|
||
|
||
Для сообщения об ошибках используйте Issues на GitHub. Пожалуйста, указывайте:
|
||
- Версию библиотеки
|
||
- Шаги для воспроизведения
|
||
- Ожидаемое и фактическое поведение
|
||
- Пример кода для демонстрации проблемы |