F#/WPF привязка событий

Я блуждаю, если есть способ подключить событие, определенное в XAML, к функции-члену F#? Конечно, я мог бы сделать это схематично, но это немного неудобно.

4 ответа

Я предполагаю, что вопрос заключается в том, можете ли вы указать член F# в качестве обработчика событий, используя разметку XAML:

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

Насколько я знаю, ответ - нет.

В C# это работает так, что регистрация обработчика событий выполняется в коде C# (частичный класс), сгенерированном дизайнером (вы можете увидеть это в obj каталог в файлах с именем, например, MainForm.g.cs). F# не имеет прямой поддержки дизайнера WPF, поэтому он не может сгенерировать это для вас. Вам придется написать код, чтобы прикрепить обработчики событий вручную (но это довольно просто).

У меня есть несколько примеров в моей лондонской беседе о Silverlight. Вы можете реализовать ? оператор, чтобы получить хороший доступ к элементам XAML:

 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 _ -> (* ... *))

? Объявление оператора, которое я использовал:

let (?) (this : Control) (prop : string) : 'T = // '
  this.FindName(prop) :?> 'T

Можно добавить привязку к команде, например. используя свойство Command="..." в кнопке.

Так что в вашем XAML вы можете иметь:

<Button Command="{Binding MyCommandHandler}">

Затем в вашем коде ViewModel, если у вас есть член с именем MyCommandHandler, он будет привязан к вышеуказанной кнопке. Так что в вашем F# что-то вроде:

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

Теперь это поддерживается в более новых версиях FsXaml, хотя работает немного иначе, чем в C#.

Используя FsXaml, вы можете определить свой Xaml и указать свой обработчик событий. Например, в окне с именем "MyWindow" вы можете сделать:

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

В вашем файле code code вы должны обработать это так:

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

    override this.button1_Click (_,_) = () // Handle event here

Вы можете сделать это, используя прикрепленное свойство:

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

module Register = 
    // http://stackru.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))

Затем используйте его в xaml следующим образом:

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

Где бар:

namespace Foo
open System.Windows

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

Я не знаю F#, поэтому приведенный выше код, вероятно, можно много почистить.

Для события click предложение Дэвида связать команду, вероятно, самое приятное.

Другие вопросы по тегам