Showing posts with label Collection. Show all posts
Showing posts with label Collection. Show all posts

Tuesday, January 29, 2013

Windows Store Apps with F# (part 4) : Collections

In the previous post we were introduced to data binding and MVVM. We dealt with one object. How can we deal with more than one object, a collection of objects?
This time we want to show a sequence of primes:
  1. 2
  2. 3
  3. 5
  4. 7
  5. 11
The first number is the sequence number, the second is the value of the prime. We want to show this sequence in a Windows Store App. We create again a new blank Windows Store App and add a F# Portable Library. We rename the F# file to Lib.fs. Let’s follow MVVM. We start with the Model. Add the IsPrime function to Lib.fs:
module MathLib
let IsPrime n =
     if n < 2L then
            false
        else
            seq{2L..n - 1L}
            |> Seq.exists(fun x -> n % x = 0L)
            |> not

Next we create an object to represent an element in the sequence:
type Element(sequenceNumber:int, value:int64) =
    member x.SequenceNumber = sequenceNumber
    member x.Value          = value
Now it is possible to create a function that will determine the next element in the sequence of primes:  let rec nextPrime (element:Element) =
   
if IsPrime (element.Value + 1L) then
           
Element(element.SequenceNumber + 1,  element.Value + 1L)
       
else
           
Element(element.SequenceNumber,  element.Value + 1L) |> nextPrime

Next we create a sequence of all primes (details at page 320 of Real-World Functional Programming by Tomas Petricek and with Jon Skeet, buy this book!!):
let rec primeSequence =
    seq { yield Element(1,  2L)
          for e in primeSequence do yield e |> nextPrime }

and test the model by adding a test to the Script.fsx file.
#load "Lib.fs"
MathLib.primeSequence |> Seq.take 5 |> Seq.iter( fun r -> (printfn "%d. %d" r.SequenceNumber r.Value))

When we run the code in F# Interactive we get the expected result:

PrimeSequence

Compile the project and add a reference from the C# project to the F# project.

Next step: the Model. We contact the UI designer and he sends us the following XAML:
<GridView ItemsSource="{Binding}" >
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>

                <TextBlock Grid.Column="0"  Text="{Binding SequenceNumber}"/>
                <TextBlock Grid.Column="1"  Text="->"/>
                <TextBlock Grid.Column="2" Text="{Binding Value}"/>
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView> 


We past it in the Grid of the MainPage. Gridview is one of the Windows Store App controls that can display collections.

Next we update the MainPage code behind:
using System.Collections.ObjectModel;

and
 public sealed partial class MainPage : Page
    {
        private ObservableCollection<MathLib.Element> Collection;
        
        public MainPage()
        {
            this.InitializeComponent();

            Collection = new ObservableCollection<MathLib.Element>()
                {
                    new MathLib.Element(1, 2),
                    new MathLib.Element(2, 3),
                    new MathLib.Element(3, 5),
                    new MathLib.Element(4, 7),
                    new MathLib.Element(5, 11)
                };

            this.DataContext = Collection;
        }

We have created an ObservableCollection class called Collection and added some elements so we can see the result of the binding.

F5:

PrimeSequenceWinRT1

It works as expected but it does not look good.

So we contact our designer again. This is the code he sends us:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <TextBlock Text="Primes" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="42" Margin="80,40,0,0" />
    <GridView x:Name="ItemListView" ItemsSource="{Binding}" Margin="80, 100, 80, 80">
        <GridView.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapGrid />
            </ItemsPanelTemplate>
        </GridView.ItemsPanel>
        <GridView.ItemTemplate>
            <DataTemplate>
                <Grid Width="200" Height="60" Background="Green">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="60"/>
                        <ColumnDefinition Width="20"/>
                        <ColumnDefinition Width="120"/>
                    </Grid.ColumnDefinitions>

                    <TextBlock Grid.Column="0"  Text="{Binding SequenceNumber}"  HorizontalAlignment="Left"
                        VerticalAlignment="Top"
                        FontSize="15"
                        Foreground="Black"
                        Margin="5,5,5,5" />
                    <TextBlock Grid.Column="2" Text="{Binding Value}"  HorizontalAlignment="Left"
                        VerticalAlignment="Top"
                        FontSize="24"
                        Foreground="White"
                        Margin="5,5,5,5"/>
                </Grid>
            </DataTemplate>
        </GridView.ItemTemplate>
    </GridView>
</Grid>

and the result looks like this (replace the XAML and press F5):

PrimeSequenceWinRT2

We accept this design.

Remark: there are better ways to deal with design data in XAML: [WPF / MVVM] How to get data in “design time” ?

Now it is time to glue the Model and the View together, we will add the View Model code.

First we change the initialization of the Collection:
  Collection = new ObservableCollection<MathLib.Element>();

and add a new F# file to the F# project called ViewModel.

We add the following code:
module ViewModel
open System.Collections.ObjectModel
let addElements(collection:ObservableCollection<MathLib.Element>) =
    collection.Clear()
    MathLib.primeSequence |> Seq.iter collection.Add


Build the solution and add the following code to event handler OnNavigatedTo of the MainPage:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    ViewModel.addElements(this.Collection);
}

F5 and the application never shows the Main Page:

screenshot_01292013_114516

When we reduce the number of elements to 100
let addElements(collection:ObservableCollection<MathLib.Element>) =
    collection.Clear()
    MathLib.primeSequence|> Seq.take 100 |> Seq.iter collection.Add


we get the expected result.

screenshot_01292013_114929

This solution will not work when we want to display a very large number of primes.

So we go the async/await way again.
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    await ViewModel.addElementsTask(this.Collection);
}

and change the ViewModel
module ViewModel
open System.Collections.ObjectModelopen System.Threading.Tasks
let addElements(collection:ObservableCollection<MathLib.Element>) =
    MathLib.primeSequence|> Seq.iter collection.Add

let addElementsTask(collection:ObservableCollection<MathLib.Element>) =
     collection.Clear()

Task.Factory.StartNew(fun _ -> addElements collection);

This time we get error messages:

Error

The C# project needs a reference to the F# library. We will not go that way, we solve the issue by creating a Task of int and add a dummy return value.
module ViewModel
open System.Collections.ObjectModelopen System.Threading.Tasks
let addElements(collection:ObservableCollection<MathLib.Element>) = 
    MathLib.primeSequence |> Seq.iter collection.Add

let addElementsTask(collection:ObservableCollection<MathLib.Element>) =
     collection.Clear()
     Task<int>.Factory.StartNew(fun _ ->
                                addElements(collection)
                                0)


F5 again. This time we get an context exception.

ContextException

So we add the context to the functions.
 protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
            await ViewModel.addElementsTask(this.Collection, System.Threading.SynchronizationContext.Current);
        }

and

module ViewModel
open System.Collections.ObjectModelopen System.Threadingopen System.Threading.Tasks
let addElements(collection:ObservableCollection<MathLib.Element>, context:SynchronizationContext) =
     MathLib.primeSequence |> Seq.iter(
fun x -> context.Post((fun _ -> collection.Add(x)) ,null))
let addElementsTask(collection:ObservableCollection<MathLib.Element>, context:SynchronizationContext) =
     collection.Clear()
     Task<int>.Factory.StartNew(
fun _ ->
                               
addElements(collection,context)
                                0)



and we get the desired result:

screenshot_01292013_124616

We end with two small remarks:


  1. In this post we used the ObservableCollection, this is a .NET solution. The WinRT way is to use IObservableVector<T>.

  2. We can replace addElementsTask by
let addElementsTaskMutable (collection:ObservableCollection<MathLib.Element>, synchronizationContext:SynchronizationContext) =
    collection.Clear()
    Task<int>.Factory.StartNew(
        fun _ -> 
        let mutable e = MathLib.Element(0,  1L)
        while true do
            e <- MathLib.nextPrime e
            let x = e  //make the result immutable
            synchronizationContext.Post((fun _ -> collection.Add(x)) ,null)
        0       
        )
This seems to improve the performance of the application quit a lot.





Total Pageviews