【问题标题】:Closing a dialog with FSharp.ViewModule使用 FSharp.ViewModule 关闭对话框
【发布时间】:2016-12-06 08:41:45
【问题描述】:

在我之前的问题“使用 FSharp.ViewModule 启用对话框 OK 按钮”中,我发现只有当对话框字段的验证器为 true 并且 ViewModule 的 IsValid 属性为 true 时才启用对话框的 OK 按钮。但在那之后我又遇到了几个问题:

1) 即使我在 XAML 中设置了IsDefault="true",单击“确定”按钮也不会关闭对话框。

2) 当单击 OK 按钮时,有时我想做比 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"--> />

还有我的视图模型 - 在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
                        )

【问题讨论】:

    标签: wpf mvvm f# fsharp.viewmodule


    【解决方案1】:

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

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

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

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

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

    UserDialog.xaml

    <Button x:Name="butt" ... >
    

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

    验证最好在 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"
    

    假设我们的解决方案称为WpfApp(在 XAML 标头中引用),那么我们可以像这样实现DialogCloser

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

    现在在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 
                             )
    

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

    要结束它,您可以使用此辅助函数从 MainViewModel 调用对话框:

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

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

    【讨论】:

    • 谢谢。不幸的是,UserDialog.xaml 中的 views:DialogCloser... 行给出了错误“未定义命名空间前缀“views””。
    • @DenisV 我在原来的帖子中可能有点过火了,现在讨论核心元素。
    • 我快到了。我只是对 dialogResult 未定义有疑问 - 请参阅原始帖子末尾的代码
    • 因为我已经将 ViewModel 设置为视图的 DataContext,所以它没有对视图的引用。那么我应该以不同的方式称呼它们吗?
    • 我想通了 - 我在 ViewModel 中创建了一个 dialogResult 属性。感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-10
    • 2013-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-04
    • 2017-06-04
    相关资源
    最近更新 更多