wpff#routedevent

Custom Routed Event in F#


I'm trying to translate this C# code. My attempt so far:

type MyButtonSimple() as self =
    inherit Button()

    static let TapEvent =
        EventManager.RegisterRoutedEvent
            ( "Tap", RoutingStrategy.Bubble, 
              typeof<RoutedEventHandler>, typeof<MyButtonSimple>)

    let tapEvent = new Event<RoutedEventHandler, RoutedEventArgs>()
    let raiseTapEvent() =
        let newEventArgs = new RoutedEventArgs(TapEvent)
        tapEvent.Trigger(self, newEventArgs)

    [<CLIEvent>]
    member x.Tap = tapEvent.Publish 

    // For demonstration purposes we raise the event when clicked
    override self.OnClick() =
        raiseTapEvent()

MainWindow.xaml

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:funk="clr-namespace:funk;assembly=appWPF"
    Title="Routed" Height="300" Width="400">
    <StackPanel Background="LightGray">
        <funk:MyButtonSimple Content="Spin" Background="#808080" Foreground="LightGray">
            <funk:MyButtonSimple.RenderTransform>
                <TransformGroup>
                    <RotateTransform x:Name="rotate"/>
                </TransformGroup>
            </funk:MyButtonSimple.RenderTransform>
            <funk:MyButtonSimple.Triggers>
                <EventTrigger RoutedEvent="funk:MyButtonSimple.Tap">
                    <BeginStoryboard>
                        <Storyboard RepeatBehavior="Forever">
                            <DoubleAnimation 
                                Storyboard.TargetName="rotate"
                                Storyboard.TargetProperty="Angle"
                                From="0" To="90" Duration="0:0:2"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </funk:MyButtonSimple.Triggers>
        </funk:MyButtonSimple>
    </StackPanel>
</Window>

Clicking the button doesn't start the animation.

Changing the code RoutedEvent="funk:MyButtonSimple.Tap" to RoutedEvent="GotFocus" gives a working example (Button.Click is overridden).

Any ideas what I'm doing wrong?


Solution

  • This is tricky because it's doing quite a bit of things that aren't standard in F#.

    A translation of that C# code would be:

    type MyButtonSimple() as self =
        inherit Button()
    
        static let tapEvent = 
           EventManager.RegisterRoutedEvent
                ( "Tap", RoutingStrategy.Bubble, 
                  typeof<RoutedEventHandler>, typeof<MyButtonSimple>)
    
        // Create a custom event so you can override AddHandler/RemoveHandler behavior
        let tapEvent = 
            { new IDelegateEvent<RoutedEventHandler> with
                member this.AddHandler del = self.AddHandler(MyButtonSimple.TapEvent, del)
                member this.RemoveHandler del = self.RemoveHandler(MyButtonSimple.TapEvent, del) }
    
        // Raise via routed eventing strategy
        let raiseTapEvent() =
            let newEventArgs = new RoutedEventArgs(MyButtonSimple.TapEvent)
            self.RaiseEvent newEventArgs
    
        // This isn't exactly the same, but public static fields aren't allowed in F#, and
        // this works for WPF
        static member TapEvent with get() = tapEvent  
    
        [<CLIEvent>]
        member x.Tap = tapEvent
    
        // For demonstration purposes we raise the event when clicked
        override self.OnClick() =
            raiseTapEvent()
    

    The main "gotchas" here are that you need to override the standard event behavior, which requires implementing IDelegateEvent yourself instead of using the normal F# event management. Also, you can't do public static readonly fields in F#, so this wraps the event into a property. WPF seems to be fine with this, even though it's not the "standard" way to implement routed events.