使用 FSharp.ViewModule 关闭对话框
Closing a dialog with FSharp.ViewModule
在我之前的问题 "Enabling dialog OK button with FSharp.ViewModule" 中,我谈到了只有当对话框字段的验证器为真且 ViewModule 的 IsValid 属性 为真时才启用对话框的确定按钮的地步。但是在那之后我 运行 又遇到了几个问题:
1) 即使我在 XAML.
中设置了 IsDefault="true"
,点击确定按钮也没有关闭对话框
2) 单击“确定”按钮时,有时我想做比 ViewModule 验证器提供的更多的检查(例如,检查电子邮件地址)。如果此自定义验证失败,我想阻止对话框关闭。
但我不知道在使用 F# 和 MVVM 时如何同时进行。首先,我尝试将 XAML 放入 C# 项目中,将视图模型代码放入 F# 库中。然后我在后面的代码中使用 OK 按钮的 Click 处理程序来关闭 window。这固定了 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
)
值得指出的是,MVVM 和 code-behind 并不是最好的朋友。
您所指的 C# 事件处理程序位于 Window
的 code-behind 文件中(即部分 class)。尽管 code-behind 被认为可以用于视图相关逻辑,但 MVVM 纯粹主义者不赞成使用它。因此,MVVM 更喜欢使用 Commands
.
,而不是在 XAML 中指定事件处理程序
选项 A - 在 code-behind 中进行,务实。
请注意,FsXaml 不提供直接连接事件(在 XAML 中指定处理程序),但您可以在 code-behind.
中自己连接事件
在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 中处理,例如对电子邮件地址使用自定义验证:
- SimpleMVVMDemo
- FSharp.ViewModule.Validation.Validators.custom
选项 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 header 中引用),然后我们可以像这样实现 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
请注意,在这种情况下没有 'code-behind'(UserDialog
类型声明中没有代码)。
在我之前的问题 "Enabling dialog OK button with FSharp.ViewModule" 中,我谈到了只有当对话框字段的验证器为真且 ViewModule 的 IsValid 属性 为真时才启用对话框的确定按钮的地步。但是在那之后我 运行 又遇到了几个问题:
1) 即使我在 XAML.
中设置了IsDefault="true"
,点击确定按钮也没有关闭对话框
2) 单击“确定”按钮时,有时我想做比 ViewModule 验证器提供的更多的检查(例如,检查电子邮件地址)。如果此自定义验证失败,我想阻止对话框关闭。
但我不知道在使用 F# 和 MVVM 时如何同时进行。首先,我尝试将 XAML 放入 C# 项目中,将视图模型代码放入 F# 库中。然后我在后面的代码中使用 OK 按钮的 Click 处理程序来关闭 window。这固定了 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
)
值得指出的是,MVVM 和 code-behind 并不是最好的朋友。
您所指的 C# 事件处理程序位于 Window
的 code-behind 文件中(即部分 class)。尽管 code-behind 被认为可以用于视图相关逻辑,但 MVVM 纯粹主义者不赞成使用它。因此,MVVM 更喜欢使用 Commands
.
选项 A - 在 code-behind 中进行,务实。
请注意,FsXaml 不提供直接连接事件(在 XAML 中指定处理程序),但您可以在 code-behind.
中自己连接事件在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 中处理,例如对电子邮件地址使用自定义验证:
- SimpleMVVMDemo
- FSharp.ViewModule.Validation.Validators.custom
选项 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 header 中引用),然后我们可以像这样实现 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
请注意,在这种情况下没有 'code-behind'(UserDialog
类型声明中没有代码)。