开发者

F#/WPF event binding

开发者 https://www.devze.com 2023-01-24 05:32 出处:网络
I am wandering if there is a way of hooking an event defined in XAML to a F# function of member ? Of course, I c开发者_高级运维ould do it diagrammatically but it is kind of inconvenient. I suppose the

I am wandering if there is a way of hooking an event defined in XAML to a F# function of member ? Of course, I c开发者_高级运维ould do it diagrammatically but it is kind of inconvenient.


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


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


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.


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
0

精彩评论

暂无评论...
验证码 换一张
取 消