DragAndDrop core
This commit is contained in:
832
Lattice.Core.DragDrop/README.md
Normal file
832
Lattice.Core.DragDrop/README.md
Normal file
@@ -0,0 +1,832 @@
|
||||
# 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/ # Аргументы событий
|
||||
└── Utilities/ # Утилиты и фабрики
|
||||
├── DragDropUtilities.cs # Синхронные утилиты
|
||||
└── AsyncDragDropUtilities.cs # Асинхронные утилиты
|
||||
```
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### 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. **Инструменты разработчика** (дебаггер, профилировщик)
|
||||
Reference in New Issue
Block a user