Update ItemsControl.ItemsSource based on selected object of a another ItemsControl - c#

We use ReactiveUI and DynamicData and have two ListBoxes: ListBoxA and ListBoxB. Based on the selection of ListBoxA, the list in ListBoxB should be updated (Not filtered). Seems straightforward but I am having some trouble refreshing ListBoxB
The ListBoxes are bound like so in the View:
this.OneWayBind(ViewModel, vm => vm.ItemsA, v => v.ListBoxA.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedItemA, v => v.ListBoxA.SelectedItem).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ItemsB, v => v.ListBoxB.ItemsSource).DisposeWith(disposables);
ViewModel:=
_storage.PoolA.Connect()
.Transform(m => new ViewModelForA(m))
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(out _itemsA)
.Subscribe();
SelectedItemA.PoolB.Connect()
.Transform(c => new ViewModelForB(c))
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(out _itemsB)
.Subscribe();
Any help would be much appreciated!

Every time you select something in ListBoxA your SelectedItemA object changes and you need to recreate you connection to it.
At first we create an IObservable from SelectedItemA and then use Switch operator from DynamicData
class MyViewModel: INotifyPropertyChange
{
public MyViewModel()
{
_storage.PoolA.Connect()
...
this.WhenPropertyChanged(x => x.SelectedItemA)
.Select(x => x.Value.PoolB)
.Switch() // Switch from DynamicData
.Transform(c => new ViewModelForB(c))
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(out _itemsB)
.Subscribe();
}
}

Related

Automapper for member condition

I am using auto mapper 6.1 and I want to map some values from one object to another, but there is a condition that those values can not be null and not all object properties are supposed to be mapped if so I could easily use ForAllMembers conditions. What I am trying to do is:
config.CreateMap<ClassA, ClassB>()
.ForMember(x => x.Branch, opt => opt.Condition(src => src.Branch != null),
cd => cd.MapFrom(map => map.Branch ?? x.Branch))
Also tried
config.CreateMap<ClassA, ClassB>().ForMember(x => x.Branch, cd => {
cd.Condition(map => map.Branch != null);
cd.MapFrom(map => map.Branch);
})
In another words for every property I define in auto mapper configuration I want to check if its null, and if it is null leave value from x.
Call for such auto mapper configuration would look like:
ClassA platform = Mapper.Map<ClassA>(classB);
If I've understood correctly, it may be simpler than you think. The opt.Condition is not necessary because the condition is already being taken care of in MapFrom.
I think the following should achieve what you want: it will map Branch if it's not null. If Branch (from the source) is null, then it will set the destination to string.Empty.
config.CreateMap<ClassA, Class>()
.ForMember(x => x.Branch, cd => cd.MapFrom(map => map.Branch ?? string.Empty));
And if you need to use another property from x instead of string.Empty, then you can write:
config.CreateMap<ClassA, Class>()
.ForMember(x => x.Branch, cd => cd.MapFrom(map => map.Branch ?? x.AnotherProperty));
If you want to implement complex logic but keep the mapping neat, you can extract your logic into a separate method. For instance:
config.CreateMap<ClassA, Class>()
.ForMember(x => x.Branch, cd => cd.MapFrom(map => MyCustomMapping(map)));
private static string MyCustomMapping(ClassA source)
{
if (source.Branch == null)
{
// Do something
}
else
{
return source.Branch;
}
}
You don't need the MapFrom, but you need a PreCondition instead. See here.

C# Entity - Delete Items by ID - ID to delete are in an other list

I need to delete items in a collection managed by Entity 6. The items that have to be deleted are in an other list
Currently I do this :
idToDelete = model.Courses.Where(x => x.Deleted).Select(x => x.Id);
entity.Courses
.Where(ent => idToDelete.Contains(ent.Id))
.ToList()
.ForEach(ent =>
_contexte.Entry(ent).State = EntityState.Deleted);
This code works fine.
How to perform the same in only one linq instruction?
entity.Courses.RemoveRange(entity.Courses.Where(e => e.Deleted));
Actually, reading your question again, it looks like the list of IDs is in a view model, and possibly your entity model doesn't have the Deleted property. So presumably you need something rather more like what you had:
_contexte.Courses.RemoveRange(
_context.Courses.Where(c => model.Courses.Where(x => x.Deleted).Select(y => y.Id).Contains(c.Id));
...or more readably:
var idsToDelete = model.Courses.Where(c => c.Deleted).Select(e => e.Id);
var entitiesToDelete = _contexte.Courses.Where(c => idsToDelete.Contains(c.Id));
_contexte.Courses.RemoveRange(entitiesToDelete);
You can use let to declare your idToDelete within the query:
(from ent in in entity.Courses
let idsToDelete = model.Courses.Where(x => x.Deleted).Select(x => x.Id)
where idsToDelete.Contains(ent.Id)
select ent).ToList()
.ForEach(ent => _contexte.Entry(ent).State = EntityState.Deleted);

WhenAny and ToProperty

I think I am missing a fundamental Observable concept.
I have a ChargeViewModel class which has a ReactiveList property which is a collection of itself
ChargeViewModel.Charges which contains related charges
I want to observe the latest entry in the charges collection (it has a ChargeViewModel.lastModified) and also have other properties in the UI based on the latest entry.
In the ctor I have this code which works to initialize the values; however it does not update the values for latestActionDate, latestBillToName, lastestNote when the observable variable "last" changes
I want the UI to update if the user updates the "last" charge or creates a new "last".
var last = Charges?
.Changed
.Select(_ => Charges.OrderByDescending(c => c.Model.LastModified).FirstOrDefault())
.StartWith(Charges.OrderByDescending(c =>c.Model.LastModified).FirstOrDefault());
last
.Select(c => c.Model.LastModified)
.ToProperty(this, vm => vm.LatestActionDate, out latestActionDate);
last
.Select(c => c.Model.BillToName)
.ToProperty(this, vm => vm.LatestBillToName, out latestBillToName);
last
.Select(c => c.Model.Note)
.ToProperty(this, vm => vm.LatestNote, out latestNote);
ReactiveList (by default) only notifies upon elements added/moved/removed, not upon content changes. It's fairly simply to enable it (and maybe you already did) by setting ChangeTrackingEnabled=true.
However when enabled, you'll receive such motifications on ItemChanged observable, and not on Changed one, so in your case you probably need to do:
Charges.Changed.Select(_ => Unit.Default).Merge(Charges.ItemChanged.Select(_ => Unit.Default))
.Select(_ => /* ... */)
....

ReactiveList problems

We are relatively new to ReactiveUI so this may explain why we are having some issues with getting a view model working.
In our view model we have a ReactiveList of a class , for which there is a 'selected' in the class.
In the view model we want to have a 'AnySelected' property such that if there is at least 1 item in the list marked as selected then AnySelected is true.
We are having difficulty in getting this work.
As a small test application, with just strings, we have tried this, but messages around changes occurring don't appear.
public class TestRx : ReactiveObject
{
private ReactiveList<string> mySelectedItems;
public ReactiveList<string> MySelectedItems
{
get { return mySelectedItems; }
set { this.RaiseAndSetIfChanged(ref mySelectedItems, value); }
}
public TestRx()
{
var firstList = new ReactiveList<string>();
var t = this.WhenAnyValue(x => x.MySelectedItems);
var t1 = t.Select(x => x ?? new ReactiveList<string>());
var changed = t1.Select(x => x.Changed.Select(_ => Unit.Default));
var itemChanged = t1.Select(x => x.ItemChanged.Select(_ => Unit.Default));
var countChanged = t1.Select(x => x.CountChanged.Select(_ => Unit.Default));
t.Subscribe(x => Debug.WriteLine("T HAS CHANGED {0}", x == firstList));
t1.Subscribe(z => Debug.WriteLine("T1 Changed {0}", z == firstList));
changed.Subscribe(x => Debug.WriteLine("Changed :"));
itemChanged.Subscribe(x => Debug.WriteLine("Item Changed :"));
var replacementList = new ReactiveList<SelItem>(new[] {
new SelItem() { Selected = false }
});
Debug.WriteLine("***********************Assign 1st list");
MySelectedItems = firstList;
Thread.Sleep(100);
Debug.WriteLine("***********************Adding item 2 list");
MySelectedItems.Add("a new string");
// we don't get any debug messages as a result of the above
Thread.Sleep(100);
Debug.WriteLine("***********************Assign null");
MySelectedItems = null;
Thread.Sleep(100);
}
}
What are we doing wrong ?
This is a common pattern, but it's a bit tricky to implement, because you have to handle all of the following scenarios:
The list is set
The list items change
The "Selected" property on any items change. Keep in mind, the items you want to watch, change because of #1 or #2.
How do I do it?
Here's one way to do it. It's complicated, and that hints at a place where future versions of RxUI could make things Better, but here's what you can do for now.
IObservable<bool> WhenAnyAreTrue(IEnumerable<ViewModel> currentElements)
{
// NB: 'Unit' here means, we don't care about the actual value, just
// that something changed
var notifyWhenAnySelectedItemChanges = currentElements
.Select(x => x.WhenAny(y => y.Selected, _ => Unit.Default).Skip(1))
.Merge();
return notifyWhenAnySelectedItemChanges
.StartWith(Unit.Default)
.Select(_ => currentElements.Any(x => x.Selected));
}
// Any time MySelectedItems change or when the items in it change,
// create a new WhenAnyAreTrue and switch to it
this.WhenAnyObservable(x => x.MySelectedItems.ItemsChanged)
.Select(_ => WhenAnyAreTrue(MySelectedItems))
.Switch()
.ToProperty(this, x => x.AnySelected, out anySelected);

ReactiveUI and casting in WhenAny

What I am doing is this:
Item.PropertyChanged += (sender, args) =>
{
if(sender is IInterface)
DoSomethingWith(((IInterface)sender).PropertyFromInterface);
}
how would I go about implementing such a stream in RxUI?
I tried this:
this.WhenAny(x => (x.Item as IInterface).PropertyFromInterface, x.GetValue())
.Subscribe(DoSomethingWith);
but it seems that is not possible to do.
Would I have to make a property like this? ->
private IInterface ItemAsInterface { get { return Item as IInterface; } }
I made a workaround for now like this:
this.WhenAny(x => x.Item, x => x.GetValue()).OfType<IInterface>()
.Select(x => x.PropertyFromInterface).DistinctUntilChanged()
.Subscribe(DoSomethingWith);
but what I actually want is getting the propertychanged updates for "PropertyFromInterface" while Item is of IInterface.
How about:
this.WhenAny(x => x.Item, x => x.Value as IInterface)
.Where(x => x != null)
.Subscribe(DoSomethingWith);
Update: Ok, I vaguely understand what you want to do now - here's how I would do it:
public ViewModelBase()
{
// Once the object is set up, initialize it if it's an IInterface
RxApp.MainThreadScheduler.Schedule(() => {
var someInterface = this as IInterface;
if (someInterface == null) return;
DoSomethingWith(someInterface.PropertyFromInterface);
});
}
If you really want to initialize it via PropertyChanged:
this.Changed
.Select(x => x.Sender as IInterface)
.Where(x => x != null)
.Take(1) // Unsubs automatically once we've done a thing
.Subscribe(x => DoSomethingWith(x.PropertyFromInterface));
Checked back with my old questions, I was looking for something like this solution:
this.WhenAny(x => x.Item, x => x.GetValue())
.OfType<IInterface>()
.Select(x => x.WhenAny(y => y.PropertyFromInterface, y => y.Value)
.Switch()
.Subscribe(DoSomethingWith);
The missing link for me was the .Switch method.
Additionally, I wanted the observable to not do anything if the property is NOT of the needed type:
this.WhenAny(x => x.Item, x => x.Value as IInterface)
.Select(x => x == null ?
Observable.Empty :
x.WhenAny(y => y.PropertyFromInterface, y => y.Value)
.Switch()
.Subscribe(DoSomethingWith);
(e.g. When I set this.Item to an instance of IInterface, I wanted DoSomethingWith to listen to changes to that instance's PropertyFromInterface, and when this.Item gets set to something different, the observable should not continue to fire until this.Item is an instance of IInterface again.)

Categories