Suppose I have the following view models:
public class AddressViewModel : ReactiveObject
{
private string line;
public string Line
{
get { return this.line; }
set { this.RaiseAndSetIfChanged(x => x.Line, ref this.line, value); }
}
}
public class EmployeeViewModel : ReactiveObject
{
private AddressViewModel address;
public AddressViewModel Address
{
get { return this.address; }
set { this.RaiseAndSetIfChanged(x => x.Address, ref this.address, value); }
}
}
Now suppose that in EmployeeViewModel
I want to expose a property with the latest value of Address.Line
:
public EmployeeViewModel()
{
this.changes = this.ObservableForProperty(x => x.Address)
.Select(x => x.Value.Line)
.ToProperty(this, x => x.Changes);
}
private readonly ObservableAsPropertyHelper<string> changes;
public string Changes
{
get { return this.changes.Value; }
}
This will only tick when a change to the Address
property is made, but not when a change to Line
within Address
occurs. If I instead do this:
public EmployeeViewModel()
{
this.changes = this.Address.Changed
.Where(x => x.PropertyName == "Line")
.Select(x => this.Address.Line) // x.Value is null here, for some reason, so I use this.Address.Line instead
.ToProperty(this, x => x.Changes);
}
This will only tick when a change to Line
within the current AddressViewModel
occurs, but doesn't take into account setting a new AddressViewModel
altogether (nor does it accommodate a null
Address
).
I'm trying to get my head around the correct approach to solving this problem. I'm new to RxUI so I could be missing something obvious. I could manually hook into address changes and set up a secondary subscription, but this seems ugly and error-prone.
Is there a standard pattern or helper I should be using to achieve this?
Here is some code that can be copy/pasted to try this out:
ViewModels.cs:
namespace RxUITest
{
using System;
using System.Reactive.Linq;
using System.Threading;
using System.Windows.Input;
using ReactiveUI;
using ReactiveUI.Xaml;
public class AddressViewModel : ReactiveObject
{
private string line1;
public string Line1
{
get { return this.line1; }
set { this.RaiseAndSetIfChanged(x => x.Line1, ref this.line1, value); }
}
}
public class EmployeeViewModel : ReactiveObject
{
private readonly ReactiveCommand changeAddressCommand;
private readonly ReactiveCommand changeAddressLineCommand;
private readonly ObservableAsPropertyHelper<string> changes;
private AddressViewModel address;
private int changeCount;
public EmployeeViewModel()
{
this.changeAddressCommand = new ReactiveCommand();
this.changeAddressLineCommand = new ReactiveCommand();
this.changeAddressCommand.Subscribe(x => this.Address = new AddressViewModel() { Line1 = "Line " + Interlocked.Increment(ref this.changeCount) });
this.changeAddressLineCommand.Subscribe(x => this.Address.Line1 = "Line " + Interlocked.Increment(ref this.changeCount));
this.Address = new AddressViewModel() { Line1 = "Default" };
// Address-only changes
this.changes = this.ObservableForProperty(x => x.Address)
.Select(x => x.Value.Line1 + " CHANGE")
.ToProperty(this, x => x.Changes);
// Address.Line1-only changes
//this.changes = this.Address.Changed
// .Where(x => x.PropertyName == "Line1")
// .Select(x => this.Address.Line1 + " CHANGE") // x.Value is null here, for some reason, so I use this.Address.Line1 instead
// .ToProperty(this, x => x.Changes);
}
public ICommand ChangeAddressCommand
{
get { return this.changeAddressCommand; }
}
public ICommand ChangeAddressLineCommand
{
get { return this.changeAddressLineCommand; }
}
public AddressViewModel Address
{
get { return this.address; }
set { this.RaiseAndSetIfChanged(x => x.Address, ref this.address, value); }
}
public string Changes
{
get { return this.changes.Value; }
}
}
}
MainWindow.cs:
using System.Windows;
namespace RxUITest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new EmployeeViewModel();
}
}
}
MainWindow.xaml:
<Window x:Class="RxUITest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBlock Text="{Binding Changes}"/>
<Button Command="{Binding ChangeAddressCommand}">Change Address</Button>
<Button Command="{Binding ChangeAddressLineCommand}">Change Address.Line1</Button>
</StackPanel>
</Window>
WhenAny
operator as observing any number of "tail" properties (Line
in the above example). I didn't realize it also had the smarts to observe any property in the chain. Thanks Paul. – Hypothesize