RxMVVMLoop.jpg
ReactiveProperty is MVVM and Asynchronous Extensions for Reactive Extensions.
Target Framework is .NET 4.0 Client Profile, Silverlight 4, Silverlight 5, Windows Phone 7.1.

Features
  • ReactiveProperty - Two-way bindable IObservable, from V to VM and VM to V
  • ReactiveCommand - Convert observable condition sequence to ICommand
  • Easy to use asynchronous extension for WebClient/WebRequest/WebResponse/Stream
  • Typesafe convert INotifyPropertyChanged to ReactiveProperty
  • Event to ReactiveProperty Blend trigger
  • There means V -> VM -> M -> VM -> V completely connected in reactive, everything is asynchronous.
  • NuGet Installation support.
  • PM> ReactiveProperty(NET40, SL4, SL5, WP7.Rx-Main - for Rx Stable)
  • PM> ReactiveProperty-Experimental(NET40, SL4, SL5, WP7.Rx-Main - for Rx Experimental)
  • PM> ReactiveProperty-WP7(Microsoft.Phone.Reactive)
  • ReactiveProperty makes viewmodel extremely clean

Note:
ReactiveProperty is not replace existing MVVM Framework.
ReactiveProperty no provides ViewModelBase, Messenger, etc.
I recommend that use MVVM Framework together.

ReactiveProperty & ReactiveCommand Basics

HelloRP.jpg

<StackPanel>
    <TextBlock>Appears chracter after 1 second.</TextBlock>
    <!-- ReactiveProperty binding ".Value" -->
    <TextBox Text="{Binding InputText.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    <TextBlock Text="{Binding DisplayText.Value}" />
    <Button Command="{Binding ReplaceTextCommand}">Is TextBox empty or not?</Button>
</StackPanel>

public class MainWindowViewModel
{
    public ReactiveProperty<string> InputText { get; private set; }
    public ReactiveProperty<string> DisplayText { get; private set; }
    public ReactiveCommand ReplaceTextCommand { get; private set; }

    public MainWindowViewModel()
    {
        InputText = new ReactiveProperty<string>(); // binding from UI

        DisplayText = InputText             // from UI to UI value routing
            .Select(s => s.ToUpper())       // rx query1
            .Delay(TimeSpan.FromSeconds(1)) // rx query2
            .ToReactiveProperty();          // convert to ReactiveProperty

        ReplaceTextCommand = InputText             // declarative set canexecute
            .Select(s => !string.IsNullOrEmpty(s)) // condition sequence of CanExecute
            .ToReactiveCommand();                  // convert to ReactiveCommand

        // ReactiveCommand's Subscribe is set ICommand's Execute
        // ReactiveProperty.Value set is push(& set) value
        ReplaceTextCommand.Subscribe(_ => InputText.Value = "Hello, ReactiveProperty!");
    }
}
ReactiveProperty's direct binding is very simple and clear syntax.

Event to ReactiveProperty

EventToReactive.jpg

<Grid>
    <!-- Use Blend SDK's Interaction Trigger -->
    <!-- Event binding to ReactiveProperty -->
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseMove">
            <r:EventToReactive ReactiveProperty="{Binding MouseMove}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <TextBlock Text="{Binding CurrentPoint.Value}" />
</Grid>

public class EventToReactiveViewModel
{
    public ReactiveProperty<MouseEventArgs> MouseMove { get; private set; }
    public ReactiveProperty<string> CurrentPoint { get; private set; }

    public EventToReactiveViewModel()
    {
       // event binding from UI
        MouseMove = new ReactiveProperty<MouseEventArgs>(mode: ReactivePropertyMode.None);

        CurrentPoint = MouseMove
            .Select(m => m.GetPosition(null))
            .Select(p => string.Format("X:{0} Y:{1}", p.X, p.Y))
            .ToReactiveProperty();
    }
}

other feature, Bindable Func<object, object> Converter and IgnoreEventArgs property for improve testability.
more details, see Sample/EventToReactive

Asynchronous Operation

Asynchronous.jpg

<StackPanel>
    <Button Command="{Binding DownloadCommand}">Download Start</Button>
    <TextBlock Text="{Binding ProgressText.Value}" />
    <TextBlock Text="{Binding DisplayText.Value}" FontSize="10" />
</StackPanel>

// using Codeplex.Reactive.Asynchronous; // appear Asynchronous Extension Methods
// using Codeplex.Reactive.Extensions;   // appear Utility Extension Methods

public class AsynchronousViewModel
{
    public ReactiveCommand DownloadCommand { get; private set; } // a search button
    public ReactiveProperty<string> DisplayText { get; private set; }
    public ReactiveProperty<string> ProgressText { get; private set; }

    public AsynchronousViewModel()
    {
        // two kinds of notifier(Codeplex.Reactive.Notifiers)
        var network = new CountNotifier();
        var progress = new ScheduledNotifier<DownloadProgressChangedEventArgs>();

        // when downloading, buttons IsEnabled == false
        DownloadCommand = network.Select(x => x == CountChangedStatus.Empty)
            .ToReactiveCommand();

        // ***ObservableAsync is easy asynchrnous operator for Rx
        DisplayText = DownloadCommand
            .SelectMany(_ =>
            {
                network.Increment(); // connect start
                return new WebClient().DownloadStringObservableAsync(new Uri("http://bing.com/"), progress)
                    .Finally(() => network.Decrement()); // connect end
            })
            .OnErrorRetry((WebException e) => DisplayText.Value = "ERROR") //  error handling and resubscripte
            .ToReactiveProperty();

        ProgressText = progress
            .Select(e => string.Format("{0}/{1} {2}%",
                e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage))
            .ToReactiveProperty();
    }
}

WebClient/WebRequest/WebResponse/Stream 's Extension Methods return IObserable<T>.
If passing ScheduledNotifier, report to notifier.
SignalNotifier makes easy manage to network connection status.
more details, see Samples/Asynchronous

Validation

ReactivePropertyValidation.jpg

[Required]
[Range(0, 100)]
public ReactiveProperty<string> ValidationAttr { get; private set; }
public ReactiveProperty<string> ValidationData { get; private set; }
public ReactiveProperty<string> ValidationNotify { get; private set; }

public ValidationViewModel()
{
    // DataAnnotation Attribute, call SetValidateAttribute and select self property
    // Note:error result dispatch to IDataErrorInfo, not exception. Therefore, XAML is ValidatesOnDataErrors=True
    ValidationAttr = new ReactiveProperty<string>()
        .SetValidateAttribute(() => ValidationAttr);

    // IDataErrorInfo, call SetValidateError and set validate condition
    // null is success(have no error), string is error message
    ValidationData = new ReactiveProperty<string>()
        .SetValidateError(s => s.All(Char.IsUpper) ? null : "not all uppercase");

    // INotifyDataErrorInfo, call SetValidateNotifyErro and set validate condition
    // first argument is self observable sequence
    // null is success(have no error), IEnumerable is error messages
    ValidationNotify = new ReactiveProperty<string>()
        .SetValidateNotifyError(self => self
            .Delay(TimeSpan.FromSeconds(3)) // asynchronous validation
            .Select(s => string.IsNullOrEmpty(s) ? null : new[] { "not empty string" }));
}

Supporting three types validation.
more details, see Sample/Validation.

Synchronize existing models

// model...
public class ObservableObject : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            PropertyChanged(this, new PropertyChangedEventArgs("Name"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged = (_, __) => { };
}

public class PlainObject
{
    public string Name { get; set; }
}

// Synchroinize exsiting models.
public class SynchronizeObjectViewModel
{
    public ReactiveProperty<string> TwoWay { get; private set; }
    public ReactiveProperty<string> OneWay { get; private set; }
    public ReactiveProperty<string> OneWayToSource { get; private set; }
    
    public SynchronizeObjectViewModel()
    {
        var inpc = new ObservableObject { Name = "Bill" };
        var poco = new PlainObject { Name = "Steve" };

        // TwoWay synchronize
        TwoWay = inpc.ToReactivePropertyAsSynchronized(x => x.Name);

        // OneWay synchronize (ObserveProperty converts INotifyPropertyChanged to IObservable)
        OneWay = inpc.ObserveProperty(x => x.Name).ToReactiveProperty();

        // OneWayToSource synchronize
        OneWayToSource = ReactiveProperty.FromObject(poco, x => x.Name);
    }
}

using with existing MVVM Framework, auto generated models, etc.
more details see sample/SynchronizeObject

Serialization

// a ViewModel
public class SerializationViewModel
{
    // no attribute, simply serialize/deserialize
    public ReactiveProperty<bool> IsChecked { get; private set; }
    [IgnoreDataMember] // ignore serialize target
    public ReactiveProperty<int> SelectedIndex { get; private set; }
    [DataMember(Order = 3)] // deserialize order
    public ReactiveProperty<int> SliderPosition { get; private set; }
}

// case Windows Phone 7 TombStone
private SerializationViewModel viewmodel = new SerializationViewModel();
private string viewmodelData = null;

protected override void OnNavigatingFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    viewmodelData = SerializeHelper.PackReactivePropertyValue(viewmodel);
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    SerializeHelper.UnpackReactivePropertyValue(viewmodel, viewmodelData);
}

Supporting serialize ignore and deserialize order by DataAnnotations.
more details, see Sample/Serialization.

Author info

Yoshifumi Kawai a.k.a. neuecc is software developer in Tokyo, Japan.
Awarded Microsoft MVP for Visual C# since April, 2011.
I am interested in Linq and Reactive Extensions very well.
Representative of my created library is linq.js - LINQ to Objects for JavaScript.
And other many libraries see -> Codeplex users/neuecc

Blog : http://neue.cc (JPN)
Twitter : http://twitter.com/neuecc (JPN)

Last edited Nov 24, 2011 at 3:14 PM by neuecc, version 39