In my previous question "Enabling dialog OK button with FSharp.ViewModule", I got to the point where a dialog's OK button was enabled only when the validators for the dialog's fields were true, and ViewModule's IsValid property became true. But I ran into a couple more problems after that:
1) Clicking on the OK button didn't close the dialog, even if I set IsDefault="true"
in XAML.
2) When the OK button is clicked, sometimes I want to do more checks than provided by the ViewModule validators (eg, checking an email address). Then I want to stop the dialog from closing if this custom validation fails.
But I don't know how to do both when using F# and MVVM. First I tried putting the XAML into a C# project and the view model code in an F# library. And then I used the OK button's Click handler in code behind to close the window. This fixed 1), but not 2).
So this is my 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"--> />
And my view model - with a comment in the validate
function to show what I want to do when the OK button is clicked:
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
)
It's worth pointing out that MVVM and code-behind aren't best friends.
The C# event handler you're referring to is located in the
Window
's code-behind file (i.e. partial class). Although code-behind is considered ok for view related logic, it's frowned upon by MVVM purists. So instead of specifying event handlers in XAML, MVVM prefers the use ofCommands
.Option A - Doing it in code-behind, being pragmatic.
Note that FsXaml doesn't provide direct wiring of events (specifying handlers in XAML), but you can wire up the events yourself in code-behind.
After you name a control in XAML, you can get a hold on it in the corresponding source file.
UserDialog.xaml
UserDialog.xaml.fs
Validation is best handled in the ViewModel, e.g. using custom validation for the email adress:
Option B - You can follow MVVM pattern using a DialogCloser.
First add a new source file at the top of your solution (Solution Explorer)
DialogCloser.fs
Say our solution is called
WpfApp
(referenced in XAML header), we can then implement theDialogCloser
like this:UserDialog.xaml
Now in the
UserDialog
's ViewModel you can hook up aCommand
and close the dialog by setting thedialogResult
to true.You could also skip the if / else clause and validate the email using custom validation.
To wrap it up, you can call the dialog from MainViewModel using this helper function:
UserDialog.xaml.fs
Note that there's no 'code-behind' in this case (no code within the
UserDialog
type declaration).