2010-06-06 59 views
3

我試圖在使用F#和異步工作流的Silverlight中實現拖放操作。使用F#和異步工作流在Silverlight中拖放

我只是試圖在畫布上拖動一個矩形,使用兩個狀態(等待和拖動)的兩個循環,這是我從Tomas Petricek的書「Real-world Functional Programming」得到的一個想法,但是我遇到問題:

與WPF或WinForms不同,Silverlight的MouseEventArgs不包含關於按鈕狀態的信息,所以我不能通過檢查鼠標左鍵是否被按下而從拖動循環返回。我只設法通過引入一個可變的標誌來解決這個問題。

有沒有人有解決方案,這不涉及可變狀態?

下面是相關的代碼部分(請原諒馬虎拖拉代碼,它捕捉矩形鼠標指針):

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()) 
     () 

非常感謝您的寶貴時間!

回答

5

解決此問題的方法是使用超載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#庫(還),但您可以在t he book's web site得到它,或者你可以使用下面的代碼:

// 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) 
    ())) 
+0

非常感謝您提供快速而徹底的答案,Tomas。我想知道爲什麼你在書中使用了AwaitObservable而不是AwaitEvent,因爲我認爲你沒有在書中討論這個。無論如何,我現在明白了 - 再次感謝! – knotig 2010-06-06 20:58:46

+0

@ knotig:「AwaitEvent」的問題在於,如果您將它與例如'Event.map',你得到一個內存泄漏。 'Observable'版本不會遇到這個問題。我更詳細地寫了這篇文章 - 我建議一種修復F#事件的方法(除了使用Observables之外),但是如果您對技術細節感興趣,也可以相對清楚地解釋這個問題:http:// tomasp .NET /學術/事件鏈/事件chains.pdf – 2010-06-07 04:03:33