5

I am wandering if there is a way of hooking an event defined in XAML to a F# function of member ? Of course, I could do it diagrammatically but it is kind of inconvenient.

user503775
  • 51
  • 2
  • I am unclear about what is being asked; if you have some XAML code and maybe e.g. the corresponding C# code to demonstrate what you are talking about, it may help get an answer. – Brian Nov 10 '10 at 22:19
  • 1
    I hope that _diagrammatically_ has nothing to do with category theory :-) – Tomas Petricek Nov 10 '10 at 22:32

4 Answers4

10

I suppose the question is whether you can specify F# member as an event handler using XAML markup:

<Button x:Name="btnClick" Content="Click!" Click="button1_Click" />

As far as I know, the answer is No.

The way this works in C# is that the registration of event handler is done in C# code (partial class) generated by the designer (you can see that in the obj directory in files named e.g. MainForm.g.cs). F# doesn't have any direct support for WPF designer, so it cannot generate this for you. You'll have to write the code to attach event handlers by hand (but that's quite easy).

I have some examples in my London talk about Silverlight. You can implement the ? operator to get nice access to the XAML elements:

 type MainPage() as this =
   inherit UserControl()
   let uri = new System.Uri("/App;component/MainPage.xaml", UriKind.Relative)
   do Application.LoadComponent(this, uri)

   // Get button using dynamic access and register handler
   let btn : Button = this?btnClick
   do btnClick.Click.Add(fun _ -> (* ... *))

The ? operator declaration that I used is:

let (?) (this : Control) (prop : string) : 'T = // '
  this.FindName(prop) :?> 'T
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thank-you for the example, the operator is handy. It is was what I though but I was not clear in my mind. Could the x:Subclass attribute be of some help ? – user503775 Nov 11 '10 at 19:55
4

It is possible to add binding to a command, eg. using the Command="..." property in the button.

So in you XAML you can have:

<Button Command="{Binding MyCommandHandler}">

Then in your ViewModel code, if you have a member called MyCommandHandler, it'll be bound to the above button. So in your F#, something like:

module ViewModel =
    type FuncCommand (canExec:(obj -> bool), doExec:(obj -> unit)) =     
        let theEvent = new DelegateEvent<EventHandler>()     
        interface ICommand with         
            [<CLIEvent>]         
            member x.CanExecuteChanged = theEvent.Publish         
            member x.CanExecute arg = canExec(arg)         
            member x.Execute arg = doExec(arg)

    type MyViewModel() =
        member this.MyCommandHandler =          
            new FuncCommand(             
                (fun _ -> ... SOME CODE WHICH RETURNS TRUE OR FALSE ...),             
                (fun _ -> ... SOME CODE TO HANDLE THE CLICK ...)
            )
David_001
  • 5,703
  • 4
  • 29
  • 55
2

You can do it using an attached property:

namespace Foo
open System.Windows
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Windows.Media

module Register = 
    // http://stackoverflow.com/a/14706890/1069200
    type internal Marker = interface end

    let ClickHandlerProperty = DependencyProperty.RegisterAttached(
                                "ClickHandler", 
                                typeof<RoutedEventHandler>, 
                                typeof<Marker>.DeclaringType, 
                                PropertyMetadata(null))

    let SetClickHandler (element: UIElement, value : RoutedEventHandler) = 
        element.SetValue(ClickHandlerProperty, value)

    let GetClickHandler (element: UIElement) : RoutedEventHandler = 
        element.GetValue(ClickHandlerProperty) :?> _

    let private OnClick (sender : obj) args = 
        let button = sender :?> UIElement
        let handler = GetClickHandler button
        if not (obj.ReferenceEquals(handler, null)) then
            handler.Invoke(sender, args)

    let private initialize =
        EventManager.RegisterClassHandler(
            typeof<FrameworkElement>, 
            ButtonBase.ClickEvent, 
            RoutedEventHandler(OnClick))

Then use it in xaml like this:

<Window ...
        xmlns:foo="clr-namespace:Foo;assembly=Foo">
    <Button Content="Click!" foo:Register.ClickHandler="{x:Static foo:Bar.OnClicked}" />
</Window>

Where bar is:

namespace Foo
open System.Windows

module Bar =
    let OnClicked =
        let onClick _ _ = MessageBox.Show "clicked" |> ignore
        RoutedEventHandler(onClick)

I don't know f# so the above code can probably be cleaned up a lot.

For the click event David's suggestion to bind the command is probably nicest.

Johan Larsson
  • 17,112
  • 9
  • 74
  • 88
2

This is now supported in the newer versions of FsXaml, though it works slightly differently than it does in C#.

Using FsXaml, you can define your Xaml and specify your event handler. For example, in a window named "MyWindow", you can do:

<Button Content="Click!" Click="button1_Click" />

In your "code behind" file, you would handle this like so:

type MyWindowBase = XAML<"MyWindow.xaml">
type MyWindow () =
    inherit MyWindowBase

    override this.button1_Click (_,_) = () // Handle event here
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373