F#/WPF event binding

后端 未结 4 1584
青春惊慌失措
青春惊慌失措 2020-12-29 16:48

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

相关标签:
4条回答
  • 2020-12-29 17:10

    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.

    0 讨论(0)
  • 2020-12-29 17:27

    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 讨论(0)
  • 2020-12-29 17:29

    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
    
    0 讨论(0)
  • 2020-12-29 17:34

    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 ...)
                )
    
    0 讨论(0)
提交回复
热议问题