InvalidOperationException when removing items

Jun 22, 2012 at 9:16 AM

Hi,

I have a scenario with a ListBox bound to a collection of ViewModels. This collection is actually a Obtics expression that binds further to a model. The model is changed by a background thread, so I use the WorkQueueOnDispatcherProvider to make sure the viewmodels are updated on the UI thread.

Adding items works perfectly fine, but removing them throws an InvalidOperationException in the ListBox (with strange message "Added item does not appear at given index").

I have no idea what I am doing wrong. Please help me.

I created a minimal example to reproduce the behaviour:

 internal class MainViewModel
    {
        private readonly Model dummyModel;

        //for binding to a ListBox
        public IEnumerable<ItemViewModel> ItemCollection { get; private set; }

        public MainViewModel()
        {
            dummyModel = new Model {Items = new ObservableCollection<string>()};
            dummyModel.Items.Add("hello");
            dummyModel.Items.Add("world");

            IValueProvider<IEnumerable<string>> valueProvider =
                ExpressionObserver.Execute(() => Enumerable.Select(dummyModel.Items, s => s));

            //from what I figured out, I have to call Async() before I create the final ItemCollection expression so that
            //CreateViewModel() is called in the UI thread.
            IEnumerable<string> modelItems =
                valueProvider.Cascade().Async(new WorkQueueOnDispatcherProvider().GetWorkQueue());

            ItemCollection =
                ExpressionObserver.Execute(() => Enumerable.Select(modelItems, s => CreateViewModel(s))).Cascade();
        }

        private static ItemViewModel CreateViewModel(string s)
        {
            //this MUST be called in the UI thread because I access some thread-affine objects in my real application.
            return new ItemViewModel {Text = s};
        }

        public void StartOperation()
        {
            Task.Factory.StartNew(
                () =>
                {
                    Thread.Sleep(1000);
                    dummyModel.Items.RemoveAt(1);
                    //dummyModel.Items.Add("hello");
                });
        }
    }

    internal class ItemViewModel
    {
        public string Text { get; set; }
    }

    internal class Model
    {
        public ObservableCollection<string> Items { get; set; }
    }

And the necessary XAML part:

 <Window.Resources>
        <ResourceDictionary>
            <WpfApplication1:MainViewModel x:Key="myMainViewModel">
            </WpfApplication1:MainViewModel>
        </ResourceDictionary>
    </Window.Resources>
    <StackPanel DataContext="{StaticResource ResourceKey=myMainViewModel}">
        <Button Click="Clicked">Start Remove</Button>
        <ListBox ItemsSource="{Binding Path=ItemCollection}"></ListBox>
    </StackPanel>

Coordinator
Jun 25, 2012 at 11:17 PM

Hi,

Problem is that Select(modelItems, s => CreateViewModel(s)) uses the lambda expression both for adding and removing items. So; CreateViewModel(s) gets called when an item is added and when an item is removed. Now; if CreateViewModel(s) does not return an equal object every time it gets called with the same s then it means that a different object will be removed than was originaly added -> error.

You should create a method GetViewModel(s) instead of CreateViewModel(s), like:

TvdP.Collections.WeakValueDictionary<string,ItemViewModel> _registry = new TvdP.Collections.WeakValueDictionary<string,ItemViewModel>()

private static ItemViewModel GetViewModel(string s)
        {
            //this MUST be called in the UI thread because I access some thread-affine objects in my real application.
            return _registry.GetOrAdd(s, s2 => new ItemViewModel {Text = s2});
        }

Jun 26, 2012 at 6:10 PM

Many thanks, that did it.