Overview
To recapitulate, the first article in the series used Reactive Extensions (Rx) directly in Windows Forms UI logic to implement a UI with a simulated background calculation, which delivered a timestamp each second. The rest of the application was an input text box, for which, when the user inputs the text, the word count is calculated asynchronously and posted into another read-only text box.
The second article introduced a WPF version of the same UI with a viewmodel that implements the INotifyPropertyChanged interface and supplies the ticker text and the word count as bindable properties. The text being input was delivered to the viewmodel via an event converted to an observable sequence, which was used to update the TextInput property of the viewmodel. The WordCount was updated from the setter of TextInput.
The third article recreated the WPF implementation in WinForms using the same viewmodel, which is shared between the two projects. The third UI implementation introduced throttling of the TextInput changes to 0.3 seconds so that the word count is updated only when the user pauses for at least a third of a second to take a look at the result.
The Refactored WPF Version
In this article, the WPF UI is further refactored, so that the concerns are separated further. These are organized as follows:
Create a sequence of some calculation results
These are timestamps in this case, implemented as an object, providing an IObservable<string> property Ticker:
public class BackgroundTicker { public IObservable<string> Ticker { get { return Observable .Interval(TimeSpan.FromSeconds(1)) .Select(_ => DateTime.Now.ToLongTimeString()); } } }
The observable sequence is active, delivering a value every second.
Count words in text asynchronously and provide another property calculated in the background
This viewmodel is finally implemented using ReactiveUI:
public class WordCounterModel : ReactiveObject
The word count and the input text are ReactiveUI boilerplate:
string _TextInput; public string TextInput { get { return _TextInput; } set { this.RaiseAndSetIfChanged(ref _TextInput, value); } } ObservableAsPropertyHelper<int> _WordCount; public int WordCount { get { return _WordCount.Value; } }
The value sequence of the background ticker is injected at construction time using its observable member. The word count and the background ticker properties are implemented using an ObservableAsPropertyHelper.
The results of the background ticker are exposed via a property:
ObservableAsPropertyHelper<string> _BackgroundTicker; public string BackgroundTicker { get { return _BackgroundTicker.Value; } }
the helper is initialized from the constructor-supplied observable:
someBackgroundTicker .ToProperty(this, ticker => ticker.BackgroundTicker, out _BackgroundTicker);
And finally, the word count logic is implemented as an output property, transforming the sequence of strings into a sequence of integers:
this.WhenAnyValue(x => x.TextInput) .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(x => x .Split() .Where(word => !string.IsNullOrWhiteSpace(word)) .Count()) .ToProperty(this, vm => vm.WordCount, out _WordCount);
The throttling can be removed from the viewmodel, as the WPF 4.5 has a Delay binding property, which can be used for the same purpose on the view side, freeing the viewmodel from that concern.
Provide a Graphical UI
Now let’s look at the view. The event subscription is gone and data binding is implemented in XAML declaratively. The DataContext of the main window is initialized with an instance of the viewmodel, which is initialized with an observable from an instance of a background ticker:
ViewModels.BackgroundTicker Ticker=new ViewModels.BackgroundTicker(); ViewModels.WordCounterModel VM = new ViewModels.WordCounterModel(Ticker.Ticker); DataContext = VM;
This instantiation can be definitely made in XAML as well, nullifying the amount of C# code for the view, as for example in the following article.
The background ticker and the word count are now bound one-way and marked asynchronous.
<TextBox ... Text="{Binding BackgroundTicker,IsAsync=True,Mode=OneWay}"></TextBox> <TextBox ... Text="{Binding WordCount,IsAsync=True,Mode=OneWay}"></TextBox>
And finally, the binding of the input text is configured in XAML to be throttled to 300 milliseconds and bind on PropertyChanged events of the Text property of the text box:
<TextBox ... Text="{Binding TextInput, UpdateSourceTrigger=PropertyChanged, Delay=300}"></TextBox>