Monthly Archives: July 2015

Implementing a storyboard in code

So, I have a requirement to flash a cell (or row) in a XamDataGrid when an error occurs on the underlying model for the cell/row. I’m currently implementing this in code simply because I’ve inherited a new control from the XamDataGrid to handle all my requirements.

I won’t go into the full implementation of the style, but suffice to say the bit that matters looks like this

var style = new Style(typeof (CellValuePresenter));

var trigger = new DataTrigger
{
   Binding = new Binding("DataItem.IsValid"),
   Value = false
};

var animation = new BrushAnimation
{
   From = Brushes.LightPink,
   To = Brushes.Firebrick,
   Duration = TimeSpan.FromSeconds(0.2),
   AutoReverse = true
};

var storyboard = new Storyboard
{
   RepeatBehavior = RepeatBehavior.Forever
};

Storyboard.SetTargetProperty(animation, 
   new PropertyPath(BackgroundProperty));
storyboard.Children.Add(animation);

var enterBeginStoryboard = new BeginStoryboard
{
   Name = "flashingStoryboard",
   Storyboard = storyboard 
};

style.RegisterName("flashingStoryboard", enterBeginStoryboard);

trigger.EnterActions.Add(enterBeginStoryboard);
trigger.ExitActions.Add(
   new StopStoryboard 
   { 
      BeginStoryboardName = "flashingStoryboard" 
   });

style.Triggers.Add(trigger);

In the above code we obviously create the style, with the target type of the CellValuePresenter. Then we’re using a trigger to detect when the view model’s IsValid property is false. This will cause our flashing animation/storyboard to pulse the background brush from light pink to firebrick and back.

Note: The BrushAnimation object is taken from a stackoverflow post Brush to Brush Animation

The creation of the animation and storyboard, hopefully, are fairly self-explanatory. The idea is that when the trigger EnterActions is called the animation begins and when the ExitAction occurs the storyboard is stoppped. We need to supply a name to the StopStoryboard object and here’s where my first mistake was made.

The line style.RegisterName(“flashingStoryboard”, enterBeginStoryboard); is important. Without it we will find that the named storyboard is not found by the StopStoryboard object. The storyboard needs to have the name registered within the
style’s namescope otherwise you’ll get the error I encountered which is an InvalidOperation exception

‘flashingStoryboard’ name cannot be found in the name scope of ‘System.Windows.Style’

This exception will only occur when the StopStoryboard code is called as this is the point it needs to find the object from the namescope.

F# MVVM plumbing code

I’m trying to see how far I can go in implementing a WPF application purely in F# (and I don’t mean that third party or framework libraries must be F#, just my code). The application isn’t going to be massive or probably very complex, I’m just interested in finding the “pain points” of using F# and WPF.

In previous posts I’ve created the code to start-up an application and load the main window along with how I might assign a view model to the DataContext of the main window.

I now want to take care of the usual mundane tasks such as binding to a view model, handling property change events on the INotifyPropertyChanged interface and implementing commands using the ICommand interface.

Handling property changes and INotifyPropertyChanged

In my C# WPF projects I use a extension methods that both assign values (if a property has changed) and raises PropertyChanged events using Expression objects as opposed to “magic strings”, i.e.

public string Name
{
   get { return name; }
   set { this.RaiseAndSetIfChanged(x => x.Name, ref name, value); }
}

I wanted to have something similar in F#. Instead of extension methods, I went the base class route

type ViewModelBase() =
    let propertyChanged = Event<_, _>()
    interface INotifyPropertyChanged with
        [<CLIEvent>]
        member this.PropertyChanged = propertyChanged.Publish

    member private this.OnPropertyChanged p = propertyChanged.Trigger(this, PropertyChangedEventArgs(p))
           
    member this.RaisePropertyChanged (p : obj) =
        match p with
        | :? string as s -> 
                this.OnPropertyChanged s
        | :? Expr as e -> 
                match propertyName e with
                | Some(pi) -> 
                    this.OnPropertyChanged pi
                | None -> ()
        | :? (string array) as a ->
                a
                |> Array.iter (fun propertyName -> this.RaisePropertyChanged propertyName) 
        | :? (Expr array) as a ->
                a
                |> Array.iter (fun propertyExpression -> this.RaisePropertyChanged propertyExpression) 
        | null ->
                this.OnPropertyChanged null
        | _ -> ()

    member this.RaiseAndSetIfChanged ((p : obj), (backingField : 'b byref), newValue) =
        assert (p <> null)

        match EqualityComparer.Default.Equals(backingField, newValue) with
        | true -> false
        | false -> 
            backingField <- newValue
            this.RaisePropertyChanged p
            true

Where the propertyName functions is defined elsewhere as

let rec propertyName quotation =
    match quotation with
    | PropertyGet (_,propertyInfo,_) -> Some(propertyInfo.Name)
    | Lambda (_,expr) -> propertyName expr
    | _ -> None

Note: This function is documented Getting a Property Name as a String in F#

You’ll notice that whilst we can overload methods in an F# type, I’ve gone with pattern matching against types passed into the RaisePropertyChanged method. This just seemed tidier and allowed me to use the same function for passing a null as well (so binding on all properties should update).

This view model base class allows me to raise a property against a “magic string”, against a Expr or against an array of either (useful when I wanted to force multiple readonly properties to update).

The RaiseAndSetIfChanged function expects a non-null property p which can be a string or an Expr.

There might be a better way to do this in F#, but this is what I’ve come up with thus far.

So to use the above code in our view model we would write something like

type MyViewModel() =
    inherit ViewModelBase()

    let mutable name = ""

    member this.Name with get() = name and 
                                set(value) = 
                                    this.RaiseAndSetIfChanged (<@ fun (v : MyViewModel) -> v.Name @>, &name, value) |> ignore

The use of the Code Quotations in F# don’t look quite as good (or terse) as C# and I’m not sure if there’s a better way, but they’re still better than “magic strings”.

Commands

Next up, we need to handle ICommand. I want something akin to ActionCommand, so implemented

type Command(execute, canExecute) =
    let canExecuteChanged = Event<_, _>()
    interface ICommand with
        [<CLIEvent>]
        member this.CanExecuteChanged = canExecuteChanged.Publish
        member this.CanExecute param = canExecute param
        member this.Execute param = execute param

    new(execute) =
        Command(execute, (fun p -> true))

    member this.RaiseCanExecuteChanged p = canExecuteChanged.Trigger(this, EventArgs.Empty)

and in use with have

type MyViewModel() =
    inherit ViewModelBase()

    let mutable name = ""

    member this.Name with get() = name and 
                                set(value) = 
                                    this.RaiseAndSetIfChanged (<@ fun (v : MyViewModel) -> v.Name @>, &name, value) |> ignore

    member this.PressMe = Command(fun p -> this.Name <- "Hello " + this.Name)

Creating a WPF application in F# (a more complete solution)

In my previous post Creating a WPF application in F# I outlined how to create a very basic WPF application using F# and WPF.

I’ve now decided to combine F#, WPF and MahApps metro/modern UI look and feel. In doing so I found a flaw in the previously posted code. First off, the code works fine, but I needed to add some resources to an App.xaml and which point I realized I needed to change the code to handle this. The previous post didn’t use an App.xaml (well it was mean’t to be minimalist solution to using F# and WPF – or at least that’s my excuse).

So I will repeat some of the previous post here in defining the steps to get an F#/WPF/MahApps application up and running.

Note: Please check out the post Build MVVM Applications in F# which outlines how to use the App.xaml file, I will be repeating this here.

Okay, here we go…

  • Create a new project and select F# Application
  • This will be a console application, so next select the project properties and change the Application | Output type from Console Application to Windows Application
  • Add references to PresentationCore, PresentationFramework, System.Xaml and WindowsBase
  • Change the Program.fs to look like this
    open System
    open System.Windows
    open System.Windows.Controls
    open System.Windows.Markup
    
    [<STAThread>]
    [<EntryPoint>]
    let main argv = 
        let application = Application.LoadComponent(
                            new System.Uri("/<Your Assembly Name>;component/App.xaml", UriKind.Relative)) :?> Application
    
        application.Run()
    

    Obviously change <Your Assembly Name> to the name of your compiled assembly name.

  • Using NuGet add MahApps.Metro to the project
  • Add a new item to your project. Unfortunately there’s no XAML file (at least not in the version of Visual Studio I’m testing this on). So simply pick an XML File and give it the name App.xaml
  • In the App.xaml file place the following code
    <Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 StartupUri="MainWindow.xaml">
        <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colors.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/Blue.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseLight.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
    </Application>
    
  • Now add another XML file to the project and name it MainWindow.xaml
  • In the MainWindow.xaml file replace the existing text with
    <Controls:MetroWindow  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
        Title="MainWindow" Height="350" Width="525">
        <Grid>
        </Grid>
    </Controls:MetroWindow >
    
  • Finally, select the MainWindow.xaml file in the solution explorer and change the file properties Build Action to Resource

Obviously you’ll still need to setup your view model at some point and without the ability to work with partial classes (as F# doesn’t support the concept). We have a couple of options.

I’ve played around with a few ways of doing this and the easiest is to do this in the Startup event of the Application object. In doing this we also need to handle the creation of the MainWindow, so

  • Remove the StartupUri=”MainWindow.xaml” from the App.xaml as we’re going to handle the creation and assignment in code
  • In the Program.fs file, before the application.Run() add the following code
    application.Startup
    |> Event.add(fun _ -> 
                         let mainWindow = Application.LoadComponent(new System.Uri("/WPFFSharp1Test;component/MainWindow.xaml", UriKind.Relative)) :?> Window
                         mainWindow.DataContext <- new MyViewModel()
                         application.MainWindow <- mainWindow
                         mainWindow.Show()
                         )
    

And we’re done. You should now have the styling of MahApps and the creation and setting up with the main window and the main view model.