# Collections
ReactiveProperty provides some collection classes.
- ReactiveCollection<T>
- ReadOnlyReactiveCollection<T>
- IFilteredReadOnlyObservableCollection<T>
# ReactiveCollection
ReactiveCollection inherits ObservableCollection.
This class is created from IObservable.
It adds an item when a value is provided from the source IObservable.
ReactiveCollection executes this process using IScheduler. The default IScheduler dispatches to the UI thread.
public class ViewModel
{
public ReactiveCollection<DateTime> Records { get; }
public ReactiveCommand StartRecordCommand { get; }
public ViewModel()
{
StartRecordCommand = new ReactiveCommand();
// Create a ReactiveCollection instance from IObservable
Records = StartRecordCommand
.ToUnit()
.Take(1)
.Concat(Observable.Defer(() => Observable.Interval(TimeSpan.FromSeconds(1)).ToUnit()))
.Select(_ => DateTime.Now)
.ToReactiveCollection();
}
}
ToUnitextension method is defined in the Reactive.Bindings.Extensions namespace. This extension method is same as.Select(_ => Unide.Default).
Example of UWP platform.
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; } = new ViewModel();
public MainPage()
{
this.InitializeComponent();
}
}
MainPage.xaml
<Page x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="Start"
Command="{x:Bind ViewModel.StartRecordCommand}"
Margin="5" />
</StackPanel>
<ListView ItemsSource="{x:Bind ViewModel.Records}"
Grid.Row="1" />
</Grid>
</Page>

# Collection operations
ReactiveCollection class has XxxxOnScheduler methods. For example, AddOnScheduler, RemoveOnScheduler, ClearOnScheduler, GetOnScheduler, etc...
Those methods run on the IScheduler and can be called from outside of the UI thread.
public class ViewModel
{
public ReactiveCollection<DateTime> Records { get; }
public ReactiveCommand StartRecordCommand { get; }
public ReactiveCommand ClearCommand { get; }
public ViewModel()
{
StartRecordCommand = new ReactiveCommand();
// Create a ReactiveCollection instance from IObservable
Records = StartRecordCommand
.ToUnit()
.Take(1)
.Concat(Observable.Defer(() => Observable.Interval(TimeSpan.FromSeconds(1)).ToUnit()))
.Select(_ => DateTime.Now)
.ToReactiveCollection();
ClearCommand = new ReactiveCommand();
ClearCommand.ObserveOn(TaskPoolScheduler.Default) // run on the another thread
.Subscribe(_ => Records.ClearOnScheduler());
}
}
<Page x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="Start"
Command="{x:Bind ViewModel.StartRecordCommand}"
Margin="5" />
<Button Content="Clear"
Command="{x:Bind ViewModel.ClearCommand}"
Margin="5" />
</StackPanel>
<ListView ItemsSource="{x:Bind ViewModel.Records}"
Grid.Row="1" />
</Grid>
</Page>

When ReactiveCollection class is Disposed, it unsubscribes from the source IObservable instance.
# ReadOnlyReactiveCollection
ReadOnlyReactiveCollection class provides one-way synchronization from ObservableCollection. Can set converting logic, and dispatch CollectionChanged event raise on the IScheduler. The default IScheduler dispatches to the UI thread.
At first, create a POCO classes.
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName]string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected void SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return;
}
field = value;
RaisePropertyChanged(propertyName);
}
}
public class TimerObject : BindableBase, IDisposable
{
private IDisposable Disposable { get; }
private long _count;
public long Count
{
get { return _count; }
private set { SetProperty(ref _count, value); }
}
public TimerObject()
{
Disposable = Observable.Interval(TimeSpan.FromSeconds(1))
.Subscribe(_ => Count++);
}
public void Dispose()
{
Disposable.Dispose();
}
}
This is a simple class that counts up the Count property per second.
Wrap the class to ViewModel layer using ReactiveProperty.
public class TimerObjectViewModel : IDisposable
{
public TimerObject Model { get; }
public ReadOnlyReactiveProperty<string> CountMessage { get; }
public TimerObjectViewModel(TimerObject timerObject)
{
Model = timerObject;
CountMessage = Model.ObserveProperty(x => x.Count)
.Select(x => $"Count value is {x}.")
.ToReadOnlyReactiveProperty();
}
public void Dispose()
{
Model.Dispose();
}
}
Manage TimerObject instances using the ObservableCollection.
We should provide TimerObjectViewModel instances to View layer, can use ReadOnlyReactiveCollection class.
ReadOnlyReactiveCollection instance is created using ToReadOnlyReactiveCollection extension method.
public class ViewModel
{
// TimerObject collection
private ReactiveCollection<TimerObject> ModelCollection { get; }
// TimerObjectViewModel collection
public ReadOnlyReactiveCollection<TimerObjectViewModel> ViewModelCollection { get; }
public ReactiveCommand AddCommand { get; }
public ReactiveCommand<TimerObjectViewModel> RemoveCommand { get; }
public ViewModel()
{
AddCommand = new ReactiveCommand();
ModelCollection = AddCommand
.Select(_ => new TimerObject())
.ToReactiveCollection();
// Create a ReadOnlyReactiveCollection instance using the converting logic.
ViewModelCollection = ModelCollection
.ToReadOnlyReactiveCollection(x => new TimerObjectViewModel(x));
RemoveCommand = new ReactiveCommand<TimerObjectViewModel>()
.WithSubscribe(x => ModelCollection.Remove(x.Model));
}
}
Test view is below.
<Page x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:ViewModels"
mc:Ignorable="d"
x:Name="root">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Content="Add"
Command="{x:Bind ViewModel.AddCommand}"
Margin="5" />
<ListView ItemsSource="{x:Bind ViewModel.ViewModelCollection}"
Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:TimerObjectViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="Remove"
Command="{Binding ViewModel.RemoveCommand, ElementName=root}"
CommandParameter="{x:Bind}"
Margin="5" />
<TextBlock Text="{x:Bind CountMessage.Value, Mode=OneWay}"
VerticalAlignment="Center"
Style="{ThemeResource BodyTextBlockStyle}"
Grid.Column="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>

When the instance was removed in the ReadOnlyReactiveCollection, then the Dispose method is called. If you don't need this behavior, then set the disposeElement argument to false in ToReadOnlyReactiveCollection().
ViewModelCollection = ModelCollection
.ToReadOnlyReactiveCollection(x => new TimerObjectViewModel(x), disposeElement: false);
# Create from IObservable
ReadOnlyReactiveCollection can be created from IObservable, it is the same as the ReactiveCollection. But, ReadOnlyReactiveCollection doesn't have collection operation methods.
ToReadOnlyReactiveCollection extension method has an onReset argument which is IObservable<Unit>.
When this argument raises a value, then the collection is cleared.
public class ViewModel
{
public ReadOnlyReactiveCollection<string> Messages { get; }
public ReactiveCommand ResetCommand { get; }
public ViewModel()
{
ResetCommand = new ReactiveCommand();
Messages = Observable.Interval(TimeSpan.FromSeconds(1))
.Select(_ => DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))
.ToReadOnlyReactiveCollection(ResetCommand.ToUnit());
}
}
<Page x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Content="Reset"
Command="{x:Bind ViewModel.ResetCommand}"
Margin="5" />
<ListView ItemsSource="{x:Bind ViewModel.Messages}"
Grid.Row="1" />
</Grid>
</Page>
When the ResetCommand is executed, clear the Messages.

# IFilteredReadOnlyObservableCollection
A collection which filters in realtime from ObservableCollection.
IFilteredReadOnlyObservableCollection watches the PropertyChanged event of the source collection item and the CollectionChanged event.
public class ValueHolder : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Id { get; set; }
private int _value;
public int Value
{
get => _value;
set
{
_value = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
public ValueHolder()
{
var r = new Random();
Observable.Interval(TimeSpan.FromSeconds(1))
.ObserveOnUIDispatcher()
.Subscribe(_ => Value = r.Next(10));
}
}
public class ViewModel
{
public ReactiveCollection<ValueHolder> ValuesSource { get; }
public IFilteredReadOnlyObservableCollection<ValueHolder> Values { get; }
public ReactiveCommand AddCommand { get; }
public ViewModel()
{
AddCommand = new ReactiveCommand();
ValuesSource = AddCommand
.Select(_ => new ValueHolder { Id = ValuesSource.Count })
.ToReactiveCollection();
Values = ValuesSource.ToFilteredReadOnlyObservableCollection(
x => x.Value > 7);
}
}
ObserveOnUIDispatcherextension method switches to the UI thread from the current thread.
<Page x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:ViewModels"
mc:Ignorable="d"
x:Name="root">
<Page.Resources>
<DataTemplate x:Key="valueHolderDataTemplate"
x:DataType="viewModels:ValueHolder">
<TextBlock>
<Run Text="Id: " />
<Run Text="{x:Bind Id}" />
<Run Text=", Value: " />
<Run Text="{x:Bind Value, Mode=OneWay}" />
</TextBlock>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="Add"
Command="{x:Bind ViewModel.AddCommand}"
Margin="5" />
<TextBlock Text="Values"
Style="{ThemeResource TitleTextBlockStyle}"
Grid.Row="1" />
<ListView ItemsSource="{x:Bind ViewModel.ValuesSource}"
ItemTemplate="{StaticResource valueHolderDataTemplate}"
Grid.Row="2" />
<TextBlock Text="Filtered Values"
Style="{ThemeResource TitleTextBlockStyle}"
Grid.Row="1"
Grid.Column="1" />
<ListView ItemsSource="{x:Bind ViewModel.Values}"
ItemTemplate="{StaticResource valueHolderDataTemplate}"
Grid.Row="2"
Grid.Column="1" />
</Grid>
</Page>

When the Value property is greater than 7, then display the value in the Filtered Values ListView (right side).
# Customize how to observe collection elements
If you want to change update elements trigger from CollectionChanged event to other, then you can customize it using another overload method that has IObservable<T> sourceElementStatusChanged argument.
For example, you want to filter nested property of elements on a collection.
// An object that has nested object property
public class NestedPropertyObject : INotifyPropertyChanged
{
// omit INPC impl
public string Id { get; } => Guid.NewGuid().ToString();
public ReactivePropertySlim<bool> NestedObject { get; } = new ReactivePropertySlim<bool>(true);
}
// --------------------
// Trigger
var sourceCollection = new ObservableCollection<NestedPropertyObject>
{
new NestedPropertyObject(),
new NestedPropertyObject(),
new NestedPropertyObject(),
};
var filteredCollection = sourceCollection.ToFilteredReadOnlyObservableCollection(
// a lambda expression for filter condition
x => x.NestedObject.Value,
// create a IObservable instance for update trigger of collection elements
x => x.ObserveProperty(y => NestedObject.Value)
);
Console.WriteLine(filteredCollection.Count); // 3
// filteredCollection is observing NextedObject.Value property path.
// Then the following line triggers re-eval for filter condition
sourceCollection[1].NestedObject.Value = false;
Console.WriteLine(filteredCollection.Count); // 2
The following two lines are same:
collection.ToFilteredReadOnlyObservableCollection(x => x.SomeProperty);
collection.ToFilteredReadOnlyObservableCollection(x => x.SomeProperty, x => x.PropertyChangedAsObservable());