Often the most straightforward way to present IObservable<T>
with MVVM is to make a conventional view-model object like one below and manually subscribe it to the observable. The subscription should be performed using .ObserveOn(SynchronizationContext.Current)
to dispatch all the notifications in the UI thread. In turn, the synchronization context should exist at that moment (SynchronizationContext.Current
is null before new Application().Run(mainWindow)
). See sample below:
public class ViewModel : INotifyPropertyChanged
{
private int _property1;
public int Property1
{
get => _property1;
set
{
_property1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Property1)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The view:
<Window x:Class="ConsoleApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox Text="{Binding Property1}" />
</Window>
The caller method:
[STAThread]
static void Main()
{
var model = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)).Take(10);
var viewModel = new ViewModel();
var mainWindow = new MainWindow
{
DataContext = viewModel
};
IDisposable subscription = null;
mainWindow.Loaded += (sender, e) =>
subscription = model
.ObserveOn(SynchronizationContext.Current) // Dispatch all the events in the UI thread
.Subscribe(item => viewModel.Property1 = item);
new Application().Run(mainWindow);
subscription?.Dispose();
}
Update
Another option is to use ReactiveUI.WPF and even more concise - ReactiveUI.Fody to generate auto-properties in the view-model.
View-model:
public class ViewModel : ReactiveObject, IDisposable
{
private readonly IDisposable subscription;
public ViewModel(IObservable<long> source)
{
subscription = source
.ObserveOnDispatcher()
.ToPropertyEx(this, x => x.Property1);
}
// To be weaved by Fody
public extern long Property1 { [ObservableAsProperty]get; }
// Unsubscribe from the source observable
public void Dispose() => subscription.Dispose();
}
Here ObserveOnDispatcher()
call works even when the dispatcher has not been started and SynchronizationContext.Current
is null.
The caller method:
[STAThread]
static void Main()
{
new Application().Run(
new MainWindow
{
DataContext = new ViewModel(
Observable.Timer(
TimeSpan.Zero,
TimeSpan.FromSeconds(1))
.Take(20))
});
}
FodyWeavers.xml near the solution file:
<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
<ReactiveUI />
</Weavers>