使用 FSharp.ViewModule 关闭对话框

Den*_*isV 5 wpf f# mvvm fsharp.viewmodule

在我之前的问题“使用 FSharp.ViewModule 启用对话框确定按钮”中,我达到了仅当对话框字段的验证器为真并且 ViewModule 的 IsValid 属性为真时才启用对话框的确定​​按钮的地步。但在那之后我又遇到了几个问题:

1) 单击确定按钮并没有关闭对话框,即使我IsDefault="true"在 XAML 中设置。

2) 单击确定按钮时,有时我想做比 ViewModule 验证器提供的检查更多的检查(例如,检查电子邮件地址)。然后,如果此自定义验证失败,我想阻止对话框关闭。

但是我不知道在使用 F# 和 MVVM 时如何做到这两点。首先,我尝试将 XAML 放入 C# 项目和 F# 库中的视图模型代码。然后我在后面的代码中使用了 OK 按钮的 Click 处理程序来关闭窗口。这修复了 1),但不是 2)。

所以这是我的 XAML:

<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Email, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="OK" IsEnabled="{Binding IsValid}" IsDefault="true" Command="{Binding OkCommand}" 
    <!--Click="OnOK"--> />
Run Code Online (Sandbox Code Playgroud)

和我的视图模型 - 在validate函数中带有注释以显示单击“确定”按钮时我想要执行的操作:

let name = self.Factory.Backing( <@ self.Name @>, "", notNullOrWhitespace)
let email = self.Factory.Backing( <@ self.Email @>, "", notNullOrWhitespace)
let dialogResult = self.Factory.Backing( <@ self.DialogResult @>, false )

let isValidEmail (e:string) = e.Length >= 5

member self.Name 
    with get() = name.Value 
    and set value = name.Value <- value
member self.Email 
    with get() = email.Value 
    and set value = email.Value <- value
member self.DialogResult
    with get() = dialogResult.Value
    and set value = dialogResult.Value <- value

member self.OkCommand = self.Factory.CommandSync(fun () ->
                        if not <| isValidEmail(email.Value) then
                            MessageBox.Show("Invalid Email") |> ignore
                        else
                            dialogResult.Value <- true
                        )
Run Code Online (Sandbox Code Playgroud)

Fun*_*unk 5

值得指出的是,MVVM 和代码隐藏并不是最好的朋友。

您所指的 C# 事件处理程序位于Window的代码隐藏文件(即部分类)中。尽管代码隐藏对于与视图相关的逻辑来说被认为是可以的,但 MVVM 纯粹主义者不赞成它。因此,与在 XAML 中指定事件处理程序不同,MVVM 更喜欢使用Commands.

选项 A - 在代码隐藏中进行,务实。

请注意,FsXaml 不提供事件的直接连接(在 XAML 中指定处理程序),但您可以在代码隐藏中自己连接事件。

在 XAML 中命名控件后,您可以在相应的源文件中保留它。

用户对话框.xaml

<Button x:Name="butt" ... >
Run Code Online (Sandbox Code Playgroud)

用户对话框.xaml.fs

namespace Views

open FsXaml

type UserDialogBase = XAML<"UserDialog.xaml">

type UserDialog() as dlg =
    inherit UserDialogBase()

    do dlg.butt.Click.Add( fun _ -> dlg.DialogResult <- System.Nullable(true) )
Run Code Online (Sandbox Code Playgroud)

验证最好在 ViewModel 中处理,例如对电子邮件地址使用自定义验证:

选项 B - 您可以使用DialogCloser遵循 MVVM 模式。

首先在解决方案的顶部添加一个新的源文件(解决方案资源管理器)

DialogCloser.fs

namespace Views

open System.Windows

type DialogCloser() =    
    static let dialogResultProperty =
        DependencyProperty.RegisterAttached("DialogResult", 
            typeof<bool>, typeof<DialogCloser>, 
                new PropertyMetadata(DialogCloser.DialogResultChanged))

    static member SetDialogResult (a:DependencyObject) (value:string) = 
        a.SetValue(dialogResultProperty, value)

    static member DialogResultChanged 
        (a:DependencyObject) (e:DependencyPropertyChangedEventArgs) =
        match a with
        | :? Window as window
            -> window.DialogResult <- System.Nullable (e.NewValue :?> bool)
        | _ -> failwith "Not a Window"
Run Code Online (Sandbox Code Playgroud)

假设我们的解决方案被调用WpfApp(在 XAML 标头中引用),然后我们可以实现DialogCloser如下:

用户对话框.xaml

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:views="clr-namespace:Views;assembly=WpfApp"
    xmlns:fsxaml="http://github.com/fsprojects/FsXaml"
    views:DialogCloser.DialogResult="{Binding DialogResult}"
    >

        ...

</Window>
Run Code Online (Sandbox Code Playgroud)

现在,在UserDialog的 ViewModel 中,您可以Command通过将 设置dialogResult为 true来连接并关闭对话框。

member __.OkCommand = __.Factory.CommandSync(fun () ->
                         if not <| isValidEmail(email.Value) then
                             System.Windows.MessageBox.Show ("...") |> ignore                       
                         else 
                             // do stuff (e.g. saving data)

                             ...

                             // Terminator
                             dialogResult.Value <- true 
                         )
Run Code Online (Sandbox Code Playgroud)

您还可以跳过 if / else 子句并使用自定义验证来验证电子邮件。

最后,您可以使用此辅助函数从 MainViewModel 调用对话框:

用户对话框.xaml.fs

namespace Views

open FsXaml

type UserDialog = XAML<"UserDialog.xaml">

module UserDialogHandling =    
    /// Show dialog and return result
    let getResult() = 
        let win = UserDialog()
        match win.ShowDialog() with
        | nullable when nullable.HasValue
            -> nullable.Value
        | _ -> false
Run Code Online (Sandbox Code Playgroud)

请注意,在这种情况下没有“代码隐藏”(UserDialog类型声明中没有代码)。