829 lines
26 KiB
Markdown
829 lines
26 KiB
Markdown
# Lattice.Core.DragDrop
|
||
|
||
Профессиональная, асинхронная система перетаскивания для .NET приложений. Полностью потокобезопасная, расширяемая архитектура с поддержкой кросс-платформенности.
|
||
|
||
## 📋 Особенности
|
||
|
||
- ✅ **Полная асинхронная поддержка** - async/await для всех операций
|
||
- ✅ **Потокобезопасность** - `ReaderWriterLockSlim` для эффективной синхронизации
|
||
- ✅ **Производительность** - Оптимизированные алгоритмы, кэширование, минимальные аллокации
|
||
- ✅ **Расширяемость** - Легкая интеграция с любыми UI фреймворками
|
||
- ✅ **Надежность** - Таймауты, обработка ошибок, корректное освобождение ресурсов
|
||
- ✅ **Статистика** - Встроенный мониторинг производительности
|
||
|
||
## 🏗️ Архитектура
|
||
|
||
### Основные компоненты
|
||
|
||
```
|
||
Lattice.Core.DragDrop/
|
||
├── Abstractions/ # Интерфейсы
|
||
│ ├── IDragSource.cs # Источник перетаскивания (синхронный)
|
||
│ ├── IAsyncDragSource.cs # Асинхронный источник
|
||
│ ├── IDropTarget.cs # Цель сброса (синхронная)
|
||
│ └── IAsyncDropTarget.cs # Асинхронная цель
|
||
├── Enums/ # Перечисления
|
||
├── Exceptions/ # Исключения с кодами ошибок
|
||
├── Extensions/ # Расширения для DI
|
||
├── Models/ # Модели данных
|
||
│ ├── DragInfo.cs # Информация о перетаскивании
|
||
│ └── DropInfo.cs # Информация о сбросе
|
||
└── Services/ # Сервисы
|
||
├── IDragDropService.cs # Основной интерфейс
|
||
├── DragDropService.cs # Реализация сервиса
|
||
└── EventArgs/ # Аргументы событий
|
||
```
|
||
|
||
## 🚀 Быстрый старт
|
||
|
||
### 1. Установка
|
||
|
||
```csharp
|
||
// Добавьте проект Lattice.Core.DragDrop в ваше решение
|
||
// или создайте NuGet пакет
|
||
```
|
||
|
||
### 2. Базовое использование
|
||
|
||
```csharp
|
||
using Lattice.Core.DragDrop;
|
||
using Lattice.Core.DragDrop.Abstractions;
|
||
using Lattice.Core.DragDrop.Services;
|
||
using Lattice.Core.Geometry;
|
||
|
||
// Создаем сервис
|
||
var dragDropService = new DragDropService();
|
||
|
||
// Создаем простой источник перетаскивания
|
||
var dragSource = DragDropUtilities.CreateSimpleDragSource(
|
||
dataProvider: () => "Example Data",
|
||
canDrag: () => true,
|
||
onCompleted: (dragInfo, effects) =>
|
||
Console.WriteLine($"Drag completed with effects: {effects}"),
|
||
onCancelled: dragInfo =>
|
||
Console.WriteLine("Drag cancelled")
|
||
);
|
||
|
||
// Создаем простую цель сброса
|
||
var dropTarget = DragDropUtilities.CreateSimpleDropTarget(
|
||
canAccept: dropInfo => dropInfo.Data is string,
|
||
onDragOver: dropInfo =>
|
||
dropInfo.SuggestedEffects = DragDropEffects.Copy,
|
||
onDrop: dropInfo =>
|
||
{
|
||
Console.WriteLine($"Dropped: {dropInfo.Data}");
|
||
dropInfo.MarkAsHandled();
|
||
}
|
||
);
|
||
|
||
// Регистрируем цель
|
||
string targetId = dragDropService.RegisterDropTarget(
|
||
dropTarget,
|
||
new Rect(100, 100, 300, 200)
|
||
);
|
||
|
||
// Начинаем перетаскивание
|
||
bool started = dragDropService.StartDrag(
|
||
dragSource,
|
||
new Point(50, 50)
|
||
);
|
||
|
||
if (started)
|
||
{
|
||
// Обновляем позицию
|
||
dragDropService.UpdateDrag(new Point(150, 150));
|
||
|
||
// Завершаем
|
||
var effects = dragDropService.EndDrag(new Point(200, 200));
|
||
}
|
||
```
|
||
|
||
## 📖 Подробное руководство
|
||
|
||
### Сервис перетаскивания
|
||
|
||
Основной класс системы - `DragDropService`, реализующий `IDragDropService`.
|
||
|
||
```csharp
|
||
// Создание с кастомными настройками
|
||
var service = new DragDropService(options =>
|
||
{
|
||
options.DragStartThreshold = 5.0;
|
||
options.EnableAsyncOperations = true;
|
||
options.AsyncOperationTimeout = 3000;
|
||
options.EnableAutoCleanup = true;
|
||
});
|
||
|
||
// Свойства
|
||
bool isActive = service.IsDragActive; // Активна ли операция
|
||
DragInfo? currentDrag = service.CurrentDragInfo; // Текущая информация
|
||
double threshold = service.DragStartThreshold; // Порог начала
|
||
|
||
// События
|
||
service.DragStarted += OnDragStarted;
|
||
service.DragUpdated += OnDragUpdated;
|
||
service.DragCompleted += OnDragCompleted;
|
||
service.DragCancelled += OnDragCancelled;
|
||
service.ErrorOccurred += OnErrorOccurred;
|
||
|
||
// Регистрация целей
|
||
string id = service.RegisterDropTarget(
|
||
target, // IDropTarget
|
||
bounds, // Rect
|
||
priority: 1, // Приоритет (выше = выше приоритет)
|
||
group: "main" // Группа для группового удаления
|
||
);
|
||
|
||
// Обновление границ
|
||
service.UpdateDropTargetBounds(id, newBounds);
|
||
|
||
// Удаление
|
||
service.UnregisterDropTarget(id);
|
||
service.UnregisterDropTargetsInGroup("main");
|
||
```
|
||
|
||
### Асинхронное использование
|
||
|
||
```csharp
|
||
// Асинхронные методы
|
||
bool started = await service.StartDragAsync(source, startPosition);
|
||
await service.UpdateDragAsync(currentPosition);
|
||
DragDropEffects effects = await service.EndDragAsync(dropPosition);
|
||
await service.CancelDragAsync();
|
||
|
||
// Статистика
|
||
var stats = service.GetStats();
|
||
Console.WriteLine($"Operations: {stats.TotalDragOperations}");
|
||
Console.WriteLine($"Success rate: {stats.SuccessfulDrops}/{stats.TotalDragOperations}");
|
||
Console.WriteLine($"Avg time: {stats.AverageOperationTime.TotalMilliseconds}ms");
|
||
```
|
||
|
||
### Создание кастомных источников и целей
|
||
|
||
#### Синхронная реализация
|
||
|
||
```csharp
|
||
public class FileDragSource : IDragSource
|
||
{
|
||
private readonly FileInfo _file;
|
||
|
||
public FileDragSource(FileInfo file) => _file = file;
|
||
|
||
public bool CanStartDrag(out DragInfo? dragInfo)
|
||
{
|
||
// Проверяем условия
|
||
if (!_file.Exists || _file.Length > 100 * 1024 * 1024) // 100 MB limit
|
||
{
|
||
dragInfo = null;
|
||
return false;
|
||
}
|
||
|
||
// Создаем DragInfo
|
||
dragInfo = new DragInfo(
|
||
data: _file,
|
||
allowedEffects: DragDropEffects.Copy | DragDropEffects.Move,
|
||
startPosition: Point.Zero,
|
||
source: this
|
||
);
|
||
|
||
// Добавляем дополнительные параметры
|
||
dragInfo.SetParameter("FileSize", _file.Length);
|
||
dragInfo.SetParameter("MimeType", GetMimeType(_file));
|
||
|
||
return true;
|
||
}
|
||
|
||
public bool StartDrag(DragInfo dragInfo)
|
||
{
|
||
// Подготовка к перетаскиванию
|
||
// Можно создать визуальное представление и т.д.
|
||
Console.WriteLine($"Starting drag of {_file.Name}");
|
||
return true;
|
||
}
|
||
|
||
public void DragCompleted(DragInfo dragInfo, DragDropEffects effects)
|
||
{
|
||
Console.WriteLine($"File drag completed with {effects}");
|
||
|
||
if (effects == DragDropEffects.Move)
|
||
{
|
||
// Файл был перемещен - возможно, удалить оригинал
|
||
// _file.Delete();
|
||
}
|
||
}
|
||
|
||
public void DragCancelled(DragInfo dragInfo)
|
||
{
|
||
Console.WriteLine("File drag cancelled");
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Асинхронная реализация
|
||
|
||
```csharp
|
||
public class DatabaseItemDragSource : IAsyncDragSource
|
||
{
|
||
private readonly DatabaseService _db;
|
||
private readonly int _itemId;
|
||
|
||
public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
|
||
{
|
||
try
|
||
{
|
||
// Асинхронные проверки
|
||
var canDrag = await _db.CanDragItemAsync(_itemId);
|
||
if (!canDrag) return (false, null);
|
||
|
||
// Асинхронная загрузка данных
|
||
var data = await _db.GetItemForDragAsync(_itemId);
|
||
if (data == null) return (false, null);
|
||
|
||
var dragInfo = new DragInfo(
|
||
data: data,
|
||
allowedEffects: DragDropEffects.Copy | DragDropEffects.Move,
|
||
startPosition: Point.Zero,
|
||
source: this
|
||
);
|
||
|
||
return (true, dragInfo);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// Логирование ошибки
|
||
return (false, null);
|
||
}
|
||
}
|
||
|
||
public Task<bool> StartDragAsync(DragInfo dragInfo)
|
||
{
|
||
// Асинхронная подготовка
|
||
return Task.FromResult(true);
|
||
}
|
||
|
||
public async Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects)
|
||
{
|
||
// Асинхронная обработка завершения
|
||
await _db.LogDragOperationAsync(_itemId, effects);
|
||
|
||
if (effects == DragDropEffects.Move)
|
||
{
|
||
await _db.MarkItemAsMovedAsync(_itemId);
|
||
}
|
||
}
|
||
|
||
public Task DragCancelledAsync(DragInfo dragInfo)
|
||
{
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
// Синхронные методы для совместимости
|
||
public bool CanStartDrag(out DragInfo? dragInfo)
|
||
{
|
||
var result = Task.Run(() => CanStartDragAsync()).GetAwaiter().GetResult();
|
||
dragInfo = result.DragInfo;
|
||
return result.CanStart;
|
||
}
|
||
|
||
// ... остальные синхронные методы
|
||
}
|
||
```
|
||
|
||
### Работа с моделями данных
|
||
|
||
#### DragInfo
|
||
|
||
```csharp
|
||
// Создание
|
||
var dragInfo = new DragInfo(
|
||
data: myObject,
|
||
allowedEffects: DragDropEffects.Copy | DragDropEffects.Move,
|
||
startPosition: new Point(x, y),
|
||
source: this
|
||
);
|
||
|
||
// Параметры
|
||
dragInfo.SetParameter("Timestamp", DateTime.UtcNow);
|
||
dragInfo.SetParameter("UserId", currentUser.Id);
|
||
|
||
// Получение параметров
|
||
if (dragInfo.TryGetParameter<string>("Category", out var category))
|
||
{
|
||
// Используем категорию
|
||
}
|
||
|
||
// Клонирование с новой позицией
|
||
var updatedDragInfo = dragInfo.CloneWithPosition(newPosition);
|
||
|
||
// Очистка ресурсов
|
||
dragInfo.Dispose();
|
||
```
|
||
|
||
#### DropInfo
|
||
|
||
```csharp
|
||
// Создается сервисом автоматически
|
||
// Работа с DropInfo в методах цели:
|
||
|
||
public void DragOver(DropInfo dropInfo)
|
||
{
|
||
// Проверяем данные
|
||
if (dropInfo.Data is MyDataType myData)
|
||
{
|
||
// Определяем позицию относительно цели
|
||
dropInfo.DropPosition = CalculateDropPosition(dropInfo.Position);
|
||
|
||
// Предлагаем эффекты
|
||
if (CanAcceptData(myData))
|
||
{
|
||
dropInfo.SuggestedEffects = DragDropEffects.Move;
|
||
dropInfo.ShowVisualFeedback = true;
|
||
dropInfo.VisualFeedbackData = CreatePreview(myData);
|
||
}
|
||
else
|
||
{
|
||
dropInfo.SuggestedEffects = DragDropEffects.None;
|
||
}
|
||
}
|
||
}
|
||
|
||
public void Drop(DropInfo dropInfo)
|
||
{
|
||
if (dropInfo.Data is MyDataType myData)
|
||
{
|
||
// Обработка сброса
|
||
ProcessDrop(myData, dropInfo.DropPosition);
|
||
|
||
// Помечаем как обработанное
|
||
dropInfo.MarkAsHandled();
|
||
}
|
||
}
|
||
```
|
||
|
||
### Утилиты и фабрики
|
||
|
||
#### Синхронные утилиты
|
||
|
||
```csharp
|
||
// Простые реализации
|
||
var simpleSource = DragDropUtilities.CreateSimpleDragSource(
|
||
() => data,
|
||
() => true,
|
||
(dragInfo, effects) => Console.WriteLine("Completed"),
|
||
dragInfo => Console.WriteLine("Cancelled")
|
||
);
|
||
|
||
var simpleTarget = DragDropUtilities.CreateSimpleDropTarget(
|
||
dropInfo => dropInfo.Data != null,
|
||
dropInfo => dropInfo.SuggestedEffects = DragDropEffects.Copy,
|
||
dropInfo => Console.WriteLine($"Dropped: {dropInfo.Data}"),
|
||
() => Console.WriteLine("Drag left")
|
||
);
|
||
|
||
// Геометрия
|
||
double distance = DragDropUtilities.CalculateDistance(p1, p2);
|
||
bool exceeded = DragDropUtilities.HasExceededDragThreshold(start, current, threshold);
|
||
DropPosition position = DragDropUtilities.GetDropPosition(point, bounds, edgeThreshold);
|
||
|
||
// Проверка совместимости
|
||
bool compatible = DragDropUtilities.AreEffectsCompatible(sourceEffects, targetEffects);
|
||
bool typeMatch = DragDropUtilities.IsDataCompatible(data, new[] { typeof(string), typeof(int) });
|
||
```
|
||
|
||
#### Асинхронные утилиты
|
||
|
||
```csharp
|
||
// Асинхронные реализации
|
||
var asyncSource = AsyncDragDropUtilities.CreateAsyncDragSource(
|
||
async () => await LoadDataAsync(),
|
||
async () => await CanDragAsync(),
|
||
async (dragInfo, effects) => await OnCompletedAsync(dragInfo, effects),
|
||
async dragInfo => await OnCancelledAsync(dragInfo)
|
||
);
|
||
|
||
var asyncTarget = AsyncDragDropUtilities.CreateAsyncDropTarget(
|
||
async dropInfo => await CanAcceptAsync(dropInfo.Data),
|
||
async dropInfo => await OnDragOverAsync(dropInfo),
|
||
async dropInfo => await OnDropAsync(dropInfo),
|
||
async () => await OnDragLeaveAsync()
|
||
);
|
||
|
||
// Адаптеры для синхронных интерфейсов
|
||
IAsyncDragSource asyncFromSync = AsyncDragDropUtilities.CreateAsyncAdapter(syncSource);
|
||
IAsyncDropTarget asyncTargetFromSync = AsyncDragDropUtilities.CreateAsyncAdapter(syncTarget);
|
||
|
||
// Комбинированные реализации (fallback стратегия)
|
||
var combined = AsyncDragDropUtilities.Combine(
|
||
syncSource,
|
||
asyncSource,
|
||
preferAsync: true // При ошибке в async использует sync
|
||
);
|
||
|
||
// Таймауты
|
||
var result = await AsyncDragDropUtilities.ExecuteWithTimeoutAsync(
|
||
task: LongOperationAsync(),
|
||
timeout: TimeSpan.FromSeconds(5),
|
||
defaultValue: fallbackValue
|
||
);
|
||
```
|
||
|
||
### Обработка ошибок
|
||
|
||
```csharp
|
||
// Подписка на ошибки
|
||
service.ErrorOccurred += (sender, e) =>
|
||
{
|
||
Console.WriteLine($"Error in {e.Operation}: {e.Exception.Message}");
|
||
|
||
// Коды ошибок определены в DragDropErrorCodes
|
||
switch (e.ErrorCode)
|
||
{
|
||
case DragDropErrorCodes.Timeout:
|
||
Console.WriteLine("Operation timed out");
|
||
break;
|
||
case DragDropErrorCodes.SourceCannotDrag:
|
||
Console.WriteLine("Source cannot drag");
|
||
break;
|
||
case DragDropErrorCodes.TargetCannotAccept:
|
||
Console.WriteLine("Target cannot accept");
|
||
break;
|
||
}
|
||
};
|
||
|
||
// Использование в коде
|
||
try
|
||
{
|
||
await service.StartDragAsync(source, position);
|
||
}
|
||
catch (DragDropException ex)
|
||
{
|
||
// Обработка специфичных для DragDrop ошибок
|
||
Console.WriteLine($"DragDrop error {ex.ErrorCode}: {ex.Message}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// Обработка других ошибок
|
||
Console.WriteLine($"General error: {ex.Message}");
|
||
}
|
||
```
|
||
|
||
## 🔧 Интеграция с UI фреймворками
|
||
|
||
### Базовый адаптер для WinUI/WPF
|
||
|
||
```csharp
|
||
public class UIElementDragSource : IAsyncDragSource
|
||
{
|
||
private readonly FrameworkElement _element;
|
||
private readonly Func<object> _dataProvider;
|
||
|
||
public UIElementDragSource(FrameworkElement element, Func<object> dataProvider)
|
||
{
|
||
_element = element;
|
||
_dataProvider = dataProvider;
|
||
|
||
// Подписка на события
|
||
_element.PointerPressed += OnPointerPressed;
|
||
_element.PointerMoved += OnPointerMoved;
|
||
_element.PointerReleased += OnPointerReleased;
|
||
}
|
||
|
||
private Point _dragStartPosition;
|
||
private bool _isDragging;
|
||
|
||
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
|
||
{
|
||
var point = e.GetCurrentPoint(_element);
|
||
_dragStartPosition = new Point(point.Position.X, point.Position.Y);
|
||
}
|
||
|
||
private async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
|
||
{
|
||
if (_isDragging) return;
|
||
|
||
var point = e.GetCurrentPoint(_element);
|
||
var current = new Point(point.Position.X, point.Position.Y);
|
||
|
||
var distance = Math.Sqrt(
|
||
Math.Pow(current.X - _dragStartPosition.X, 2) +
|
||
Math.Pow(current.Y - _dragStartPosition.Y, 2));
|
||
|
||
if (distance > 3.0) // Порог
|
||
{
|
||
_isDragging = true;
|
||
|
||
// Начинаем перетаскивание через сервис
|
||
var service = GetDragDropService();
|
||
await service.StartDragAsync(this, ConvertToScreen(_dragStartPosition));
|
||
}
|
||
}
|
||
|
||
public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
|
||
{
|
||
var data = _dataProvider();
|
||
if (data == null) return (false, null);
|
||
|
||
var dragInfo = new DragInfo(
|
||
data,
|
||
DragDropEffects.Copy | DragDropEffects.Move,
|
||
Point.Zero,
|
||
this
|
||
);
|
||
|
||
return (true, dragInfo);
|
||
}
|
||
|
||
// ... остальная реализация
|
||
}
|
||
```
|
||
|
||
## 🧪 Тестирование
|
||
|
||
### Примеры модульных тестов
|
||
|
||
```csharp
|
||
[TestClass]
|
||
public class DragDropServiceTests
|
||
{
|
||
private DragDropService _service;
|
||
private Mock<IAsyncDragSource> _mockSource;
|
||
private Mock<IAsyncDropTarget> _mockTarget;
|
||
|
||
[TestInitialize]
|
||
public void Setup()
|
||
{
|
||
_service = new DragDropService();
|
||
_mockSource = new Mock<IAsyncDragSource>();
|
||
_mockTarget = new Mock<IAsyncDropTarget>();
|
||
}
|
||
|
||
[TestMethod]
|
||
public async Task StartDrag_ValidSource_ReturnsTrue()
|
||
{
|
||
// Arrange
|
||
var dragInfo = new DragInfo("test", DragDropEffects.Copy, Point.Zero);
|
||
_mockSource.Setup(s => s.CanStartDragAsync())
|
||
.ReturnsAsync((true, dragInfo));
|
||
_mockSource.Setup(s => s.StartDragAsync(It.IsAny<DragInfo>()))
|
||
.ReturnsAsync(true);
|
||
|
||
// Act
|
||
var result = await _service.StartDragAsync(_mockSource.Object, Point.Zero);
|
||
|
||
// Assert
|
||
Assert.IsTrue(result);
|
||
Assert.IsTrue(_service.IsDragActive);
|
||
}
|
||
|
||
[TestMethod]
|
||
public async Task UpdateDrag_FindsTarget_CallsDragOver()
|
||
{
|
||
// Arrange
|
||
var targetId = _service.RegisterDropTarget(
|
||
_mockTarget.Object,
|
||
new Rect(0, 0, 100, 100)
|
||
);
|
||
|
||
await StartTestDrag();
|
||
|
||
_mockTarget.Setup(t => t.CanAcceptDropAsync(It.IsAny<DropInfo>()))
|
||
.ReturnsAsync(true);
|
||
|
||
// Act
|
||
await _service.UpdateDragAsync(new Point(50, 50));
|
||
|
||
// Assert
|
||
_mockTarget.Verify(t => t.DragOverAsync(It.IsAny<DropInfo>()), Times.Once);
|
||
}
|
||
|
||
private async Task StartTestDrag()
|
||
{
|
||
var dragInfo = new DragInfo("test", DragDropEffects.Copy, Point.Zero);
|
||
_mockSource.Setup(s => s.CanStartDragAsync())
|
||
.ReturnsAsync((true, dragInfo));
|
||
_mockSource.Setup(s => s.StartDragAsync(It.IsAny<DragInfo>()))
|
||
.ReturnsAsync(true);
|
||
|
||
await _service.StartDragAsync(_mockSource.Object, Point.Zero);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 📊 Мониторинг и производительность
|
||
|
||
### Сбор статистики
|
||
|
||
```csharp
|
||
// Получение статистики
|
||
var stats = service.GetStats();
|
||
|
||
Console.WriteLine($"Total operations: {stats.TotalDragOperations}");
|
||
Console.WriteLine($"Successful: {stats.SuccessfulDrops}");
|
||
Console.WriteLine($"Cancelled: {stats.CancelledOperations}");
|
||
Console.WriteLine($"Errors: {stats.ErrorCount}");
|
||
Console.WriteLine($"Avg time: {stats.AverageOperationTime.TotalMilliseconds}ms");
|
||
|
||
// Мониторинг в реальном времени
|
||
private Stopwatch _operationTimer;
|
||
|
||
service.DragStarted += (s, e) =>
|
||
{
|
||
_operationTimer = Stopwatch.StartNew();
|
||
Console.WriteLine($"Drag started from {e.DragInfo.Source}");
|
||
};
|
||
|
||
service.DragCompleted += (s, e) =>
|
||
{
|
||
_operationTimer.Stop();
|
||
Console.WriteLine($"Drag completed in {_operationTimer.ElapsedMilliseconds}ms");
|
||
|
||
if (service.EnableAsyncOperations)
|
||
{
|
||
var stats = service.GetStats();
|
||
Console.WriteLine($"Success rate: {(double)stats.SuccessfulDrops / stats.TotalDragOperations:P}");
|
||
}
|
||
};
|
||
```
|
||
|
||
### Оптимизация производительности
|
||
|
||
```csharp
|
||
// 1. Настройка параметров
|
||
var service = new DragDropService(options =>
|
||
{
|
||
options.DragStartThreshold = 4.0; // Увеличить порог для предотвращения случайных перетаскиваний
|
||
options.AsyncOperationTimeout = 2000; // Уменьшить таймаут для отзывчивости
|
||
options.EnableAutoCleanup = true; // Автоочистка неиспользуемых целей
|
||
});
|
||
|
||
// 2. Группировка целей
|
||
_service.RegisterDropTarget(target1, bounds1, group: "toolbox");
|
||
_service.RegisterDropTarget(target2, bounds2, group: "toolbox");
|
||
// Быстрое удаление всех целей группы
|
||
_service.UnregisterDropTargetsInGroup("toolbox");
|
||
|
||
// 3. Приоритеты для оптимизации поиска
|
||
_service.RegisterDropTarget(importantTarget, bounds, priority: 100); // Высокий приоритет
|
||
_service.RegisterDropTarget(defaultTarget, bounds, priority: 0); // Низкий приоритет
|
||
|
||
// 4. Периодическая очистка
|
||
service.ClearAllDropTargets(); // При смене контекста
|
||
```
|
||
|
||
## 🚀 Продвинутые сценарии
|
||
|
||
### Переупорядочивание элементов
|
||
|
||
```csharp
|
||
public class ReorderableListDropTarget : IAsyncDropTarget
|
||
{
|
||
private readonly IList<object> _items;
|
||
|
||
public async Task<bool> CanAcceptDropAsync(DropInfo dropInfo)
|
||
{
|
||
return dropInfo.Data is object && _items.Contains(dropInfo.Data);
|
||
}
|
||
|
||
public async Task DropAsync(DropInfo dropInfo)
|
||
{
|
||
var item = dropInfo.Data;
|
||
var insertIndex = CalculateInsertIndex(dropInfo);
|
||
|
||
// Удаляем из старой позиции
|
||
_items.Remove(item);
|
||
|
||
// Вставляем в новую позицию
|
||
if (insertIndex < _items.Count)
|
||
_items.Insert(insertIndex, item);
|
||
else
|
||
_items.Add(item);
|
||
|
||
dropInfo.MarkAsHandled();
|
||
}
|
||
|
||
private int CalculateInsertIndex(DropInfo dropInfo)
|
||
{
|
||
// Логика определения позиции вставки на основе dropInfo.Position
|
||
// и визуального расположения элементов
|
||
return 0;
|
||
}
|
||
}
|
||
```
|
||
|
||
### Мультиселект и групповое перетаскивание
|
||
|
||
```csharp
|
||
public class MultiSelectionDragSource : IAsyncDragSource
|
||
{
|
||
private readonly IEnumerable<object> _selectedItems;
|
||
|
||
public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync()
|
||
{
|
||
if (!_selectedItems.Any()) return (false, null);
|
||
|
||
// Создаем коллекцию для перетаскивания
|
||
var dragData = new DragItemCollection(_selectedItems);
|
||
|
||
var dragInfo = new DragInfo(
|
||
dragData,
|
||
DragDropEffects.Copy | DragDropEffects.Move,
|
||
Point.Zero,
|
||
this
|
||
);
|
||
|
||
dragInfo.SetParameter("ItemCount", _selectedItems.Count());
|
||
dragInfo.SetParameter("IsMultiSelect", true);
|
||
|
||
return (true, dragInfo);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 📚 API Reference
|
||
|
||
### Основные интерфейсы
|
||
|
||
#### IDragDropService
|
||
```csharp
|
||
bool IsDragActive { get; }
|
||
DragInfo? CurrentDragInfo { get; }
|
||
IDropTarget? CurrentDropTarget { get; }
|
||
double DragStartThreshold { get; set; }
|
||
bool EnableAsyncOperations { get; set; }
|
||
|
||
// Регистрация целей
|
||
string RegisterDropTarget(IDropTarget target, Rect bounds, int priority = 0, string? group = null);
|
||
bool UpdateDropTargetBounds(string id, Rect bounds);
|
||
bool UnregisterDropTarget(string id);
|
||
void UnregisterDropTargetsInGroup(string group);
|
||
|
||
// Асинхронные операции
|
||
Task<bool> StartDragAsync(IDragSource source, Point startPosition);
|
||
Task UpdateDragAsync(Point position);
|
||
Task<DragDropEffects> EndDragAsync(Point position);
|
||
Task CancelDragAsync();
|
||
|
||
// Синхронные операции
|
||
bool StartDrag(IDragSource source, Point startPosition);
|
||
void UpdateDrag(Point position);
|
||
DragDropEffects EndDrag(Point position);
|
||
void CancelDrag();
|
||
|
||
// Утилиты
|
||
void ClearAllDropTargets();
|
||
DragDropStats GetStats();
|
||
```
|
||
|
||
#### IAsyncDragSource
|
||
```csharp
|
||
Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync();
|
||
Task<bool> StartDragAsync(DragInfo dragInfo);
|
||
Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects);
|
||
Task DragCancelledAsync(DragInfo dragInfo);
|
||
```
|
||
|
||
#### IAsyncDropTarget
|
||
```csharp
|
||
Task<bool> CanAcceptDropAsync(DropInfo dropInfo);
|
||
Task DragOverAsync(DropInfo dropInfo);
|
||
Task DropAsync(DropInfo dropInfo);
|
||
Task DragLeaveAsync();
|
||
```
|
||
|
||
### Перечисления
|
||
|
||
#### DragDropEffects
|
||
```csharp
|
||
[Flags]
|
||
None = 0
|
||
Copy = 1 << 0 // Копирование данных
|
||
Move = 1 << 1 // Перемещение данных
|
||
Link = 1 << 2 // Ссылка на данные
|
||
CopyOrMove = Copy | Move
|
||
All = Copy | Move | Link
|
||
|
||
// Методы расширения:
|
||
bool CanCopy(this DragDropEffects effects)
|
||
bool CanMove(this DragDropEffects effects)
|
||
bool CanLink(this DragDropEffects effects)
|
||
DragDropEffects GetEffectFromKeys(bool controlKey, bool shiftKey, bool altKey)
|
||
```
|
||
|
||
#### DropPosition
|
||
```csharp
|
||
Inside // Внутри элемента
|
||
Top // Сверху
|
||
Bottom // Снизу
|
||
Left // Слева
|
||
Right // Справа
|
||
Center // По центру
|
||
```
|
||
|
||
## 🔮 Планы развития
|
||
|
||
1. **Интеграция с популярными UI фреймворками** (WinUI, Uno Platform, Avalonia)
|
||
2. **Поддержка жестов** (тач, мультитач)
|
||
3. **Виртуализация** для работы с большими наборами данных
|
||
4. **Продвинутые визуальные эффекты** (анимации, превью)
|
||
5. **Source Generators** для автоматической генерации кода
|
||
6. **Инструменты разработчика** (дебаггер, профилировщик) |