Data binding connects XAML code directly with .NET objects. Let’s create a simple example.
Open the project we have created in part 1 and updated in part 2 and add a F# file called Process. We add the namespace MathLib and create an object Process.
namespace MathLib
type Process() =
let mutable _output = "output"
member x.Output
with get() = _output
and set(v) =
_output <- v
and rebuild the project so the Process object becomes available in the C# project.
To the C# code of the MainPage we add a process variable, create a new Process object. We add it to the DataContext of the MainPage:
Process process;
public MainPage()
{
this.InitializeComponent();
process = new Process();
this.DataContext = process;
}
and change the MainPage.xaml: Text="{Binding Output}"
<TextBlock Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="3"
x:Name="result"
Style="{StaticResource FSharp}"
Text="{Binding Output}"
/>
F5 and this is the result.
The initial value of the Process object (“output”) is shown at the location of the result TextBlock.
Next we at an extra property to Process object.
namespace MathLib
type Process() =
let mutable _input = "input"
let mutable _output = "output"
member x.Input
with get() = _input
and set(v) =
_input <- v
member x.Output
with get() = _output
and set(v) =
_output <- v
And add a Execute method.
member x.Execute() =
x.Output <- _input |> MathLib.Wrapper.IsPrimeText
And change the XAML of the TextBox
<TextBox Grid.Row="1" Grid.Column="3"
x:Name="inputValue"
FontFamily="Verdana"
FontSize="32"
Text="{Binding Path=Input, Mode=TwoWay}"
/>
We change the code of the event handler:
private void OnClick(object sender, RoutedEventArgs e)
{
process.Execute();
result.Text = process.Output;
}
and test the result by entering “12” as input and click the button.
So by adding “Mode=TwoWay” the input value of the TextBox is transferred back to the Input property of the Process object.
Let’s change the event handler, let’s remove the last line:
private void OnClick(object sender, RoutedEventArgs e)
{
process.Execute();
}
and test again.
This time the result is not updated. We have to notify the XAML that things have changed.
We can tell the XAML that things have changed by implementing the INotifyPropertyChanged interface. This is a simple interface, it has one method: PropertyChanged.
We could write this code ourselves, or we could leverage what is already available (standing on the shoulders of giants). So let’s enter MVVM (Model View ViewModel).
There are several related architectural patterns MVP, MVC, MVVM. They all prescribe the way separation of concerns is implemented a rich user interface application.
So the main theme is separation of concerns. In the case of MVVM we have:
- Model. This is all the code that does not care about the presentation. It contains all code that is unaware of users interacting with the system, so we could create several ways of presenting the model to the user: WPF, Windows Store App, internet website, etc., in all cases the model stays the same. So the model could contain:
- all business logic
- all persistence related logic
In our example the model is managed by a mathematician. He is the domain expert and knows everything about primes. - all business logic
- View. This is the presentation layer. It describes the look and feel of the application. The aim is to create declarative code like XAML and reduce the code in the code behind as much as possible.
In our example the view is managed by a UI designer. He has chosen the red color and the font. - View Model. This is the bridge between the model and view. Imaging the model and view are already available. We know what it the App looks like and all business and persistence logic is created. All code required to make the application complete will be part of the View Model.
This is where we are right know in our example. To complete the application we have to create View Model code. We could write this code ourselves, or we could leverage what is already available.
There are several MVVM frame works available: MVVVM Frameworks.
Of course we go for the F# option: FSharpWpfMvvmTemplate by Dan Mohl. This template was not created with WinRT in mind, so we have to be careful.
Create a new base class in the F# project called “ViewModelBase.fs” and copy the code for the template project:
//https://github.com/dmohl/FSharpWpfMvvmTemplate
namespace FSharpWpfMvvmTemplate.ViewModel
open Systemopen System.Windowsopen System.Windows.Inputopen System.ComponentModel
type ViewModelBase() =
let propertyChangedEvent = new DelegateEvent<PropertyChangedEventHandler>()
interface INotifyPropertyChanged with
[<CLIEvent>]
member x.PropertyChanged = propertyChangedEvent.Publish
member x.OnPropertyChanged propertyName =
propertyChangedEvent.Trigger([| x; new PropertyChangedEventArgs(propertyName) |])
And change the code of the Process class:
namespace MathLib
open FSharpWpfMvvmTemplate.ViewModel
type Process() =
inherit ViewModelBase()
let mutable _input = "input"
let mutable _output = "output"
member x.Input
with get() = _input
and set(v) =
_input <- v
x.OnPropertyChanged("Input")
member x.Output
with get() = _output
and set(v) =
_output <- v
x.OnPropertyChanged("Output")
member x.Execute() =
x.Output <- MathLib.Wrapper.IsPrimeText _input
we derive the Process class from the ViewModelBase class and raise the OnPropertyChanged event when the value of one of the properties is changed.
We have to move the Process file to it’s correct place in the project to let it compile:
The result:
So it works. We have now two issues to solve:
- The code we created can not deal with log running calculations. Just enter 756771235126757131 and the UI freezes.
- We still have an event handler, the button click. Is there a better way to deal with this so we can write the what happen after we click the button with F# code?
We solve the second issue first. Again MVVM comes to the rescue. We can bind a Command to the button. A Command is a class that implements the ICommand interface. We copy again from FSharpWpfMvvmTemplate. Create a new F# file and call it RelayCommand and copy the code:
//https://github.com/dmohl/FSharpWpfMvvmTemplatenamespace FSharpWpfMvvmTemplate.ViewModel
open Systemopen System.Windowsopen System.Windows.Inputopen System.ComponentModel
type RelayCommand (canExecute:(obj -> bool), action:(obj -> unit)) =
let event = new DelegateEvent<EventHandler>()
interface ICommand with
[<CLIEvent>]
member x.CanExecuteChanged = event.Publish
member x.CanExecute arg = canExecute(arg)
member x.Execute arg = action(arg)
Replace the Execute method Process file by the ExecuteCommand:
member x.ExcecuteCommand =
new RelayCommand ((fun canExecute -> true),
(fun action ->
x.Output <- x.Input |> Wrapper.IsPrimeText ))
Remove the event handler in the code behind and change the XAML of the button into:
<Button Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3"
Content="Is it a prime?"
x:Name="calculate"
HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="#FFE68484" FontFamily="Verdana" FontSize="32"
Command="{Binding ExcecuteCommand}"/>
We deleted the Click handler and add the binding of the Command.
F5 and the app works the way it worked, this time without the C# event handler and with F# Command.
To solve the first issue, dealing with long running calculations we change the code of the Command:
member x.ExcecuteCommand =
new RelayCommand ((fun canExecute -> true),
(fun action ->
let task = x.Input |> Wrapper.IsPrimeTask
x.Output <- task.Result))
in this case task wrapper to perform the code asynchronous.
So everything works fine again.
Let’s end with two remarks, the first is related with MVVM, the second with F# async.
From our solution it is not clear how we dealt with separation of concerns.
In our case we have a very small model, it contains just the IsPrime function. The View is the C# project and so the remaining F# code has to be the View Model: the wrappers, the Process class, the ViewModelBase and the RelayCommand are all part of the glue we needed to make the IsPrime function available for the user interface. In a real world project we have to make the structure explicate.
In stead of using a Task we could use the F# async workflow. So let’s replace the ExecuteCommand by
member x.ExcecuteCommand =
new RelayCommand ((fun canExecute -> true),
(fun action ->
let execute =
async {
x.Output <-Wrapper.IsPrimeText(x.Input)
}
execute |> Async.Start
))
F5 and enter a value un the TextBox. We get an exception:
The Dutch inner exception message tell us that we used the wrong thread to update the UI.
We can fix this changing the ViewModelBase. We capture the context at initialization and Post at the context.
type ViewModelBase() =
//capture the context of the UI
let context = System.Threading.SynchronizationContext.Current
let propertyChangedEvent = new DelegateEvent<PropertyChangedEventHandler>()
interface INotifyPropertyChanged with
[<CLIEvent>]
member x.PropertyChanged = propertyChangedEvent.Publish
member x.OnPropertyChanged propertyName =
//Post at the right thread
context.Post(( fun _ -> propertyChangedEvent.Trigger([| x; new PropertyChangedEventArgs(propertyName) |])), null)
After the fix everything works fine again.
The next post we discuss dealing with collections from a XAML F# perspective.