I’m writing simple WP7 app to study ReactiveUI.
I want to show a collection on the screen, items should be selectable, and I want to have a Command which removes all selected items from the collection. Also the command must be executable only when there is at least one selected item.
I’ve got a collection defined in this way:
Persons = model.Persons
.CreateDerivedCollection(x => new PersonViewModel(x));
In PersonViewModel I have a property:
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { this.RaiseAndSetIfChanged(x => x.IsSelected, ref _isSelected, value); }
}
There is no information about selection state in model, just in viewModel.
In Page ViewModel I have this code:
Persons = model.Persons.CreateDerivedCollection(x => new PersonViewModel(x));
Persons.ChangeTrackingEnabled = true;
var deleteSelectedCanExecute = Persons.ItemChanged
.Select(_ => Persons .Any(p => p.IsSelected));
DeleteSelectedCommand = new ReactiveCommand
(
deleteSelectedCanExecute
);
DeleteSelectedCommand.Subscribe(
x => RemoveSelected()
);
and a method:
private void RemoveSelected()
{
var res = Persons.Where(p => p.IsSelected)
.Select(x => x.Model).ToList();
foreach (var person in res)
{
_model.Persons.Remove(person);
}
}
first question (not so important, I think I can find the solution by myself):
when I run an app the DeleteSelected button is Active.DeleteSelectedCommand.CanExecute does not fires. However after selecting/deselection any item – button states works fine.
and the main problem:
After I run DeleteSelectedCommand it removes all selected items (I see it in the debugger). And then I got “NotSupportedException” with following stack trace:
at System.Threading.Interlocked.Decrement(Int64& location)
at ReactiveUI.RefcountDisposeWrapper.Release()
at ReactiveUI.ReactiveCollection`1.removeItemFromPropertyTracking(PersonViewModel toUntrack)
at ReactiveUI.ReactiveCollection`1.<setupRx>b__18(PersonViewModelx)
at System.Reactive.AnonymousObserver`1.Next(PersonViewModelvalue)
at System.Reactive.AbstractObserver`1.OnNext(PersonViewModelvalue)
at System.Reactive.AutoDetachObserver`1.Next(PersonViewModelvalue)
at System.Reactive.AbstractObserver`1.OnNext(PersonViewModelvalue)
at System.Reactive.ScheduledObserver`1.<>c__DisplayClass4.<Next>b__2()
at System.Reactive.ScheduledObserver`1.<EnsureActive>b__0(Action self)
at System.Reactive.Concurrency.Scheduler.<Schedule>b__0(Action`1 _action, Action`1 self)
at System.Reactive.Concurrency.Scheduler.<>c__DisplayClass9`1.<InvokeRec1>b__6(Action`1 state1)
at System.Reactive.Concurrency.Scheduler.InvokeRec1[TState](IScheduler scheduler, Pair`2 pair)
at System.Reactive.Concurrency.DispatcherScheduler.<>c__DisplayClass1`1.<Schedule>b__0()
at System.Reflection.RuntimeMethodInfo.InternalInvoke(RuntimeMethodInfo rtmi, Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture, Boolean isBinderDefault, Assembly caller, Boolean verifyAccess, StackCrawlMark& stackMark)
at System.Reflection.RuntimeMethodInfo.InternalInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, StackCrawlMark& stackMark)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at System.Delegate.DynamicInvokeOne(Object[] args)
at System.MulticastDelegate.DynamicInvokeImpl(Object[] args)
at System.Delegate.DynamicInvoke(Object[] args)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.Dispatch(DispatcherPriority priority)
at System.Windows.Threading.Dispatcher.OnInvoke(Object context)
at System.Windows.Hosting.CallbackCookie.Invoke(Object[] args)
at System.Windows.Hosting.DelegateWrapper.InternalInvoke(Object[] args)
at System.Windows.RuntimeHost.ManagedHost.InvokeDelegate(IntPtr pHandle, Int32 nParamCount, ScriptParam[] pParams, ScriptParam& pResult)
So I’m doing it wrong, but what’s the problem? I can’t understand from the ST. What’s the right way to implement this behaviour. It’s so common isn’t it?
upd
If I remove all code about deleteSelectedCanExecute and run program – it crashes. If i remove Participants.ChangeTrackingEnabled = true; – it works as I expect.
Anton, you are the proud owner of a new ReactiveUI commit – build from source and your crash should go away.
As to your question about the selection, this is one scenario that is a bit tricky to do at the moment if the collection can change sizes. Code somewhere must not only subscribe to each item in the collection, it must also keep a list of which items are being added or removed (i.e. there are two ways to no longer be selected,
Item.IsSelectedgoing fromtrue=>false, orItembeing removed).If you don’t have a list that changes rapidly, you can do this in a somewhat inefficient though far easier way:
Incidentally, this solution also doesn’t require
ChangeTrackingEnabled, so you don’t need to work around the bug I just fixed.