private void HandleItemRemoveMoveReplace( ItemsGeneratorNode node, NotifyCollectionChangedEventArgs e ) { GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 ); //index not important for now. nodeHelper.ReverseCalculateIndex(); node.AdjustItemCount( -e.OldItems.Count ); node.AdjustLeafCount( -e.OldItems.Count ); int nodeStartIndex = e.OldStartingIndex; int nodeEndIndex = nodeStartIndex + e.OldItems.Count - 1; int detailCountToRemove = CustomItemContainerGenerator.ComputeDetailsCount( node, nodeStartIndex, nodeEndIndex ); int detailCountBeforeRemovedItems = 0; if( nodeStartIndex > 0 ) { detailCountBeforeRemovedItems = CustomItemContainerGenerator.ComputeDetailsCount( node, 0, nodeStartIndex - 1 ); } int startIndex = nodeHelper.Index + e.OldStartingIndex + detailCountBeforeRemovedItems; int endIndex = startIndex + detailCountToRemove + e.OldItems.Count - 1; int removeCount = e.OldItems.Count + detailCountToRemove; int replaceCount = ( e.Action == NotifyCollectionChangedAction.Replace ) ? e.NewItems.Count : 0; // *** RemoveDetails must be done before GeneratorPositionFromIndex, since GeneratorPositionFromIndex will indirectly do a RemapFloatingDetails // *** that will cause the index to already be rectified and make a double rectification to occurs. //Remove the details from the ItemsGeneratorNode and re-index the other details appropriatly. this.RemoveDetails( node, nodeStartIndex, nodeEndIndex, replaceCount ); GeneratorPosition removeGenPos = this.GeneratorPositionFromIndex( startIndex ); //Try to remap the old item for detail remapping (will do nothing if item has no details ) foreach( object oldItem in e.OldItems ) { this.QueueDetailItemForRemapping( oldItem ); } //if the node is totally expanded if( node.IsComputedExpanded ) { List<DependencyObject> removedContainers = new List<DependencyObject>(); int genRemCount = this.RemoveGeneratedItems( startIndex, endIndex, removedContainers ); this.IncrementCurrentGenerationCount(); this.SendRemoveEvent( removeGenPos, startIndex, removeCount, genRemCount, removedContainers ); } //then, based on the action that was performed (move, replace or remove) switch( e.Action ) { case NotifyCollectionChangedAction.Move: this.OffsetDetails( node, e.NewStartingIndex, e.NewItems.Count ); this.HandleItemMoveRemoveReplaceHelper( node, e, nodeHelper.Index ); break; case NotifyCollectionChangedAction.Replace: this.HandleItemMoveRemoveReplaceHelper( node, e, nodeHelper.Index ); break; case NotifyCollectionChangedAction.Remove: // Do nothing! break; case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Reset: default: throw new DataGridInternalException(); } }
private void HandleHeaderFooterRemove( HeadersFootersGeneratorNode node, NotifyCollectionChangedEventArgs e ) { bool itemsRemoved = false; node.AdjustItemCount( -e.OldItems.Count ); //if the node is totally expanded if( node.IsComputedExpanded ) { GroupGeneratorNode parentGroup = node.Parent as GroupGeneratorNode; int removeGenPosIndex; int removeIndex; foreach( object item in e.OldItems ) { object realItem = ( parentGroup != null ) ? new GroupHeaderFooterItem( parentGroup.CollectionViewGroup, item ) : item; removeGenPosIndex = m_genPosToItem.IndexOf( realItem ); GeneratorPosition removeGenPos; // If the value is -1, it means the Header/Footer was not realized when the remove occured. if( removeGenPosIndex != -1 ) { removeIndex = m_genPosToIndex[ removeGenPosIndex ]; removeGenPos = new GeneratorPosition( removeGenPosIndex, 0 ); } else { //Since there is no way to get the item's index from the list of generated items, then //compute it based on the node's index and the event args parameters. GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 ); nodeHelper.ReverseCalculateIndex(); removeIndex = nodeHelper.Index + e.OldStartingIndex; removeGenPos = this.GeneratorPositionFromIndex( removeIndex ); } List<DependencyObject> removedContainers = new List<DependencyObject>(); this.RemoveGeneratedItems( node, realItem, removedContainers ); this.SendRemoveEvent( removeGenPos, removeIndex, 1, removedContainers.Count, removedContainers ); itemsRemoved = true; } this.IncrementCurrentGenerationCount( itemsRemoved ); } }
private void HandleSameLevelGroupMove( GeneratorNode node, NotifyCollectionChangedEventArgs e ) { GroupGeneratorNode parentGroup = node.Parent as GroupGeneratorNode; //Start a NodeHelper on the first child of the node where the move occured. GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 ); nodeHelper.ReverseCalculateIndex(); //determine index of the node. //Advance to the first "Group" node (skip the GroupHEaders) while( !( nodeHelper.CurrentNode is GroupGeneratorNode ) ) { if( !nodeHelper.MoveToNext() ) throw new DataGridInternalException(); } //then move up to the removal start point. if( !nodeHelper.MoveToNextBy( e.OldStartingIndex ) ) { throw new DataGridInternalException(); } //remember the current node as the start point of the move (will be used when "extracting the chain") GeneratorNode startNode = nodeHelper.CurrentNode; //also remember the index of the node, to calculate range of elements to remove (containers ) int startIndex = nodeHelper.Index; //then, cumulate the total number of items in the groups concerned int totalCountRemoved = 0; node = this.ProcessGroupRemoval( startNode, e.OldItems.Count, false, out totalCountRemoved ); //send a message to the panel to remove the visual elements concerned GeneratorPosition removeGenPos = this.GeneratorPositionFromIndex( startIndex ); List<DependencyObject> removedContainers = new List<DependencyObject>(); int genCountRemoved = this.RemoveGeneratedItems( startIndex, startIndex + totalCountRemoved - 1, removedContainers ); this.SendRemoveEvent( removeGenPos, startIndex, totalCountRemoved, genCountRemoved, removedContainers ); //reset the node parameter for the "re-addition" node = ( parentGroup != null ) ? parentGroup.Child : m_firstItem; if( node == null ) throw new DataGridInternalException(); //Once the chain was pulled out, re-insert it at the appropriate location. nodeHelper = new GeneratorNodeHelper( node, 0, 0 ); //do not care about the index for what I need //Advance to the first "Group" node (skip the GroupHEaders) while( !( nodeHelper.CurrentNode is GroupGeneratorNode ) ) { if( !nodeHelper.MoveToNext() ) throw new DataGridInternalException(); } bool insertBefore = nodeHelper.MoveToNextBy( e.NewStartingIndex ); if( insertBefore ) { if( nodeHelper.CurrentNode == m_firstItem ) { if( m_startNode == m_firstItem ) { m_startNode = startNode; } m_firstItem = startNode; } //reinsert the chain at the specified location. nodeHelper.InsertBefore( startNode ); } else { nodeHelper.InsertAfter( startNode ); } //and finally, call to increment the generation count for the generator content this.IncrementCurrentGenerationCount(); }
private void HandleItemAddition( GeneratorNode node, NotifyCollectionChangedEventArgs e ) { GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 ); //index not important for now. node.AdjustItemCount( e.NewItems.Count ); ItemsGeneratorNode itemsNode = node as ItemsGeneratorNode; if( itemsNode != null ) { itemsNode.AdjustLeafCount( e.NewItems.Count ); this.OffsetDetails( itemsNode, e.NewStartingIndex, e.NewItems.Count ); } //if the node is totally expanded if( node.IsComputedExpanded ) { nodeHelper.ReverseCalculateIndex(); //invalidate the indexes this.IncrementCurrentGenerationCount(); int startIndex = nodeHelper.Index + e.NewStartingIndex; GeneratorPosition addGenPos = this.GeneratorPositionFromIndex( startIndex ); //and send notification message this.SendAddEvent( addGenPos, startIndex, e.NewItems.Count ); } }
private void OnGeneratorNodeGroupsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) { if( this.Status == GeneratorStatus.GeneratingContainers ) { throw new InvalidOperationException( "Cannot perform this operation while the generator is busy generating items" ); } GroupGeneratorNode node = sender as GroupGeneratorNode; if( node != null ) { switch( e.Action ) { case NotifyCollectionChangedAction.Add: int addCount; GeneratorNode addNode = this.HandleParentGroupAddition( node, out addCount, e ); if( node.IsComputedExpanded ) { GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( addNode, 0, 0 );//index not important, will reserve find it. nodeHelper.ReverseCalculateIndex(); this.IncrementCurrentGenerationCount(); GeneratorPosition genPos = this.GeneratorPositionFromIndex( nodeHelper.Index ); this.SendAddEvent( genPos, nodeHelper.Index, addCount ); } break; case NotifyCollectionChangedAction.Move: if( node.Child == null ) { throw new DataGridInternalException(); } else { Debug.Assert( e.OldStartingIndex != e.NewStartingIndex, "An attempt was made to move a group to the same location." ); #if LOG Log.Assert( this, e.OldStartingIndex != e.NewStartingIndex, "An attempt was made to move a group to the same location." ); #endif this.HandleSameLevelGroupMove( node.Child, e ); } break; case NotifyCollectionChangedAction.Remove: if( node.Child == null ) { throw new DataGridInternalException(); } else { int remCount; int generatedRemCount; int removeIndex; List<DependencyObject> removedContainers = new List<DependencyObject>(); GeneratorPosition remPos = this.HandleParentGroupRemove( node, out remCount, out generatedRemCount, out removeIndex, e, removedContainers ); if( node.IsComputedExpanded ) { this.IncrementCurrentGenerationCount(); this.SendRemoveEvent( remPos, removeIndex, remCount, generatedRemCount, removedContainers ); } } break; case NotifyCollectionChangedAction.Replace: //if( node.Child == null ) //{ // throw new DataGridInternalException(); //} //else //{ // this.HandleGroupReplace( node.Child, e ); //} //m_currentGeneratorContentGeneration++; throw new DataGridInternalException(); //break; case NotifyCollectionChangedAction.Reset: //m_currentGeneratorContentGeneration++; throw new DataGridInternalException(); default: throw new DataGridInternalException(); } } }
private GeneratorPosition HandleSameLevelGroupRemove( GeneratorNode firstChild, out int countRemoved, out int genCountRemoved, out int removeIndex, NotifyCollectionChangedEventArgs e, IList<DependencyObject> removedContainers ) { GeneratorPosition retval; countRemoved = 0; genCountRemoved = 0; GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( firstChild, 0, 0 ); nodeHelper.ReverseCalculateIndex(); //Advance to the first "Group" node (skip the GroupHEaders) while( !( nodeHelper.CurrentNode is GroupGeneratorNode ) ) { if( !nodeHelper.MoveToNext() ) throw new DataGridInternalException(); } //then move up to the removal start point. if( !nodeHelper.MoveToNextBy( e.OldStartingIndex ) ) { throw new DataGridInternalException(); } GroupGeneratorNode startNode = nodeHelper.CurrentNode as GroupGeneratorNode; removeIndex = -1; //Only fetch the index if the group itself is not "collapsed" or under a collapsed group already if( ( startNode.IsExpanded == startNode.IsComputedExpanded ) && ( startNode.ItemCount > 0 ) ) { removeIndex = nodeHelper.Index; retval = this.GeneratorPositionFromIndex( removeIndex ); } else { retval = new GeneratorPosition( -1, 1 ); } //retrieve the generator position for the first item to remove. this.ProcessGroupRemoval( startNode, e.OldItems.Count, true, out countRemoved ); //Clean the chain "isolated" previously this.NodeFactory.CleanGeneratorNodeTree( startNode ); if( removeIndex != -1 ) { //remove the appropriate genCountRemoved = this.RemoveGeneratedItems( removeIndex, removeIndex + countRemoved - 1, removedContainers ); } return retval; }
private void OnGeneratorNodeExpansionStateChanged( object sender, ExpansionStateChangedEventArgs e ) { //throw an error is the Generator is actually busy generating! if( this.Status == GeneratorStatus.GeneratingContainers ) { throw new InvalidOperationException( "Cannot perform this operation while the generator is busy generating items" ); } GeneratorNode node = sender as GeneratorNode; Debug.Assert( node != null, "node != null" ); #if LOG Log.Assert( this, node != null, "node != null" ); #endif if( node == null ) return; GroupGeneratorNode changedNode = node.Parent as GroupGeneratorNode; Debug.Assert( changedNode != null, "changedNode != null" ); //should never be null, as the "node" is supposed to be the child node of this one. #if LOG Log.Assert( this, changedNode != null, "changedNode != null" ); //should never be null, as the "node" is supposed to be the child node of this one. #endif //Determine if the changedNode is "below" a collapsed group (because if so, I don't need any sort of notification or removal ). GroupGeneratorNode parentGroupNode = changedNode.Parent as GroupGeneratorNode; if( ( parentGroupNode != null ) && ( !parentGroupNode.IsComputedExpanded ) ) return; GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 ); nodeHelper.ReverseCalculateIndex(); //if the node was "Collapsed" if( !e.NewExpansionState ) { int removeCount = e.Count; int startIndex = nodeHelper.Index + e.IndexOffset; GeneratorPosition removeGenPos = this.GeneratorPositionFromIndex( startIndex ); List<DependencyObject> removedContainers = new List<DependencyObject>(); //remove the Generated items between the appropriate indexes int removeUICount = this.RemoveGeneratedItems( startIndex, startIndex + removeCount - 1, removedContainers ); if( removeCount > 0 ) { //send the event so the panel can remove the group elements this.SendRemoveEvent( removeGenPos, startIndex, removeCount, removeUICount, removedContainers ); } } //if the node was "Expanded" else { int addCount = e.Count; int startIndex = nodeHelper.Index + e.IndexOffset; GeneratorPosition addGenPos = this.GeneratorPositionFromIndex( startIndex ); if( addCount > 0 ) { //send the event so the panel can add the group elements this.SendAddEvent( addGenPos, startIndex, addCount ); } } }
private void OnGroupsChanged( object sender, NotifyCollectionChangedEventArgs e ) { // Avoid re-entrance when processing a global reset if( m_isProcessingGlobalResetOrRemovingAllGeneratedItemsDisposableCount > 0 ) return; if( this.Status == GeneratorStatus.GeneratingContainers ) { throw new InvalidOperationException( "Cannot perform this operation while the generator is busy generating items" ); } //this fonction is only used to process the content of the DataGridControl.Items.Groups collection... // for the CollectionChanged event of branch groups ( IsBottomLevel = false ), refer to the // OnBranchGroupsChanged fonction switch( e.Action ) { case NotifyCollectionChangedAction.Add: int addCount = e.NewItems.Count; GeneratorPosition genPos = new GeneratorPosition( -1, 1 ); //this would map to the first item in the list if not generated. int addIndex = -1; //if the first item is empty, do not do anything, the structure will be generated when the generator is started! if( m_firstItem != null ) { //The only moment where the m_firstItem is null is typically when a reset occured... //other moments is when there are 0 items (in which case, the. GeneratorNode addNode = this.HandleSameLevelGroupAddition( m_firstItem, out addCount, e ); this.IncrementCurrentGenerationCount(); GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( addNode, 0, 0 );//index not important, will reserve find it. nodeHelper.ReverseCalculateIndex(); addIndex = nodeHelper.Index; genPos = this.GeneratorPositionFromIndex( addIndex ); } this.SendAddEvent( genPos, addIndex, addCount ); break; case NotifyCollectionChangedAction.Move: if( m_firstItem != null ) { if( !( m_firstItem is GroupGeneratorNode ) ) { throw new DataGridInternalException(); } Debug.Assert( e.OldStartingIndex != e.NewStartingIndex, "An attempt was made to move a group to the same location." ); #if LOG Log.Assert( this, e.OldStartingIndex != e.NewStartingIndex, "An attempt was made to move a group to the same location." ); #endif this.HandleSameLevelGroupMove( m_firstItem, e ); } break; case NotifyCollectionChangedAction.Remove: if( m_firstItem != null ) { if( !( m_firstItem is GroupGeneratorNode ) ) { throw new DataGridInternalException(); } int remCount; int generatedRemCount; int removeIndex; List<DependencyObject> removedContainers = new List<DependencyObject>(); GeneratorPosition remPos = this.HandleSameLevelGroupRemove( m_firstItem, out remCount, out generatedRemCount, out removeIndex, e, removedContainers ); //there is no need to check if the parent node is expanded or not... since the first level of group cannot be collapsed. this.IncrementCurrentGenerationCount(); this.SendRemoveEvent( remPos, removeIndex, remCount, generatedRemCount, removedContainers ); } break; case NotifyCollectionChangedAction.Replace: throw new NotSupportedException( "Replace not supported at the moment on groups!!!" ); //break; case NotifyCollectionChangedAction.Reset: //I'm forced to handle it specifically since the Panel will AUTOMATICALLY clear its children this.HandleGlobalItemsReset(); break; default: throw new DataGridInternalException(); } }
public List<StickyContainerGenerated> GenerateStickyFooters( DependencyObject container, bool areFootersSticky, bool areGroupFootersSticky ) { List<StickyContainerGenerated> generatedStickyContainers = new List<StickyContainerGenerated>(); GeneratorNode containerNode; int containerRealizedIndex; object containerDataItem; if( this.FindGeneratorListMappingInformationForContainer( container, out containerNode, out containerRealizedIndex, out containerDataItem ) ) { GeneratorNodeHelper nodeHelper = null; DetailGeneratorNode detailNode = containerNode as DetailGeneratorNode; if( detailNode != null ) { // Get the parent item of the detail node to be able // to readjust the containerRealizedIndex to the one // of the master item container since the detail node // will be processed by the DetailGenerator. containerDataItem = detailNode.DetailContext.ParentItem; // OPTIMIZATION: We will look in the m_genPos* first to avoid using // FindItem for performance reason. int index = m_genPosToItem.IndexOf( containerDataItem ); if( index > -1 ) { int sourceDataIndex = ( int )m_genPosToContainer[ index ].GetValue( DataGridVirtualizingPanel.ItemIndexProperty ); containerNode = m_genPosToNode[ index ]; containerRealizedIndex = m_genPosToIndex[ index ]; CollectionGeneratorNode collectionNode = containerNode as CollectionGeneratorNode; if( collectionNode != null ) { nodeHelper = new GeneratorNodeHelper( containerNode, containerRealizedIndex - collectionNode.IndexOf( containerDataItem ), sourceDataIndex ); } } if( nodeHelper == null ) { // We want to find the ItemsGeneratorNode for the DetailNode. nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 ); containerRealizedIndex = nodeHelper.FindItem( containerDataItem ); containerNode = nodeHelper.CurrentNode; } if( containerRealizedIndex == -1 ) throw new DataGridInternalException(); generatedStickyContainers.AddRange( this.GenerateStickyFootersForDetail( container, detailNode, areFootersSticky, areGroupFootersSticky ) ); } else { CollectionGeneratorNode collectionNode = containerNode as CollectionGeneratorNode; if( collectionNode != null ) { // We don't need to have an up to date sourceDataIndex so we pass 0 nodeHelper = new GeneratorNodeHelper( containerNode, containerRealizedIndex - collectionNode.IndexOf( containerDataItem ), 0 ); } if( nodeHelper == null ) { nodeHelper = new GeneratorNodeHelper( containerNode, 0, 0 ); nodeHelper.ReverseCalculateIndex(); } } bool isHeaderNode = ( ( nodeHelper.CurrentNode is HeadersFootersGeneratorNode ) && ( nodeHelper.CurrentNode.Previous == null ) ); // We want to find the HeaderFooterGeneratorNode for the container // node. This is to find the footers for the container. nodeHelper.MoveToEnd(); HeadersFootersGeneratorNode footersNode = nodeHelper.CurrentNode as HeadersFootersGeneratorNode; if( !isHeaderNode ) { // There is no footers to generate if the item count of the node is 0. if( footersNode.ItemCount > 0 ) { if( ( ( areFootersSticky ) && ( footersNode.Parent == null ) ) || ( ( areGroupFootersSticky ) && ( footersNode.Parent is GroupGeneratorNode ) ) ) { generatedStickyContainers.AddRange( this.GenerateStickyFootersForNode( footersNode, nodeHelper.Index, containerRealizedIndex, ( footersNode == containerNode ) ) ); } } } // We must also find the bottom most footers for our level of detail and, if they need to be sticky, // we will generate the containers and add them the to list. HeadersFootersGeneratorNode bottomFootersNode = this.GetDetailFootersNode( nodeHelper ); if( ( areFootersSticky ) && ( bottomFootersNode != null ) && ( bottomFootersNode != footersNode ) && ( bottomFootersNode.ItemCount > 0 ) ) { generatedStickyContainers.AddRange( this.GenerateStickyFootersForNode( bottomFootersNode, nodeHelper.Index ) ); } } return generatedStickyContainers; }
public int GetFirstHoldingContainerIndexForStickyFooter( DependencyObject stickyFooter ) { int firstContainerIndex = 0; int containerRealizedIndex; GeneratorNode containerNode; object containerDataItem; if( this.FindGeneratorListMappingInformationForContainer( stickyFooter, out containerNode, out containerRealizedIndex, out containerDataItem ) ) { DetailGeneratorNode detailNode = containerNode as DetailGeneratorNode; if( detailNode != null ) { int detailIndex = this.FindGlobalIndexForDetailNode( detailNode ); firstContainerIndex = detailNode.DetailGenerator.GetFirstHoldingContainerIndexForStickyFooter( stickyFooter ) + detailIndex; } else { GeneratorNodeHelper nodeHelper = null; CollectionGeneratorNode collectionNode = containerNode as CollectionGeneratorNode; if( collectionNode != null ) { // We don't need to have an up to date sourceDataIndex so we pass 0 nodeHelper = new GeneratorNodeHelper( containerNode, containerRealizedIndex - collectionNode.IndexOf( containerDataItem ), 0 ); } if( nodeHelper == null ) { nodeHelper = new GeneratorNodeHelper( containerNode, 0, 0 ); nodeHelper.ReverseCalculateIndex(); } nodeHelper.MoveToFirst(); nodeHelper.MoveToNext(); // We exclude headers from this calculation. firstContainerIndex = nodeHelper.Index; } } return firstContainerIndex; }
private int GetLastHoldingContainerIndexForStickyHeaderRecurse( DependencyObject stickyHeader, int parentCount ) { int lastContainerIndex = 0; int containerRealizedIndex; GeneratorNode containerNode; object containerDataItem; if( this.FindGeneratorListMappingInformationForContainer( stickyHeader, out containerNode, out containerRealizedIndex, out containerDataItem ) ) { DetailGeneratorNode detailNode = containerNode as DetailGeneratorNode; if( detailNode != null ) { lastContainerIndex = detailNode.DetailGenerator.GetLastHoldingContainerIndexForStickyHeaderRecurse( stickyHeader, detailNode.ItemCount ) + this.FindGlobalIndexForDetailNode( detailNode ); } else { ItemsGeneratorNode itemsNode = containerNode as ItemsGeneratorNode; if( itemsNode != null ) { // This means that the sticky container is a MasterRow for a detail. if( this.AreDetailsExpanded( containerDataItem ) ) { List<DetailGeneratorNode> detailNodesForDataItem = m_masterToDetails[ containerDataItem ]; foreach( DetailGeneratorNode detailNodeForDataItem in detailNodesForDataItem ) { lastContainerIndex += detailNodeForDataItem.ItemCount + this.FindGlobalIndexForDetailNode( detailNodeForDataItem ) - 1; } } } else { GeneratorNodeHelper nodeHelper = null; // This means that the sticky container is a Header. CollectionGeneratorNode collectionNode = containerNode as CollectionGeneratorNode; if( collectionNode != null ) { // We don't need to have an up to date sourceDataIndex so we pass 0 nodeHelper = new GeneratorNodeHelper( containerNode, containerRealizedIndex - collectionNode.IndexOf( containerDataItem ), 0 ); } if( nodeHelper == null ) { nodeHelper = new GeneratorNodeHelper( containerNode, 0, 0 ); nodeHelper.ReverseCalculateIndex(); } lastContainerIndex = nodeHelper.Index; if( containerNode.Parent != null ) { // This means that it is a GroupHeader lastContainerIndex += containerNode.Parent.ItemCount - 1; } else { lastContainerIndex += Math.Max( 0, parentCount - 1 ); } } } } return lastContainerIndex; }