简体   繁体   English

使用F#和异步工作流在Silverlight中拖放

[英]Drag and Drop in Silverlight with F# and Asynchronous Workflows

I'm trying to implement drag and drop in Silverlight using F# and asynchronous workflows. 我正在尝试使用F#和异步工作流在Silverlight中实现拖放。

I'm simply trying to drag around a rectangle on the canvas, using two loops for the the two states (waiting and dragging), an idea I got from Tomas Petricek's book "Real-world Functional Programming", but I ran into a problem: 我只是试图在画布上拖动一个矩形,使用两个循环来处理两个状态(等待和拖动),这是我从Tomas Petricek的书“Real-world Functional Programming”中得到的一个想法,但我遇到了一个问题:

Unlike WPF or WinForms, Silverlight's MouseEventArgs do not carry information about the button state, so I can't return from the drag-loop by checking if the left mouse button is no longer pressed. 与WPF或WinForms不同,Silverlight的MouseEventArgs不包含有关按钮状态的信息,因此我无法通过检查是否不再按下鼠标左键来从拖动循环返回。 I only managed to solve this by introducing a mutable flag. 我只是通过引入一个可变标志来解决这个问题。

Would anyone have a solution for this, that does not involve mutable state? 有没有人有这个解决方案,这不涉及可变状态?

Here's the relevant code part (please excuse the sloppy dragging code, which snaps the rectangle to the mouse pointer): 这是相关的代码部分(请原谅拖拽拖动代码,将矩形捕捉到鼠标指针):

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

Thank you very much for your time! 非常感谢您的宝贵时间!

The way to solve this issue is to use an overloaded AwaitEvent that allows you to wait for two events. 解决此问题的方法是使用重载的AwaitEvent ,它允许您等待两个事件。 Instead of just waiting for MouseMove , you can also wait for the MouseUp event - in the first case, you can continue moving and in the second case, you can return from the loop and stop drag&drop (this is actually discussed later in the book in section 16.4.5 ). 而不是只是等待MouseMove ,你也可以等待MouseUp事件 - 在第一种情况下,你可以继续移动,在第二种情况下,你可以从循环返回并停止拖放(这实际上将在本书的后面讨论)第16.4.5节)。

Here is the code - it actually uses AwaitObservable variant of the method (see below), which is a better choice in general, because it works with Observable.map and similar combinators (in case you wanted to use these). 这是代码 - 它实际上使用方法的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()  

As far as I know, the overloaded AwaitObservable method is not available in the F# libraries (yet), but you can get it from t he book's web site , or you can use the following code: 据我所知,重载的AwaitObservable方法在F#库中尚未提供,但您可以从本书的网站上获取它,或者您可以使用以下代码:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM