# 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(); ``` ### 2. Создание источника перетаскивания ```csharp public class ItemDragSource : IDragSource { private readonly Item _item; public ItemDragSource(Item item) { _item = item; } public async Task 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 _items; public async Task 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. Пожалуйста, указывайте: - Версию библиотеки - Шаги для воспроизведения - Ожидаемое и фактическое поведение - Пример кода для демонстрации проблемы