Files
Lattice/Lattice.UI.DragDrop.WinUI

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. Установка

// Пример регистрации в DI-контейнере
services.AddSingleton<IDragDropService, DragDropService>();

2. Создание источника перетаскивания

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. Создание цели сброса

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. Регистрация и использование сервиса

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);
        // Обработка результатов
    }
}

📊 Статистика и мониторинг

// Получение статистики использования
var stats = _dragDropService.GetStats();

Console.WriteLine($"Всего операций: {stats.TotalDragOperations}");
Console.WriteLine($"Успешных сбросов: {stats.SuccessfulDrops}");
Console.WriteLine($"Отменено операций: {stats.CancelledOperations}");
Console.WriteLine($"Среднее время операции: {stats.AverageOperationTime}");

⚙️ Конфигурация

Параметры сервиса

// Настройка через свойства сервиса
_dragDropService.DragStartThreshold = 5.0; // Порог в пикселях
_dragDropService.EnableAsyncOperations = true;
_dragDropService.AsyncOperationTimeout = 3000; // 3 секунды

Константы по умолчанию

Все значения по умолчанию определены в классе DragDropConstants:

  • DefaultDragThreshold: 3.0 пикселей
  • DefaultAsyncTimeout: 5000 миллисекунд
  • TargetLifetimeMinutes: 10 минут

🔧 Расширенные сценарии

Группировка целей сброса

// Регистрация группы целей
_dragDropService.RegisterDropTarget(target1, bounds1, group: "panel");
_dragDropService.RegisterDropTarget(target2, bounds2, group: "panel");

// Массовая отмена регистрации
_dragDropService.UnregisterDropTargetsInGroup("panel");

Обработка ошибок

private void OnError(object sender, DragDropErrorEventArgs e)
{
    _logger.LogError(e.Exception, 
        "Ошибка в операции {Operation}", 
        e.Operation);
    
    // Уведомление пользователя
    _notificationService.ShowError("Ошибка при перетаскивании");
}

Кастомизация эффектов

// Использование расширений для работы с эффектами
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. Интерфейсы переименованы:

    • CanStartDragAsyncTryStartDragAsync
    • DragCompletedAsyncOnDragCompletedAsync
    • DragCancelledAsyncOnDragCancelledAsync
  2. Классы EventArgs объединены:

    • Все события наследуются от DragEventArgs
    • Упрощена иерархия классов событий
  3. Удалены устаревшие компоненты:

    • AsyncDragDropUtilities удален
    • ServiceCollectionExtensions удален

📄 Лицензия

Библиотека распространяется под лицензией MIT. Подробности см. в файле LICENSE.

🤝 Вклад в разработку

Мы приветствуем вклад в развитие библиотеки. Перед отправкой pull request ознакомьтесь с руководством по контрибьютингу.

🐛 Отчеты об ошибках

Для сообщения об ошибках используйте Issues на GitHub. Пожалуйста, указывайте:

  • Версию библиотеки
  • Шаги для воспроизведения
  • Ожидаемое и фактическое поведение
  • Пример кода для демонстрации проблемы