Skip to content

Support range notifications #9568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2400,25 +2400,25 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (args.NewItems.Count != 1)
if (args.NewItems.Count < 1)
throw new NotSupportedException(SR.RangeActionsNotSupported);
OnItemAdded(args.NewItems[0], args.NewStartingIndex);
OnItemsAdded(args.NewItems, args.NewStartingIndex);
break;

case NotifyCollectionChangedAction.Remove:
if (args.OldItems.Count != 1)
if (args.OldItems.Count < 1)
throw new NotSupportedException(SR.RangeActionsNotSupported);
OnItemRemoved(args.OldItems[0], args.OldStartingIndex);
OnItemsRemoved(args.OldItems, args.OldStartingIndex);
break;

case NotifyCollectionChangedAction.Replace:
// Don't check arguments if app targets 4.0, for compat ( 726682)
if (!FrameworkCompatibilityPreferences.TargetsDesktop_V4_0)
{
if (args.OldItems.Count != 1)
if (args.OldItems.Count < 1)
throw new NotSupportedException(SR.RangeActionsNotSupported);
}
OnItemReplaced(args.OldItems[0], args.NewItems[0], args.NewStartingIndex);
OnItemsReplaced(args.OldItems, args.NewItems, args.NewStartingIndex);
break;

case NotifyCollectionChangedAction.Move:
Expand Down Expand Up @@ -2446,6 +2446,86 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
}
}

// Called when an items are added to the items collection
void OnItemsAdded(IList items, int index)
{
if (_itemMap == null)
{
// reentrant call (from RemoveAllInternal) shouldn't happen,
// but if it does, don't crash
Debug.Assert(false, "unexpected reentrant call to OnItemAdded");
return;
}

ValidateAndCorrectIndex(items[0], ref index);

GeneratorPosition position = new GeneratorPosition(-1, 0);

// find the block containing the new item
ItemBlock block = _itemMap.Next;
int offsetFromBlockStart = index;
int unrealizedItemsSkipped = 0; // distance since last realized item
while (block != _itemMap && offsetFromBlockStart >= block.ItemCount)
{
offsetFromBlockStart -= block.ItemCount;
position.Index += block.ContainerCount;
unrealizedItemsSkipped = (block.ContainerCount > 0) ? 0 : unrealizedItemsSkipped + block.ItemCount;
block = block.Next;
}

position.Offset = unrealizedItemsSkipped + offsetFromBlockStart + 1;
// the position is now correct, except when pointing into a realized block;
// that case is fixed below

// if it's an unrealized block, add the item by bumping the count
UnrealizedItemBlock uib = block as UnrealizedItemBlock;
if (uib != null)
{
MoveItems(uib, offsetFromBlockStart, items.Count, uib, offsetFromBlockStart + 1, 0);
uib.ItemCount += items.Count;
}

// if the item can be added to a previous unrealized block, do so
else if ((offsetFromBlockStart == 0 || block == _itemMap) &&
((uib = block.Prev as UnrealizedItemBlock) != null))
{
uib.ItemCount += items.Count;
}

// otherwise, create a new unrealized block
else
{
uib = new UnrealizedItemBlock();
uib.ItemCount = items.Count;

// split the current realized block, if necessary
RealizedItemBlock rib;
if (offsetFromBlockStart > 0 && (rib = block as RealizedItemBlock) != null)
{
RealizedItemBlock newBlock = new RealizedItemBlock();
MoveItems(rib, offsetFromBlockStart, rib.ItemCount - offsetFromBlockStart, newBlock, 0, offsetFromBlockStart);
newBlock.InsertAfter(rib);
position.Index += block.ContainerCount;
position.Offset = 1;
block = newBlock;
}

uib.InsertBefore(block);
}

// tell generators what happened
if (MapChanged != null)
{
MapChanged(null, index, items.Count, uib, 0, 0);
}

// tell layout what happened
if (ItemsChanged != null)
{
ItemsChanged(this, new ItemsChangedEventArgs(NotifyCollectionChangedAction.Add, position, items.Count, 0));
}
}

// Called when an item is added to the items collection
void OnItemAdded(object item, int index)
{
Expand Down Expand Up @@ -2526,6 +2606,15 @@ void OnItemAdded(object item, int index)
}
}

// Called when items are removed from the items collection
// TODO this could probably be improved
void OnItemsRemoved(IList items, int itemIndex)
{
foreach (var item in items)
{
OnItemRemoved(item, itemIndex);
}
}

// Called when an item is removed from the items collection
void OnItemRemoved(object item, int itemIndex)
Expand Down Expand Up @@ -2592,6 +2681,33 @@ void OnItemRemoved(object item, int itemIndex)
}
}

// this could probably be optimized
void OnItemsReplaced(IList oldItems, IList newItems, int index)
{
// Handle replacements for the overlapping part
for (int i = 0; i < Math.Min(oldItems.Count, newItems.Count); i++)
{
OnItemReplaced(oldItems[i], newItems[i], index + i);
}

// Handle extra removals (oldItems has more elements)
if (oldItems.Count > newItems.Count)
{
for (int i = oldItems.Count - 1; i >= newItems.Count; i--)
{
OnItemRemoved(oldItems[i], index + i);
}
}
// Handle extra additions (newItems has more elements)
else if (newItems.Count > oldItems.Count)
{
for (int i = oldItems.Count; i < newItems.Count; i++)
{
OnItemAdded(newItems[i], index + i);
}
}
}

void OnItemReplaced(object oldItem, object newItem, int index)
{
// search for the replaced item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1890,6 +1890,8 @@ void ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs

object oldItem = (args.OldItems != null && args.OldItems.Count > 0) ? args.OldItems[0] : null;
object newItem = (args.NewItems != null && args.NewItems.Count > 0) ? args.NewItems[0] : null;
IList oldItems = args.OldItems ?? Array.Empty<object>();
IList newItems = args.NewItems ?? Array.Empty<object>();

LiveShapingList lsList = InternalList as LiveShapingList;
LiveShapingItem lsi;
Expand Down Expand Up @@ -1925,7 +1927,7 @@ void ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs
if (!IsGrouping)
{
AdjustCurrencyForAdd(adjustedNewIndex);
args = new NotifyCollectionChangedEventArgs(effectiveAction, newItem, adjustedNewIndex);
args = new NotifyCollectionChangedEventArgs(effectiveAction, newItems, adjustedNewIndex);
}
else
{
Expand Down Expand Up @@ -1963,7 +1965,7 @@ void ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs
if (!IsGrouping)
{
AdjustCurrencyForRemove(adjustedOldIndex);
args = new NotifyCollectionChangedEventArgs(effectiveAction, args.OldItems[0], adjustedOldIndex);
args = new NotifyCollectionChangedEventArgs(effectiveAction, oldItems, adjustedOldIndex);
}
else
{
Expand Down Expand Up @@ -1993,7 +1995,7 @@ void ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs
if (!IsGrouping)
{
AdjustCurrencyForReplace(adjustedOldIndex);
args = new NotifyCollectionChangedEventArgs(effectiveAction, args.NewItems[0], args.OldItems[0], adjustedOldIndex);
args = new NotifyCollectionChangedEventArgs(effectiveAction, newItems, oldItems, adjustedOldIndex);
}
else
{
Expand Down Expand Up @@ -2392,34 +2394,58 @@ internal void AdjustShadowCopy(NotifyCollectionChangedEventArgs e)
case NotifyCollectionChangedAction.Add:
if (e.NewStartingIndex > _unknownIndex)
{
ShadowCollection.Insert(e.NewStartingIndex, e.NewItems[0]);
ShadowCollection.InsertRange(e.NewStartingIndex, e.NewItems);
}
else
{
ShadowCollection.Add(e.NewItems[0]);
ShadowCollection.AddRange(e.NewItems);
}
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldStartingIndex > _unknownIndex)
{
ShadowCollection.RemoveAt(e.OldStartingIndex);
ShadowCollection.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
}
else
{
ShadowCollection.Remove(e.OldItems[0]);
for (int i = e.OldStartingIndex; i < e.OldItems.Count; i++)
{
ShadowCollection.Remove(e.OldItems[i]);
}
}
break;
case NotifyCollectionChangedAction.Replace:
if (e.OldStartingIndex > _unknownIndex)
{
ShadowCollection[e.OldStartingIndex] = e.NewItems[0];
if (e.NewItems.Count == e.OldItems.Count)
{
for (int i = 0; i < e.NewItems.Count; i++)
{
ShadowCollection[e.OldStartingIndex + i] = e.NewItems[i];
}
}
else
{
if (e.OldItems.Count > 0)
{
ShadowCollection.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
}

if (e.NewItems.Count > 0)
{
ShadowCollection.InsertRange(e.OldStartingIndex, e.NewItems);
}
}
}
else
{
// allow the ShadowCollection to throw the IndexOutOfRangeException
// if the item is not found.
tempIndex = ShadowCollection.IndexOf(e.OldItems[0]);
ShadowCollection[tempIndex] = e.NewItems[0];
for (int i = 0; i < e.OldItems.Count; i++)
{
tempIndex = ShadowCollection.IndexOf(e.OldItems[i]);
ShadowCollection[tempIndex] = e.NewItems[i];
}
}
break;
case NotifyCollectionChangedAction.Move:
Expand Down Expand Up @@ -2522,17 +2548,17 @@ private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewItems.Count != 1)
if (e.NewItems.Count < 1)
throw new NotSupportedException(SR.RangeActionsNotSupported);
break;

case NotifyCollectionChangedAction.Remove:
if (e.OldItems.Count != 1)
if (e.OldItems.Count < 1)
throw new NotSupportedException(SR.RangeActionsNotSupported);
break;

case NotifyCollectionChangedAction.Replace:
if (e.NewItems.Count != 1 || e.OldItems.Count != 1)
if (e.NewItems.Count < 1 || e.OldItems.Count < 1)
throw new NotSupportedException(SR.RangeActionsNotSupported);
break;

Expand Down