четверг, 25 ноября 2010 г.

Немного а Drag'n'Drop в WPF.

Чтобы понять о чём я говорю для начала следует заглянуть в простейшую статью на сайте мелкомягких. В ней даётся некоторое описание относительно того, что такое дата объект и путём каких эвентов можно работать с механизмом драг-н-дропа.

Вначале всё казалось просто и понятно: есть Drag Source, который по каким-то действиям может инициировать драг-н-дроп, описав те эффекты, с которыми может оперировать Drop Target. Дальше, дроп таргет реагирует на события, такие как DragEnter, DragOver, Drop, применяя те или иные эффекты, после чего уже Drag Source реагирует на совершённые действия (например завершение операции путем дропа или отмены).
Немного сумбурно, поэтому попытаюсь изобразить на картинке:
Схематичное изображение процесса Drag'n'Drop
По сути две части (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 позволяет узнавать состояние операции, получить состояние клавиш на текущий момент.
Последнее кажется несколько странным, поскольку от состояния клавиш Drag Source не может получить никакой полезной информации, из-за того что эффект определяется поведением Drop Target'а. Начиная с этого места мы имеем первый хак:
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).

Комментариев нет:

Отправить комментарий