Показаны сообщения с ярлыком Callback. Показать все сообщения
Показаны сообщения с ярлыком Callback. Показать все сообщения

четверг, 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).

вторник, 22 июня 2010 г.

Асинхронный вызов

Куча разнообразных программных интерфейсов предоставляет возможность осуществлять асинхронные вызовы чего-либо. К сожалению, иногда есть возможность осуществлять только асинхронные вызовы. Этим разработчикам нужно выразить отдельную благодарность, но иногда даже с идеальным фундаментом можно побороться выстраивая кривое здание.
Именно это здание мы и будем сейчас выстраивать.

Как же именно можно осуществлять подобные вызовы? Мне пришло в голову следующее шаманство:
Создадим объект, в который будем складывать последовательно функции, которые необходимо вызвать, а по завершению коллбэка вызывать на исполнение следующую функцию.
Что нам понадобится?
  • Массив, чтобы в нём хранить функции
  • Добавление функции в массив
  • Исполнение текущей функции
  • Индикатор, для того, чтобы понимать, когда-же закончился коллбэк.
У меня получилось вот такое прелестное чудо:
function SyncSequence(){
  this.__functions__ = new Array();
}
SyncSequence.prototype={  
  add:function(func){
    this.__functions__.push(func);    
  },
  wrapCallback:function(callback){
    var $this = this;    
    return function(){
      callback.apply(null,arguments);
      $this.runSequence();
    }
  },
  runSequence:function(){    
    if(this.__functions__.length > 0){
      this.__functions__.shift()();
    }
  }
}
В данном случае __functions__ является очередью, в который мы последовательно помещаем те вызовы, которые должны использовать.
wrapCallback создаёт функцию, которая заменит традиционный коллбэк. Основное её отличие заключается в том, что в конце исполнения данного ей коллбэка будет вызвана следующая функция в очереди.
Ну а runSequence просто запускает верхушку на исполнение. Процесс создания очереди будет выглядеть примерно так.
var ss = new SyncSequence();
ss.add(function(){
  test(1,ss.wrapCallback(testCallback))
});
ss.add(function(){
  test(2,ss.wrapCallback(testCallback))
});
ss.runSequence();
Конечно, не образец изящества, но всё-таки. Дополним код полусферическим примером:
function test(arg,callback){
  alert("Test started " + arg);
  setTimeout(function(){
    callback(arg + 100);
  },1000);
}

function testCallback(arg){
  alert("Test callback started " + arg);
}
При компоновке и запуске мне последовательно выдались 4 алерта:
  • Test started 1
  • Test callback started 101
  • Test started 2
  • Test callback started 102
Ура. Конструкция может существовать не только в невесомости, но и в условиях слабой гравитации. Поразительно, но мне для осуществления коварных планов по порабощению мира написанию одной маленькой утилиты этого богатства вполне хватило.
Будет нужно, подумаю что ещё можно будет добавить. А пока оставлю как есть.
И да, асинхронные вызовы снова стали синхронными. Троекратное ура.

Как всегда, исходные коды можно скачать.