Search code examples
c#wpfcollectionsbindingderived-class

C# insert method inside derived collection - binding stops working, but outside works


firstly sorry for maybe not the best title, I didn't know how to express it better. This also means I could've missed similar answered questions. Feel free to suggest a better one title for the others :)

My question is pretty simple. I have derived collection limited to my object, it looks like this:

public sealed class TrajectoryCollection<T> : ObservableCollection<T> where T : TrajectoryPoint
{
    public string FileName { get; set; }

    public void AddPointUnder(T selectedPoint)
    {
        var newPoint = (TrajectoryPoint)selectedPoint.Clone();
        var nextIndex = Items.IndexOf(selectedPoint) + 1;

        newPoint.SetPosition(PositionBetween(selectedPoint, Items.ElementAt(nextIndex)));

        Items.Insert(nextIndex, (T)newPoint);
        RecalculatePointIndexes();
    }

    public void RemovePoint(T item)
    {
        Remove(item);
        RecalculatePointIndexes();
    }

    private void RecalculatePointIndexes()
    {
        for (int i = 0; i < Count; i++)
        {
            Items.ElementAt(i).Number = i + 1;
        }
    }

    private Point3D PositionBetween(Point3D first, Point3D second)
    {
        return new Point3D(Math.Round((first.X + second.X) / 2, 3), Math.Round((first.Y + second.Y) / 2, 3), Math.Round((first.Z + second.Z) / 2, 3));
    }
}

and in my ViewModel I have this (Trajectory is bound to ListView itemsource):

public TrajectoryCollection<TrajectoryPoint> Trajectory
{
    get => _trajectory;
    set => SetProperty(ref _trajectory, value);
}    
public TrajectoryPoint SelectedPoint
{
    get => _selectedPoint;
    set => SetProperty(ref _selectedPoint, value);
}

private void RemovePointFromTrajectoryExecute()
{
    var selectedIndex = Trajectory.IndexOf(SelectedPoint);
    Trajectory.RemovePoint(SelectedPoint);
    SelectedPoint = Trajectory.ElementAt(selectedIndex);
    RefreshViewport();
}

private void AddPointToTrajectoryExecute()
{
    var selectedIndex = Trajectory.IndexOf(SelectedPoint);
    Trajectory.AddPointUnder(SelectedPoint);
    SelectedPoint = Trajectory.ElementAt(selectedIndex + 1);
    RefreshViewport();
}

private void RefreshViewport()
{
    RaisePropertyChanged("Trajectory");
    RaisePropertyChanged("SelectedPoint");
}

Removing point works but adding doesn't. After adding one point, it IS added to that collection (I have verified it), but ListView won't show it - it behaves like it skips that added line (so it shows for example items 1,2,3,5,6...) and when I scroll down, it throws an exception

System.InvalidOperationException: 'An ItemsControl is inconsistent with its items source.

BUT. When I move that code from derived collection to my ViewModel, so it looks like this:

private void AddPointToTrajectoryExecute()
{
    var newPoint = (TrajectoryPointModel)SelectedPoint.Clone();
    var nextIndex = Trajectory.IndexOf(SelectedPoint) + 1;
    //new Point3D is PositionBetween
    newPoint.SetPosition(new Point3D(Math.Round((SelectedPoint.X + Trajectory.ElementAt(nextIndex).X) / 2, 3), Math.Round((SelectedPoint.Y + Trajectory.ElementAt(nextIndex).Y) / 2, 3), Math.Round((SelectedPoint.Z + Trajectory.ElementAt(nextIndex).Z) / 2, 3)));

    Trajectory.Insert(nextIndex, newPoint);
    RefreshViewport();
}

it works as it is supposed to and without any exceptions!

Could please anyone explain why this is happening? And what can I do to make working my Collection.AddPointUnder() method?

Thank you in advance!


Solution

  • I think the problem is as follows: In your add you are inserting in the underlying Items collection. While the remove happens directly on the ObservableCollection. Use Insert or InsertItem on the ObservableCollection and it should update your view just fine. This is because the Items collection of the ObservableCollection is not observable.