Creating a ReactiveUI derived collection with more elements than the original
Asked Answered
K

2

11

Is it possible to create a ReactiveUI derived collection that has more elements in it than the original?

I've seen that there is a way of filtering a collection, and selecting single properties, but what I'm looking for is the equivalent of the SelectMany operation for enumerables.

To illustrate, imagine trying to get a derived collection representing every passenger stuck in a traffic jam.

class Car 
{
    ReactiveCollection<Passenger> Passengers;
}

var TrafficJam=new ReactiveCollection<Car>();
EveryPassengerInTheTrafficJam=Cars.CreateDerivedCollection(c=>c.Passengers);

The above doesn't work, I think the error was IEnumerable<ReactiveCollection<Passenger>> can't be cast to ReactiveCollection<Passenger> - or something up with the types, in any case.

I can't figure out the right approach for this flattening - admittedly I may be barking up completely the wrong tree here, so please let me know if there's a better way of achieving the same thing!

Karyn answered 6/3, 2013 at 18:4 Comment(0)
C
13

At the moment, CreateDerivedCollection doesn't support SelectMany as a transformation, it gets too Tricky to handle removes. If you don't have many items, you can just regenerate the collection every time:

cars.Changed
    .Select(_ => cars.SelectMany(x => x.Passengers).ToList())
    .ToProperty(this, x => x.Passengers);

Edit: Alright, here we go:

var whenCarsOrPassengersInThoseCarsChange = Observable.Merge(
    cars.Changed
        .SelectMany(_ =>
            cars.Select(x => x.Passengers.Changed).Merge())
        .Select(_ => Unit.Default),
    cars.Changed.Select(_ => Unit.Default));

whenCarsOrPassengersInThoseCarsChange.StartWith(Unit.Default)
    .Select(_ => cars.SelectMany(x => x.Passengers).ToList())
    .ToProperty(this, x => x.Passengers);

So, the idea is that we've got two main situations when we want to reevaluate the passengers list:

  1. When one of the passengers change in the cars
  2. When one of the cars change

However, the tricky part is, we only want to watch passengers for cars in the collection (i.e. if a car is removed, we no longer care about its passengers).

Properly tracking suicidal passengers

So, the idea in that weird SelectMany is, "Every time the car list changes, build a new list of Observables that represent when the passenger collection changes, and merge them all together".

However, if we only had that statement, we would have to wait for a car to be added and its passengers change before we got a new passenger list, so we also have to update when the list of cars change too.

What's this "Unit" business?

In this case, I actually don't care about the values that these Observables put out, just when they happen. "Unit" is the Rx version of void, it only has a single value, "Unit.Default". You use it when you only care when something happens, not what the value of it is.

Collapse answered 7/3, 2013 at 1:10 Comment(6)
One side effect of this question is now I gotta go learn how your framework works. :)Underpin
So, the reason that the straightforward version doesn't work, is that the cars.SelectMany returns an IEnumerable (a SelectManyEnumerable or something), not a type like ObservableCollection. There is no way for CreateDerivedCollection to figure out how to watch this collection, so it will populate initially but not update as cars has items added / removedCollapse
@PaulBetts Ideally, I'd like to recalculate when a passenger collection in a car changes (someone gets out in desperation, Everybody Hurts style), or when the car collection changes (a new car arrives in the traffic jam), but not on other properties of the car (if WindscreenWipersEnabled becomes true, I don't want to recompute EveryPassengerInTheTrafficJam!). Is this possible?Karyn
@Underpin definitely! I'm a complete ReactiveUI (and reactive programming) newbie, but from what I've seen so far, it is very cool. I love how it makes it possible to describe the whole behaviour of a user interface in a declarative way.Karyn
If I'm not mistaken Paul's code requires to wait if the passengers of a car change before the cars do. I updated it to add StartWith(Unit.Default) before selecting the Passengers.Changed observables and it works.Durra
@PaulBetts so I've used this technique in an app of mine to good effect. But now another question ... how do I track changes on the Passengers? :-) Simple example, say the list of Passengers is bound to a list on the UI. And the Passenger has an IsSelected bool property that is bound to the Selected property of the item in the list. Can I get a derived collection of the selected passengers? Basically, I want to know when the number of selected passengers changes so that a command is only available when there is at least one selected. This one has me stumped so far.Criollo
U
2

Hmm...I'm not all that familiar with ReactiveUI, but just reading thru, it looks like you need to alter where your SelectMany goes:

var jam = cars.SelectMany(x => x.Passengers).CreateDerivedCollection(p => p);
Underpin answered 6/3, 2013 at 22:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.