Functional ViewModel

The functional ViewModel is in F#

namespace FuncViewModel
open Microsoft.FSharp.Linq.RuntimeHelpers
open System
open System.Linq.Expressions
open System.Windows.Input
open MVVM.ViewModel

    module Lambda =
        let toExpression (``f# lambda`` : Quotations.Expr<'a>) =
            ``f# lambda``
            |> LeafExpressionConverter.QuotationToExpression
            |> unbox<Expression<'a>>

//    type Command(func) =
//      let canExecuteChanged = new Event<EventHandler, EventArgs>()
//      let f = func
//      interface ICommand with
//        member __.CanExecute _ = true
//        [<CLIEvent>]
//        member __.CanExecuteChanged = canExecuteChanged.Publish
//        member __.Execute _ =
//          f()

    type MyFuncViewModel() =
        inherit ViewModelBase()

        let mutable status=""

//        member this.RunSetStatus() =
//            status <- "Reset @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
//            base.OnPropertyChanged("Status")

        member this.RunSetStatus() =
            async {
                    this.Status <- "!Start resetting @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
                    let! task = async {

                        do! Async.Sleep (10 * 1000)
                        return "!Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
                    }

                    this.Status <- task                 } |> Async.StartImmediate
//            Async.StartWithContinuations(task,
//                (fun _ -> this.Status <- "Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"), //                (fun _ -> this.Status <- "Operation failed."), //                (fun _ -> this.Status <- "Operation canceled."))         //member this.SetStatus = new DelegateCommand(fun _ -> Async.Start(this.RunSetStatus()) )
        member this.SetStatus = new DelegateCommand(fun _ -> this.RunSetStatus() )

        member this.Status
            with get() =
                status
            and set(value) =
                 status <- value
                 base.OnPropertyChanged( <@ Func<MyFuncViewModel,string> (fun (x : MyFuncViewModel) -> x.Status) @> |> Lambda.toExpression)

 

I’m extending my previous ViewModelBase with a new overload

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq.Expressions;

namespace MVVM.ViewModel
{
	///
<summary>
	/// Description of ViewModelBase.
	/// </summary>

    public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
    {
        #region Constructor

        protected ViewModelBase()
        {
        }

        #endregion // Constructor

        #region DisplayName

        ///
<summary>
        /// Returns the user-friendly name of this object.
        /// Child classes can set this property to a new value,
        /// or override it to determine the value on-demand.
        /// </summary>

        public virtual string DisplayName { get; protected set; }

        #endregion // DisplayName

        #region Debugging Aides

        ///
<summary>
        /// Warns the developer if this object does not have
        /// a public property with the specified name. This
        /// method does not exist in a Release build.
        /// </summary>

        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void VerifyPropertyName(string propertyName)
        {
            // Verify that the property name matches a real,
            // public, instance property on this object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;

                if (this.ThrowOnInvalidPropertyName)
                    throw new Exception(msg);
                else
                    Debug.Fail(msg);
            }
        }

        ///
<summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might
        /// override this property's getter to return true.
        /// </summary>

        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        #endregion // Debugging Aides

        #region INotifyPropertyChanged Members

        ///
<summary>
        /// Raised when a property on this object has a new value.
        /// </summary>

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged<X, T>(Expression<Func<X, T>> selectorExpression)
        {
            if (selectorExpression == null)
                throw new ArgumentNullException("selectorExpression");
            MemberExpression body = selectorExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("The body must be a member expression");
            OnPropertyChanged(body.Member.Name);
        }

        protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
        {
            if (selectorExpression == null)
                throw new ArgumentNullException("selectorExpression");
            MemberExpression body = selectorExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("The body must be a member expression");
            OnPropertyChanged(body.Member.Name);
        }

        ///
<summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>

        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);

            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        #endregion // INotifyPropertyChanged Members

        #region IDisposable Members

        ///
<summary>
        /// Invoked when this object is being removed from the application
        /// and will be subject to garbage collection.
        /// </summary>

        public void Dispose()
        {
            this.OnDispose();
        }

        ///
<summary>
        /// Child classes can override this method to perform
        /// clean-up logic, such as removing event handlers.
        /// </summary>

        protected virtual void OnDispose()
        {
        }

#if DEBUG
        ///
<summary>
        /// Useful for ensuring that ViewModel objects are properly garbage collected.
        /// </summary>

        ~ViewModelBase()
        {
            string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
            System.Diagnostics.Debug.WriteLine(msg);
        }
#endif

        #endregion // IDisposable Members
    }

}

 

with DelegateCommand.cs

using System;
using System.Windows.Input;

namespace MVVM.ViewModel
{
	///
<summary>
	/// Description of DelegateCommand.
	/// </summary>

public class DelegateCommand : ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;

    public event EventHandler CanExecuteChanged;

    public DelegateCommand(Action<object> execute)
                   : this(execute, null)
    {
    }

    public DelegateCommand(Action<object> execute,
                   Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null)
        {
            return true;
        }

        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public void RaiseCanExecuteChanged()
    {
        if( CanExecuteChanged != null )
        {
            CanExecuteChanged(this, EventArgs.Empty);
        }
    }
}

}
 

A more realistic example

What happens if we have to call a real blocking task?

let! task = async {

    Threading.Thread.Sleep(10 * 1000)
    //do! Async.Sleep (10 * 1000)
    return "Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
}

Well, now the command execution freezes the UI!
We have to follow the suggestion of Sergey Tihon, as he writes in his tweet:

you start execution on UI thread and let! does not change threads for you – take a look here Async.StartImmediate Method (F#)

so the working code will be

member this.RunSetStatus() =
    async {
                this.Status <- "Start resetting @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
                let context = System.Threading.SynchronizationContext.Current
                do! Async.SwitchToThreadPool()
                let! task = async {

                    Threading.Thread.Sleep(10 * 1000)
                    //do! Async.Sleep (10 * 1000)
                    return "Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
                }
                do! Async.SwitchToContext(context)
                this.Status <- task             } |> Async.StartImmediate

Another very good alternative from Tomas Petricek‘s article about Writing non-blocking user-interfaces in F#

    member this.RunSetStatus() =
        async {
                    this.Status <- "Start f# resetting @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
                    let! task = Async.StartChild( async {
                        Threading.Thread.Sleep(10 * 1000)
                        return "F# Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
                    })
                    let! asyncResult = task
                    this.Status <- asyncResult                 } |> Async.StartImmediate
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s