Q:

How to handle updates to a WPF DataGrid manually?

The Items for my DataGrid is an IList.  The only way the IList is updated is via methods on the class of which it is a property.
class SomeObject
{
    public ReadOnlyCollection<SomeType> Items { get; }

    public void AddItem(SomeType someType);

    public event Action<SomeType> ItemAdded;
}

The list itself is a read-only collection and cannot be updated directly - iow, it is definitely not an ObservableCollection.  Is there a way I can bind to the DataGrid for display purposes, but handle the create, update, and delete to the items myself using binding/datagrid hooks of some kind?

A:

Create a proxy for your collection that implements both IBindingList and ICancelAddNew.  With these two you can "intercept" calls to add new items and remove existing ones.  The method AddNew() is called when a new item is added, and Remove/RemoveAt is called when an item is deleted.  Instead of directly modifying the collection, at these times you can call API methods designed for that purpose.  Below is the minimal implementation required.  Note a few things:

New items are not immediately added to the collection - the are stored temporarily in a _newItem field
If escape is hit while editing a new item, first CancelNew, then EndNew is called
It is assumed 'Items' is an observable collection, the events from which trigger corresponding ListChanged events
This technique doesn't provide a means to intercept edits made to properties of existing items

...
class SomeTypeBindingList : IBindingList, ICancelAddNew
{
    public SomeTypeBindingList(SomeObject someObject)
    {
        _someObject = someObject;

        var observableCollection = _someObject.Items as ObservableCollection<SomeType>;
        if (observableCollection != null)
            observableCollection.CollectionChanged += ObservableCollectionOnCollectionChanged;
    }

    public IEnumerator GetEnumerator()
    {
        return new SomeTypeEnumerator(this);
    }

    public int Count => _someObject.Items.Count + (_newItem == null ? 0 : 1);

    public object SyncRoot { get; } = new object();

    public bool IsSynchronized { get; } = false;

    public bool Contains(object value)
    {
        return IndexOf(value) != -1;
    }

    public int IndexOf(object value)
    {
        if (ReferenceEquals(value, _newItem))
            return _someObject.Items.Count;

        return _someObject.Items.IndexOf((SomeType)value);
    }

    public void Remove(object value)
    {
        var someType = (SomeType)value;
        _someObject.RemoveItem(someType);
    }

    public void RemoveAt(int index)
    {
        var someType = _someObject.Items[index];
        _someObject.RemoveItem(someType);
    }

    public object this[int index]
    {
        get
        {
            if (index >= _someObject.Items.Count)
            {
                if(_newItem == null)
                    throw new IndexOutOfRangeException();

                return _newItem;
            }

            return _someObject.Items[index];
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public object AddNew()
    {
        _newItem = new SomeType();

        ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.ItemAdded, _someObject.Items.Count));
        return _newItem;
    }

    public void CancelNew(int itemIndex)
    {
        _newItem = null;
        ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, itemIndex));
    }

    public void EndNew(int itemIndex)
    {
        if (_newItem != null)
        {
            var someType = _newItem;
            _newItem = null;
            _someObject.AddItem(someType);
        }
    }

    private void ObservableCollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Remove)
            Enumerable.Range(e.OldStartingIndex, e.OldItems.Count).ForEach(i => ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, i)));
        else if(e.Action == NotifyCollectionChangedAction.Add)
            Enumerable.Range(e.NewStartingIndex, e.NewItems.Count).ForEach(i => ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.ItemAdded, i)));
    }

    private readonly SomeObject _someObject;
    private SomeType _newItem;

    class SomeTypeEnumerator : IEnumerator
    {
        public SomeTypeEnumerator(SomeObject someObject)
        {
            _someObject = someObject;
            Reset();
        }

        public void Dispose()
        {

        }

        public bool MoveNext()
        {
            _index++;
            return _someObject.Items.Count < _index;
        }

        public void Reset()
        {
            _index = -1;
        }

        public object Current => _someObject.Items[_index];

        object IEnumerator.Current
        {
            get { return Current; }
        }

        private readonly SomeObject _someObject;
        private int _index;
    }
}

