关闭 FSharp.ViewModule 中的对话框

5
在我之前的问题“在FSharp.ViewModule中启用对话框OK按钮”中,我已经实现了当对话框中的字段验证器为true时,对话框的OK按钮才能启用,并且ViewModule的IsValid属性变为true。但是在那之后我遇到了另外两个问题:
1. 即使我在XAML中设置了IsDefault="true",单击OK按钮也不能关闭对话框。
2. 当单击OK按钮时,有时候我希望进行更多的检查(例如,检查电子邮件地址),而不仅仅是使用ViewModule的验证器提供的检查。如果这种自定义验证失败,则希望阻止对话框关闭。
但是我不知道如何在使用F#和MVVM时同时解决这两个问题。起初,我尝试将XAML放入C#项目中,并将视图模型代码放入F#库中。然后,我使用代码后台中的OK按钮的Click处理程序来关闭窗口。这解决了第一个问题,但没有解决第二个问题。
以下是我的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函数中加上一个注释,以显示当单击OK按钮时我想要执行的操作:

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
                        )
1个回答

5
值得指出的是,MVVM 和代码后台并不是最好的朋友。你提到的 C# 事件处理程序位于 Window 的代码后台文件(即 partial class)中。尽管代码后台被认为是与视图相关的逻辑方案,但它被一些 MVVM 纯粹主义者所反对。因此,MVVM 更倾向于使用 Commands 而非在 XAML 中指定事件处理程序。
选项 A - 做到实用,进行代码后台处理。
请注意,FsXaml 不提供直接的事件连接(在 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
@DenisV 我在原帖中可能有点过头了,现在只关注核心要素。 - Funk
我快完成了。我只有一个问题,dialogResult未定义 - 请参见我的原始帖子末尾的代码 - DenisV
因为我已将ViewModel设置为视图的DataContext,所以它没有对视图的引用。那么我应该以不同的方式调用它们吗? - DenisV
我解决了 - 我在ViewModel中创建了一个dialogResult属性。感谢您的帮助! - DenisV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接