Перетаскивание в Silverlight с помощью F # и асинхронных рабочих процессов - PullRequest
3 голосов
/ 07 июня 2010

Я пытаюсь реализовать перетаскивание в Silverlight, используя F # и асинхронные рабочие процессы.

Я просто пытаюсь перетащить прямоугольник на холсте, используя два цикла для двух состояний (ожиданиеи перетаскивание), идея, которую я получил из книги Томаса Петричека «Функциональное программирование в реальном мире», но столкнулся с проблемой:

В отличие от WPF или WinForms, MouseEventArgs Silverlight не несут информацию о состоянии кнопки,Я не могу вернуться из цикла перетаскивания, проверив, не нажата ли левая кнопка мыши.Мне удалось решить эту проблему только с помощью изменяемого флага.

У кого-нибудь есть решение для этого, которое не включает изменяемое состояние?

Вот соответствующая часть кода (пожалуйста, извините за неаккуратное перетаскиваниекод, который привязывает прямоугольник к указателю мыши):

type MainPage() as this =
    inherit UserControl()
    do
        Application.LoadComponent(this, new System.Uri("/SilverlightApplication1;component/Page.xaml", System.UriKind.Relative))
    let layoutRoot : Canvas = downcast this.FindName("LayoutRoot")
    let rectangle1 : Rectangle = downcast this.FindName("Rectangle1")

    let mutable isDragged = false

    do
        rectangle1.MouseLeftButtonUp.Add(fun _ -> isDragged <- false)

        let rec drag() = async {
            let! args = layoutRoot.MouseMove |> Async.AwaitEvent
            if (isDragged) then
                Canvas.SetLeft(rectangle1, args.GetPosition(layoutRoot).X)
                Canvas.SetTop(rectangle1, args.GetPosition(layoutRoot).Y)
                return! drag()
            else
                return()
            } 
        let wait() = async {
            while true do
                let! args = Async.AwaitEvent rectangle1.MouseLeftButtonDown
                isDragged <- true
                do! drag()
            }

        Async.StartImmediate(wait())
        ()

Большое спасибо за потраченное время!

1 Ответ

5 голосов
/ 07 июня 2010

Способ решения этой проблемы - использовать перегруженную AwaitEvent, которая позволяет вам ждать два события. Вместо того, чтобы просто ждать MouseMove, вы также можете ждать события MouseUp - в первом случае вы можете продолжить движение, а во втором случае вы можете вернуться из цикла и остановить перетаскивание (это на самом деле обсуждается позже в книге в разделе 16.4.5 ).

Вот код - он на самом деле использует AwaitObservable вариант метода (см. Ниже), который является лучшим выбором в целом, потому что он работает с Observable.map и подобными комбинаторами (если вы хотели использовать их) ,

let! args = Async.AwaitObservable(layoutRoot.MouseMove, layoutRoot.MouseUp)
match args with
| Choice1Of2(args) ->
    // Handle the 'MouseMove' event with 'args' here
    Canvas.SetLeft(rectangle1, args.GetPosition(layoutRoot).X)  
    Canvas.SetTop(rectangle1, args.GetPosition(layoutRoot).Y)  
    return! drag()  
| Choice2Of2(_) ->
    // Handle the 'MouseUp' event here
    return()  

Насколько я знаю, перегруженный метод AwaitObservable недоступен в библиотеках F # (пока), но вы можете получить его на веб-сайте книги *1015*, или вы можете использовать следующее код:

// Adds 'AwaitObservable' that takes two observables and returns
// Choice<'a, 'b> containing either Choice1Of2 or Choice2Of2 depending
// on which of the observables occurred first
type Microsoft.FSharp.Control.Async with   
  static member AwaitObservable(ev1:IObservable<'a>, ev2:IObservable<'b>) = 
    Async.FromContinuations((fun (cont,econt,ccont) -> 
      let rec callback1 = (fun value ->
        remover1.Dispose()
        remover2.Dispose()
        cont(Choice1Of2(value)) )
      and callback2 = (fun value ->
        remover1.Dispose()
        remover2.Dispose()
        cont(Choice2Of2(value)) )
      // Attach handlers to both observables
      and remover1 : IDisposable  = ev1.Subscribe(callback1) 
      and remover2 : IDisposable  = ev2.Subscribe(callback2) 
      () ))
...