F#/WPF event binding
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
精彩评论