Seiten

Posts mit dem Label event werden angezeigt. Alle Posts anzeigen
Posts mit dem Label event werden angezeigt. Alle Posts anzeigen

Freitag, 18. November 2011

F# Wpf MVVM. Mouse Tracking with AttachedProperty.

Seit letztem Beitrag habe ich überlegt, wie man die Hindernisse einfach per Maus ziehen (bei gedrückter linker Maustaste) erstellen kann.
Die zweite Herausforderung bestand in der Mausklick-Verarbeitung im Zusammenhang mit der Positionsbestimmung des Mauszeigers. Man sollte per Mausklick entweder ein Chip auswählen können oder ein Hindernis an der entsprechenden Position zu zeichnen oder zu löschen.
Nicht zu vergessen wir sind im MVVM-Land und Code-Behind ist nicht erwünscht. In diesem Fall kann die AttachedProperty eine mögliche Option sein.
...
<Canvas Name="canvas" Grid.Column="0"
 AttachedProperty:TrackMouseBehavior.TrackPosition="{Binding TrackMouseMove}">
...
</ Canvas> 
<Canvas.InputBindings> <MouseBinding MouseAction="RightClick" Command="{Binding RightClickCommand}" /> <MouseBinding MouseAction="LeftClick" Command="{Binding LeftClickCommand}" /> </Canvas.InputBindings> Mouse Tracking mittels AttachedProperty.
//MouseTrack.fs 
//NO GUARANTEE. I'm not sure that using of the Event class at this point is allowed.
namespace FSharpWpfMvvmTemplate.AttachedProperty

open System.Windows
open System.Windows.Input

// The type represents just the action that should be executed 
// upon the occurrence of PreviewMouseMove event.
type TrackMouse  = {action : Point -> unit}

type TrackMouseBehavior() =
    let mutable startTrack = false
    let mutable endTrack = true
    static let mutable TrackPositionProperty : DependencyProperty = 
        DependencyProperty.RegisterAttached
            ("TrackPosition", typeof<TrackMouse>, typeof<TrackMouseBehavior>,
                                            new PropertyMetadata(null, new PropertyChangedCallback(TrackMouseBehavior.OnPropertyChanged)))

    static member OnPropertyChanged (d:DependencyObject) (e:DependencyPropertyChangedEventArgs) =
        let element = d :?> UIElement
        if e.NewValue <> null then
            let trackMouse = e.NewValue :?> TrackMouse
            let trackFunc = TrackMouseBehavior.MouseTrack trackMouse element
            // First when the left mouse button is already pressed and the mouse is moved,
            // until the left mouse button is released.
            element.PreviewMouseLeftButtonDown
               |> Event.map (fun args -> args :> MouseEventArgs)
               |> Event.merge element.PreviewMouseMove
               |> Event.filter (fun args -> args.LeftButton =  MouseButtonState.Pressed)
               |> Event.add (fun args -> func(args))

    static member GetTrackPosition(d:DependencyObject) =
        d.GetValue(TrackPositionProperty) :?> TrackMouse
    static member SetTrackPosition(d:DependencyObject, value : TrackMouse) =
        d.SetValue(TrackPositionProperty, value)
    
    static member MouseTrack(trackPos : TrackMouse) (element:UIElement) (mouseEventArgs : MouseEventArgs ) =   
            let point = mouseEventArgs.GetPosition(element)
            trackPos.action point
Im ViewModel wird die AddPoint-Methode definiert, die im TrackMouse-Type gekapsellt an der TrackMouseMove-AttachedProperty übergeben wird.
// JumpSearchViewModel.fs
type JumpSearchViewModel() as x=   
    class
...
        let mutable mazePath = String.Empty
        let mutable obstacles = set[]
        let mutable env : MazeEnvironment = empty
...
        let AddPointToPath (x, y) wallSize =
            let xf, yf = (float x) * wallSize, (float y) * wallSize
            let v = sprintf "M%f,%fV%f" xf yf  (yf + wallSize)
            let h= sprintf " H%fV%fH%f" (xf + wallSize) yf xf
            sprintf "%s%s%s" mazePath v h

        member x.AddPoint (point : Point) =
            if not env.IsEmpty && not timer.IsEnabled && x.VerifyX() = null && x.VerifyY() = null then
                let cellPos (posx, posy) = (posx / 20.0 |> int), (posy / 20.0 |> int)
                let pointX, pointY = cellPos (point.X, point.Y)
                if pointX <= x.MazeX - 1 && pointY <= x.MazeY - 1 then
                    //check if the mouse click hit the start or the finish coin position.
                    match (pointX, pointY) = cellPos (env.coinX, env.coinY),  (pointX, pointY) = cellPos (env.targetX, env.targetY) with
                    | true, _ -> selectedCoin <- Start (env.coinX, env.coinY)
                    |_, true -> selectedCoin <- Finish (env.targetX, env.targetY)
                    | _ ->
                        obstacles <- Set.add (pointX, pointY) obstacles
                        mazePath <- AddPointToPath (pointX, pointY) x.WallSize
                        x.MazeData <- Geometry.Parse(mazePath)

        //maze path geometry.   
        member x.MazeData  
            with get () =  mazeGeometry
            and set value = 
                mazeGeometry <- value
                base.RaisePropertyChangedEvent(<@x.MazeData@>) 
        
        // Get the TrackMouse action that should be executed. 
        member x.TrackMouseMove
            with get () =  {action = x.AddPoint}
...
Der Code.