Important information

This project has been moved to github.

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 Oct 5 at 9:08 PM by xin9le, version 43