void OnItemMoved(object item, int oldIndex, int newIndex) { if (_itemMap == null) { // reentrant call (from RemoveAllInternal) shouldn't happen, // but if it does, don't crash Debug.Assert(false, "unexpected reentrant call to OnItemMoved"); return; } DependencyObject container = null; // the corresponding container int containerCount = 0; UnrealizedItemBlock uib; // search for the moved item GeneratorPosition position; ItemBlock block; int offsetFromBlockStart; int correctIndex; GetBlockAndPosition(item, oldIndex, true, out position, out block, out offsetFromBlockStart, out correctIndex); GeneratorPosition oldPosition = position; RealizedItemBlock rib = block as RealizedItemBlock; if (rib != null) { containerCount = 1; container = rib.ContainerAt(offsetFromBlockStart); } // remove the item, and remove the block if it's now empty MoveItems(block, offsetFromBlockStart + 1, block.ItemCount - offsetFromBlockStart - 1, block, offsetFromBlockStart, 0); --block.ItemCount; RemoveAndCoalesceBlocksIfNeeded(block); // // now insert into the new spot. // position = new GeneratorPosition(-1,0); block = _itemMap.Next; offsetFromBlockStart = newIndex; while (block != _itemMap && offsetFromBlockStart >= block.ItemCount) { offsetFromBlockStart -= block.ItemCount; if (block.ContainerCount > 0) { position.Index += block.ContainerCount; position.Offset = 0; } else { position.Offset += block.ItemCount; } block = block.Next; } position.Offset += offsetFromBlockStart + 1; // if it's an unrealized block, add the item by bumping the count uib = block as UnrealizedItemBlock; if (uib != null) { MoveItems(uib, offsetFromBlockStart, 1, uib, offsetFromBlockStart+1, 0); ++ uib.ItemCount; } // 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; } // otherwise, create a new unrealized block else { uib = new UnrealizedItemBlock(); uib.ItemCount = 1; // split the current realized block, if necessary 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; offsetFromBlockStart = 0; block = newBlock; } uib.InsertBefore(block); } DependencyObject parent = VisualTreeHelper.GetParentInternal(container); // tell layout what happened if (ItemsChanged != null) { ItemsChanged(this, new ItemsChangedEventArgs(NotifyCollectionChangedAction.Move, position, oldPosition, 1, containerCount)); } // unhook the container. Do this after layout has (presumably) removed it from // the UI, so that it doesn't inherit DataContext falsely. if (container != null) { if (parent == null || VisualTreeHelper.GetParentInternal(container) != parent) { UnlinkContainerFromItem(container, item); } else { // If the container has the same visual parent as before then that means that // the container was just repositioned within the parent's VisualCollection. // we don't need to unlink the container, but we do need to re-realize the block. Realize(uib, offsetFromBlockStart, item, container); } } // fix up the AlternationIndex on containers affected by the move if (_alternationCount > 0) { // start with the smaller of the two positions, and proceed forward. // This tends to preserve the AlternatonIndex on containers at the // front of the list, as users expect int index = Math.Min(oldIndex, newIndex); GetBlockAndPosition(index, out position, out block, out offsetFromBlockStart); SetAlternationIndex(block, offsetFromBlockStart, GeneratorDirection.Forward); } }
// "Realize" the item in a block at the given offset, to be // the given item with corresponding container. This means updating // the item map data structure so that the item belongs to a Realized block. // It also requires updating the state of every generator to track the // changes we make here. void Realize(UnrealizedItemBlock block, int offset, object item, DependencyObject container) { RealizedItemBlock prevR, nextR; RealizedItemBlock newBlock; // new location of the target item int newOffset; // its offset within the new block int deltaCount; // diff between cumulative item count of block and newBlock // if we're realizing the leftmost item and there's room in the // previous block, move it there if (offset == 0 && (prevR = block.Prev as RealizedItemBlock) != null && prevR.ItemCount < ItemBlock.BlockSize) { newBlock = prevR; newOffset = prevR.ItemCount; MoveItems(block, offset, 1, newBlock, newOffset, -prevR.ItemCount); MoveItems(block, 1, block.ItemCount, block, 0, +1); } // if we're realizing the rightmost item and there's room in the // next block, move it there else if (offset == block.ItemCount - 1 && (nextR = block.Next as RealizedItemBlock) != null && nextR.ItemCount < ItemBlock.BlockSize) { newBlock = nextR; newOffset = 0; MoveItems(newBlock, 0, newBlock.ItemCount, newBlock, 1, -1); MoveItems(block, offset, 1, newBlock, newOffset, offset); } // otherwise we need a new block for the target item else { newBlock = new RealizedItemBlock(); newOffset = 0; deltaCount = offset; // if target is leftmost item, insert it before remaining items if (offset == 0) { newBlock.InsertBefore(block); MoveItems(block, offset, 1, newBlock, newOffset, 0); MoveItems(block, 1, block.ItemCount, block, 0, +1); } // if target is rightmost item, insert it after remaining items else if (offset == block.ItemCount - 1) { newBlock.InsertAfter(block); MoveItems(block, offset, 1, newBlock, newOffset, offset); } // otherwise split the block into two, with the target in the middle else { UnrealizedItemBlock newUBlock = new UnrealizedItemBlock(); newUBlock.InsertAfter(block); newBlock.InsertAfter(block); MoveItems(block, offset+1, block.ItemCount-offset-1, newUBlock, 0, offset+1); MoveItems(block, offset, 1, newBlock, 0, offset); } } RemoveAndCoalesceBlocksIfNeeded(block); // add the new target to the map newBlock.RealizeItem(newOffset, item, container); }
// Called when an item is added to the items collection void OnItemAdded(object item, 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(item, ref index); GeneratorPosition position = new GeneratorPosition(-1,0); // find the block containing the new item ItemBlock block = _itemMap.Next; int offset = index; while (block != _itemMap && offset >= block.ItemCount) { offset -= block.ItemCount; position.Index += block.ContainerCount; block = block.Next; } position.Offset = offset + 1; // if it's an unrealized block, add the item by bumping the count UnrealizedItemBlock uib = block as UnrealizedItemBlock; if (uib != null) { MoveItems(uib, offset, 1, uib, offset+1, 0); ++ uib.ItemCount; } // if the item can be added to a previous unrealized block, do so else if ((offset == 0 || block == _itemMap) && ((uib = block.Prev as UnrealizedItemBlock) != null)) { ++ uib.ItemCount; } // otherwise, create a new unrealized block else { uib = new UnrealizedItemBlock(); uib.ItemCount = 1; // split the current realized block, if necessary RealizedItemBlock rib; if (offset > 0 && (rib = block as RealizedItemBlock) != null) { RealizedItemBlock newBlock = new RealizedItemBlock(); MoveItems(rib, offset, rib.ItemCount - offset, newBlock, 0, offset); 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, +1, uib, 0, 0); } // tell layout what happened if (ItemsChanged != null) { ItemsChanged(this, new ItemsChangedEventArgs(NotifyCollectionChangedAction.Add, position, 1, 0)); } }
// Called when an item is added to the items collection void OnItemAdded(object item, int index) { ValidateAndCorrectIndex(item, ref index); GeneratorPosition position = new GeneratorPosition(-1,0); // find the block containing the new item ItemBlock block = _itemMap.Next; int offset = index; while (block != _itemMap && offset >= block.ItemCount) { offset -= block.ItemCount; position.Index += block.ContainerCount; block = block.Next; } position.Offset = offset + 1; // if it's an unrealized block, add the item by bumping the count UnrealizedItemBlock uib = block as UnrealizedItemBlock; if (uib != null) { MoveItems(uib, offset, 1, uib, offset+1, 0); ++ uib.ItemCount; } // if the item can be added to a previous unrealized block, do so else if ((offset == 0 || block == _itemMap) && ((uib = block.Prev as UnrealizedItemBlock) != null)) { ++ uib.ItemCount; } // otherwise, create a new unrealized block else { uib = new UnrealizedItemBlock(); uib.ItemCount = 1; // split the current realized block, if necessary RealizedItemBlock rib; if (offset > 0 && (rib = block as RealizedItemBlock) != null) { RealizedItemBlock newBlock = new RealizedItemBlock(); MoveItems(rib, offset, rib.ItemCount - offset, newBlock, 0, offset); newBlock.InsertAfter(rib); position.Index += block.ContainerCount; position.Offset = 1; block = newBlock; } uib.InsertBefore(block); } if (_traceLog != null) _traceLog.Add("OnItemAdded {0} index = {1}", TraceLog.IdFor(item), index); // tell generators what happened if (MapChanged != null) { MapChanged(null, index, +1, null, 0, 0); } // tell layout what happened if (ItemsChanged != null) { ItemsChanged(this, new ItemsChangedEventArgs(NotifyCollectionChangedAction.Add, position, 1, 0)); } }