0

Let's say I have two observables Obs1 and Obs2. I want a trigger on Obs1 to suppress the subsequent trigger on Obs2 (marble diagram below). How do I do this?

Obs1---x---x--x----
Obs2----yyy-yy-yyyy
Rslt-----yy--y--yyy

Specifically, I have a class with two properties, Target and SelectedItem. When the target is set, SelectedItem should be set immediately based on a SpecialValue property on the target. Users should be able to change the selection, in which case the new value gets propagated back to the target. SelectedItem should propagate back to the target only if the user changes the value; however, the value is propagating back to the target the moment the target is set - this is the undesired behavior I'm trying to fix.

(SelectionViewModel leverages ReactiveUI, but we mimicked Prism's SetProperty method to aid in migration. BindToProperty is just a helper method that does what it says.)

sealed class SelectionViewModel
{
    internal SelectionViewModel( )
    {
        this.WhenAnyValue(x => x.Target).Where(t => t != null)
            .Select(_ => Target.SpecialValue)
            .BindToProperty(this, x => x.SelectedItem);

        this.WhenAnyValue(x => x.Target).Where(t => t != null)
            .Select(_ => this.WhenAnyValue(x => x.SelectedItem).Skip(1))
            .Switch()
            .BindToProperty(this, x => Target.SpecialValue);
    }

    private MyClass _selectedItem;

    public MyClass SelectedItem
    {
        get { return _selectedItem; }
        set { SetProperty(ref _selectedItem, value); }
    }

    private ITarget _target;

    public ITarget Target
    {
        get { return _target; }
        set { SetProperty(ref _target, value); }
    }
}
ket
  • 728
  • 7
  • 22
  • In the interest of clarity can you please share code for `BindToProperty()` – aateeque Apr 18 '16 at 21:14
  • It's a fair request, but the code is pretty lengthy and I think it's considered proprietary, so the powers that be would probably break my kneecaps if I shared it. – ket Apr 18 '16 at 22:15
  • Could you describe what it does? Does it create a new `Binding` with first param as source and second as target? – aateeque Apr 19 '16 at 09:54
  • It basically ends up compiling the setter expression and subscribes that to the observable; the resulting IDisposable is returned. – ket Apr 19 '16 at 14:10
  • A better name would perhaps be `SubscribeToProperty`? – aateeque Apr 19 '16 at 20:54

2 Answers2

2

This produces the values that you want:

var Obs1 = new Subject<string>();
var Obs2 = new Subject<string>();

var query = Obs2.Publish(pobs2 => Obs1.Select(x => pobs2.Skip(1)).Switch());

query.Subscribe(Console.WriteLine);

Obs1.OnNext("X0");
Obs2.OnNext("Y0");
Obs2.OnNext("Y1");
Obs2.OnNext("Y2");
Obs1.OnNext("X1");
Obs2.OnNext("Y3");
Obs2.OnNext("Y4");
Obs1.OnNext("X2");
Obs2.OnNext("Y5");
Obs2.OnNext("Y6");
Obs2.OnNext("Y7");
Obs2.OnNext("Y8");

I get:

Y1
Y2
Y4
Y6
Y7
Y8
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
0

It seems what you are trying to do is to decipher whether the SelectedItem is set by the IObservable or by the user. I wonder if one way to share the state is by using a flag to notify whether the SelectedItem is being changed by the change in the Target object or by the user, so:

sealed class SelectionViewModel
{
    private bool _setByTarget = false;

    internal SelectionViewModel( )
    {
        this.WhenAnyValue(x => x.Target).Where(t => t != null)
            .Select(_ => Target.SpecialValue)
            .Do(_ => _setByTarget = true)
            .BindToProperty(this, x => x.SelectedItem);

        this.WhenAnyValue(x => x.Target)
            .Where(t => t != null && !_setByTarget )
            .Select(_ => this.WhenAnyValue(x => x.SelectedItem))
            .Switch()
            .BindToProperty(this, x => Target.SpecialValue);

        this.WhenAnyValue(x => x.Target)
            .Where(!_setByTarget)
            .Subscribe(_ => _setByTarget = false);
    }

    private MyClass _selectedItem;

    public MyClass SelectedItem
    {
        get { return _selectedItem; }
        set { SetProperty(ref _selectedItem, value); }
    }

    private ITarget _target;

    public ITarget Target
    {
        get { return _target; }
        set { SetProperty(ref _target, value); }
    }
}

This solution uses the side-effect inducing .Do which is not good practice because side-effects are bad!

aateeque
  • 2,161
  • 5
  • 23
  • 35
  • Interesting. I wasn't aware of the Do method (which seems to be the case for most Observable extension methods). I might give this a try - side effects beat non-functionality! – ket Apr 18 '16 at 22:17
  • Having given it a second thought, I am not sure it will work this way. because it depends which order the setting of that flag occurs in. To control that you could do an `ObserveOn(EventLoopScheduler.Instance)` - but I suspect there's a more elegant solution to this than what I have proposed – aateeque Apr 19 '16 at 09:55
  • I'll give a couple days to see if anyone else has any ideas; if not, you win! – ket Apr 19 '16 at 14:11