Вначале всё казалось просто и понятно: есть Drag Source, который по каким-то действиям может инициировать драг-н-дроп, описав те эффекты, с которыми может оперировать Drop Target. Дальше, дроп таргет реагирует на события, такие как
DragEnter
, DragOver
, Drop
, применяя те или иные эффекты, после чего уже Drag Source реагирует на совершённые действия (например завершение операции путем дропа или отмены).Немного сумбурно, поэтому попытаюсь изобразить на картинке:
По сути две части (Drag Source и Drop Target) должны являться независимыми, но просматривая примеры использования я не обнаружил независимой реализации. Практически везде обработка действия
Move
(чаще всего удаление данных из источника) происходила в реализации метода OnDrop
. То есть сам Drop Target определял действия с Drag Source.Создадим небольшое приложение, пусть главное окно выглядит так:
<Window x:Class="DragDropTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Drag'n'Drop Test" Height="350" Width="525">
<StackPanel>
<ListBox x:Name="DragSource" SelectedValuePath="Content"
GiveFeedback="OnGiveFeedbackRaised"
QueryContinueDrag="OnQueryContinueDragRaised"
PreviewMouseMove="DragSource_OnPreviewMouseMove" >
<ListBoxItem>first</ListBoxItem>
<ListBoxItem>second</ListBoxItem>
</ListBox>
<TextBox x:Name="AddTextBox"></TextBox>
<Button Click="OnAddPressed">Add</Button>
</StackPanel>
</Window>
Я не стал сильно мучиться с тем, как именно начинать операцию Drag'n'Drop, поэтому код DragSource_OnPreviewMouseMove
выглядит следующим образом (чуть более правильно начало описывается тут):private void DragSource_OnPreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && DragSource.SelectedItem != null)
{
var data = new DataObject(DataFormats.Text, DragSource.SelectedValue);
DragDrop.DoDragDrop(DragSource, data, DragDropEffects.All);
}
}
Теперь посмотрим, что же мы можем узнавать при помощи фидбека:GiveFeedbackEventArgs
позволяют узнать, какой именно эффект сейчас используется и позволяет задать вид курсора.QueryContinueDragEventArgs
позволяет узнавать состояние операции, получить состояние клавиш на текущий момент.
private void OnGiveFeedbackRaised(object sender, GiveFeedbackEventArgs e)
{
_lastEffect = e.Effects;
}
А именно сохранение последнего применённого эффекта, который нам и предстоит обрабатывать по окончанию действия. Следующая бочка дёгтя заглючается в следующем:Вопреки логике, событие
QueryContinueDrag
с Action
равным DragAction.Drop
не приходит. Про это даже заведена бага. Для разрешения проблемы я использовал следующий грязный трюк: можно считать что Drag'n'Drop завершился в тот момент, когда левая кнопка мыши перестала быть зажатой. Мне кажется, что подобное поведение будет правильным для большинства сценариев. private void OnQueryContinueDragRaised(object sender, QueryContinueDragEventArgs e)
{
if((e.KeyStates & DragDropKeyStates.LeftMouseButton) == DragDropKeyStates.None)
{
OnEndDrag(_lastEffect);
}
}
И уже на OnEndDrag
обрабатывается завершение операции, а именно удаление элемента из списка если выбрано действие Move
.Готовое приложение можно скачать отсюда. Оно, по сути, является независимой реализацией Drag Source и позволяет корректно вставлять текст, например, в Word или Notepad++ (кстати что любопытно они используют разные комбинации клавиш, для создания эффекта
Move
).