private void CollectionView_CurrentChanged(object sender, EventArgs e)
        {
            if (this._expectingCurrentChanged)
            {
                // Committing Edit could cause our item to move to a group that no longer exists.  In
                // this case, we need to update the item.
                CollectionViewGroup collectionViewGroup = _itemToSelectOnCurrentChanged as CollectionViewGroup;
                if (collectionViewGroup != null)
                {
                    DataGridRowGroupInfo groupInfo = this._owner.RowGroupInfoFromCollectionViewGroup(collectionViewGroup);
                    if (groupInfo == null)
                    {
                        // Move to the next slot if the target slot isn't visible
                        if (!this._owner.IsSlotVisible(_backupSlotForCurrentChanged))
                        {
                            _backupSlotForCurrentChanged = this._owner.GetNextVisibleSlot(_backupSlotForCurrentChanged);
                        }
                        // Move to the next best slot if we've moved past all the slots.  This could happen if multiple
                        // groups were removed.
                        if (_backupSlotForCurrentChanged >= this._owner.SlotCount)
                        {
                            _backupSlotForCurrentChanged = this._owner.GetPreviousVisibleSlot(this._owner.SlotCount);
                        }
                        // Update the itemToSelect
                        int newCurrentPosition = -1;
                        _itemToSelectOnCurrentChanged = this._owner.ItemFromSlot(_backupSlotForCurrentChanged, ref newCurrentPosition);
                    }
                }

                this._owner.ProcessSelectionAndCurrency(
                    this._columnForCurrentChanged,
                    this._itemToSelectOnCurrentChanged,
                    this._backupSlotForCurrentChanged,
                    this._selectionActionForCurrentChanged,
                    this._scrollForCurrentChanged);
            }
            else if (this.CollectionView != null)
            {
                this._owner.UpdateStateOnCurrentChanged(this.CollectionView.CurrentItem, this.CollectionView.CurrentPosition);
            }
        }
        private double UpdateRowGroupVisibility(DataGridRowGroupInfo targetRowGroupInfo, Visibility newVisibility, bool isDisplayed)
        {
            double heightChange = 0;
            int slotsExpanded = 0;
            int startSlot = targetRowGroupInfo.Slot + 1;
            int endSlot;

            if (newVisibility == Visibility.Visible)
            {
                // Expand
                foreach (int slot in this.RowGroupHeadersTable.GetIndexes(targetRowGroupInfo.Slot + 1))
                {
                    if (slot >= startSlot)
                    {
                        DataGridRowGroupInfo rowGroupInfo = this.RowGroupHeadersTable.GetValueAt(slot);
                        if (rowGroupInfo.Level <= targetRowGroupInfo.Level)
                        {
                            break;
                        }
                        if (rowGroupInfo.Visibility == Visibility.Collapsed)
                        {
                            // Skip over the items in collapsed subgroups
                            endSlot = rowGroupInfo.Slot;
                            ExpandSlots(startSlot, endSlot, isDisplayed, ref slotsExpanded, ref heightChange);
                            startSlot = rowGroupInfo.LastSubItemSlot + 1;
                        }
                    }
                }
                if (targetRowGroupInfo.LastSubItemSlot >= startSlot)
                {
                    ExpandSlots(startSlot, targetRowGroupInfo.LastSubItemSlot, isDisplayed, ref slotsExpanded, ref heightChange);
                }
                if (isDisplayed)
                {
                    UpdateDisplayedRows(this.DisplayData.FirstScrollingSlot, this.CellsHeight);
                }
            }
            else
            {
                // Collapse
                endSlot = this.SlotCount - 1;
                foreach (int slot in this.RowGroupHeadersTable.GetIndexes(targetRowGroupInfo.Slot + 1))
                {
                    DataGridRowGroupInfo rowGroupInfo = this.RowGroupHeadersTable.GetValueAt(slot);
                    if (rowGroupInfo.Level <= targetRowGroupInfo.Level)
                    {
                        endSlot = slot - 1;
                        break;
                    }
                }

                int oldLastDisplayedSlot = this.DisplayData.LastScrollingSlot;
                int endDisplayedSlot = Math.Min(endSlot, this.DisplayData.LastScrollingSlot);
                if (isDisplayed)
                {
                    // We need to remove all the displayed slots that aren't already collapsed
                    int elementsToRemove = endDisplayedSlot - startSlot + 1 - _collapsedSlotsTable.GetIndexCount(startSlot, endDisplayedSlot);

                    if (_focusedRow != null && _focusedRow.Slot >= startSlot && _focusedRow.Slot <= endSlot)
                    {
                        Debug.Assert(this.EditingRow == null);
                        // Don't call ResetFocusedRow here because we're already cleaning it up below, and we don't want to FullyRecycle yet
                        _focusedRow = null;
                    }

                    for (int i = 0; i < elementsToRemove; i++)
                    {
                        RemoveDisplayedElement(startSlot, false /*wasDeleted*/, false /*updateSlotInformation*/);
                    }
                }

                double heightChangeBelowLastDisplayedSlot = 0;
                if (this.DisplayData.FirstScrollingSlot >= startSlot && this.DisplayData.FirstScrollingSlot <= endSlot)
                {
                    // Our first visible slot was collapsed, find the replacement
                    int collapsedSlotsAbove = this.DisplayData.FirstScrollingSlot - startSlot - _collapsedSlotsTable.GetIndexCount(startSlot, this.DisplayData.FirstScrollingSlot);
                    Debug.Assert(collapsedSlotsAbove > 0);
                    int newFirstScrollingSlot = GetNextVisibleSlot(this.DisplayData.FirstScrollingSlot);
                    while (collapsedSlotsAbove > 1 && newFirstScrollingSlot < this.SlotCount)
                    {
                        collapsedSlotsAbove--;
                        newFirstScrollingSlot = GetNextVisibleSlot(newFirstScrollingSlot);
                    }
                    heightChange += CollapseSlotsInTable(startSlot, endSlot, ref slotsExpanded, oldLastDisplayedSlot, ref heightChangeBelowLastDisplayedSlot);
                    if (isDisplayed)
                    {
                        if (newFirstScrollingSlot >= this.SlotCount)
                        {
                            // No visible slots below, look up
                            UpdateDisplayedRowsFromBottom(targetRowGroupInfo.Slot);
                        }
                        else
                        {
                            UpdateDisplayedRows(newFirstScrollingSlot, this.CellsHeight);
                        }
                    }
                }
                else
                {
                    heightChange += CollapseSlotsInTable(startSlot, endSlot, ref slotsExpanded, oldLastDisplayedSlot, ref heightChangeBelowLastDisplayedSlot);
                }

                if (this.DisplayData.LastScrollingSlot >= startSlot && this.DisplayData.LastScrollingSlot <= endSlot)
                {
                    // Collapsed the last scrolling row, we need to update it
                    this.DisplayData.LastScrollingSlot = GetPreviousVisibleSlot(this.DisplayData.LastScrollingSlot);
                }

                // Collapsing could cause the vertical offset to move up if we collapsed a lot of slots
                // near the bottom of the DataGrid.  To do this, we compare the height we collapsed to
                // the distance to the last visible row and adjust the scrollbar if we collapsed more
                if (isDisplayed && this._verticalOffset > 0)
                {
                    int lastVisibleSlot = GetPreviousVisibleSlot(this.SlotCount);
                    int slot = GetNextVisibleSlot(oldLastDisplayedSlot);
                    // AvailableSlotElementRoom ends up being the amount of the last slot that is partially scrolled off
                    // as a negative value, heightChangeBelowLastDisplayed slot is also a negative value since we're collapsing
                    double heightToLastVisibleSlot = this.AvailableSlotElementRoom + heightChangeBelowLastDisplayedSlot;
                    while ((heightToLastVisibleSlot > heightChange) && (slot < lastVisibleSlot))
                    {
                        heightToLastVisibleSlot -= GetSlotElementHeight(slot);
                        slot = GetNextVisibleSlot(slot);
                    }
                    if (heightToLastVisibleSlot > heightChange)
                    {
                        double newVerticalOffset = _verticalOffset + heightChange - heightToLastVisibleSlot;
                        if (newVerticalOffset > 0)
                        {
                            SetVerticalOffset(newVerticalOffset);
                        }
                        else
                        {
                            // Collapsing causes the vertical offset to go to 0 so we should go back to the first row.
                            ResetDisplayedRows();
                            this.NegVerticalOffset = 0;
                            SetVerticalOffset(0);
                            int firstDisplayedRow = GetNextVisibleSlot(-1);
                            UpdateDisplayedRows(firstDisplayedRow, this.CellsHeight);
                        }
                    }
                }
            }
            targetRowGroupInfo.Visibility = newVisibility;

            // Update VisibleSlotCount
            this.VisibleSlotCount += slotsExpanded;

            return heightChange;
        }
        internal void InsertElementAt(int slot, int rowIndex, object item, DataGridRowGroupInfo groupInfo, bool isCollapsed)
        {
            Debug.Assert(slot >= 0 && slot <= this.SlotCount);

            if (isCollapsed)
            {
                InsertElement(slot, null /*element*/, true /*updateVerticalScrollBarOnly*/, true /*isCollapsed*/);
            }
            else if (SlotIsDisplayed(slot))
            {
                // Row at that index needs to be displayed
                if (rowIndex != -1)
                {
                    InsertElement(slot, GenerateRow(rowIndex, slot, item), false /*updateVerticalScrollBarOnly*/, false /*isCollapsed*/);
                }
                else
                {
                    InsertElement(slot, GenerateRowGroupHeader(slot, groupInfo), false /*updateVerticalScrollBarOnly*/, false /*isCollapsed*/);
                }
            }
            else
            {
                InsertElement(slot, null, this._vScrollBar == null || this._vScrollBar.Visibility == Visibility.Visible /*updateVerticalScrollBarOnly*/, false /*isCollapsed*/);
            }
        }
        private DataGridRowGroupHeader GenerateRowGroupHeader(int slot, DataGridRowGroupInfo rowGroupInfo)
        {
            Debug.Assert(slot > -1);
            Debug.Assert(rowGroupInfo != null);

            DataGridRowGroupHeader groupHeader = this.DisplayData.GetUsedGroupHeader() ?? new DataGridRowGroupHeader();
            groupHeader.OwningGrid = this;
            groupHeader.RowGroupInfo = rowGroupInfo;
            groupHeader.DataContext = rowGroupInfo.CollectionViewGroup;
            groupHeader.Level = rowGroupInfo.Level;

            // Set the RowGroupHeader's PropertyName. Unfortunately, CollectionViewGroup doesn't have this
            // so we have to set it manually
            Debug.Assert(this.DataConnection.CollectionView != null && groupHeader.Level < this.DataConnection.CollectionView.GroupDescriptions.Count);
            PropertyGroupDescription propertyGroupDescription = this.DataConnection.CollectionView.GroupDescriptions[groupHeader.Level] as PropertyGroupDescription;
            if (propertyGroupDescription != null)
            {
                if (this.DataConnection.DataType == null)
                {
                    groupHeader.PropertyName = propertyGroupDescription.PropertyName;
                }
                else
                {
                    groupHeader.PropertyName = this.DataConnection.DataType.GetDisplayName(propertyGroupDescription.PropertyName) ?? propertyGroupDescription.PropertyName;
                }
            }

            INotifyPropertyChanged inpc = rowGroupInfo.CollectionViewGroup as INotifyPropertyChanged;
            if (inpc != null)
            {
                inpc.PropertyChanged -= new PropertyChangedEventHandler(CollectionViewGroup_PropertyChanged);
                inpc.PropertyChanged += new PropertyChangedEventHandler(CollectionViewGroup_PropertyChanged);
            }
            groupHeader.UpdateTitleElements();

            OnLoadingRowGroup(new DataGridRowGroupHeaderEventArgs(groupHeader));

            return groupHeader;
        }
 private void EnsureRowGroupVisibility(DataGridRowGroupInfo rowGroupInfo, Visibility visibility, bool setCurrent)
 {
     if (rowGroupInfo == null)
     {
         return;
     }
     if (rowGroupInfo.Visibility != visibility)
     {
         if (this.IsSlotVisible(rowGroupInfo.Slot))
         {
             DataGridRowGroupHeader rowGroupHeader = this.DisplayData.GetDisplayedElement(rowGroupInfo.Slot) as DataGridRowGroupHeader;
             Debug.Assert(rowGroupHeader != null);
             rowGroupHeader.ToggleExpandCollapse(visibility, setCurrent);
         }
         else
         {
             if (_collapsedSlotsTable.Contains(rowGroupInfo.Slot))
             {
                 // Somewhere up the parent chain, there's a collapsed header so all the slots remain the same and
                 // we just need to mark this header with the new visibility
                 rowGroupInfo.Visibility = visibility;
             }
             else
             {
                 if (rowGroupInfo.Slot < this.DisplayData.FirstScrollingSlot)
                 {
                     double heightChange = UpdateRowGroupVisibility(rowGroupInfo, visibility, false /*isDisplayed*/);
                     // Use epsilon instead of 0 here so that in the off chance that our estimates put the vertical offset negative
                     // the user can still scroll to the top since the offset is non-zero
                     SetVerticalOffset(Math.Max(DoubleUtil.DBL_EPSILON, _verticalOffset + heightChange));
                 }
                 else
                 {
                     UpdateRowGroupVisibility(rowGroupInfo, visibility, false /*isDisplayed*/);
                 }
                 UpdateVerticalScrollBar();
             }
         }
     }
 }
 private void EnsureAnscestorsExpanderButtonChecked(DataGridRowGroupInfo parentGroupInfo)
 {
     if (IsSlotVisible(parentGroupInfo.Slot))
     {
         DataGridRowGroupHeader ancestorGroupHeader = this.DisplayData.GetDisplayedElement(parentGroupInfo.Slot) as DataGridRowGroupHeader;
         while (ancestorGroupHeader != null)
         {
             ancestorGroupHeader.EnsureExpanderButtonIsChecked();
             if (ancestorGroupHeader.Level > 0)
             {
                 int slot = this.RowGroupHeadersTable.GetPreviousIndex(ancestorGroupHeader.RowGroupInfo.Slot);
                 if (IsSlotVisible(slot))
                 {
                     ancestorGroupHeader = this.DisplayData.GetDisplayedElement(slot) as DataGridRowGroupHeader;
                     continue;
                 }
             }
             break;
         }
     }
 }
        private void CollectionViewGroup_CollectionChanged_Add(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null && e.NewItems.Count > 0)
            {
                // We need to figure out the CollectionViewGroup that the sender belongs to.  We could cache
                // it by tagging the collections ahead of time, but I think the extra storage might not be worth
                // it since this lookup should be performant enough
                int insertSlot = -1;
                DataGridRowGroupInfo parentGroupInfo = GetParentGroupInfo(sender);
                CollectionViewGroup group = e.NewItems[0] as CollectionViewGroup;

                if (parentGroupInfo != null)
                {
                    if (group != null || parentGroupInfo.Level == -1)
                    {
                        insertSlot = parentGroupInfo.Slot + 1;
                        // For groups, we need to skip over subgroups to find the correct slot
                        DataGridRowGroupInfo groupInfo;
                        for (int i = 0; i < e.NewStartingIndex; i++)
                        {
                            do
                            {
                                insertSlot = this.RowGroupHeadersTable.GetNextIndex(insertSlot);
                                groupInfo = this.RowGroupHeadersTable.GetValueAt(insertSlot);
                            }
                            while (groupInfo != null && groupInfo.Level > parentGroupInfo.Level + 1);
                            if (groupInfo == null)
                            {
                                // We couldn't find the subchild so this should go at the end
                                insertSlot = this.SlotCount;
                            }
                        }

                    }
                    else
                    {
                        // For items the slot is a simple calculation
                        insertSlot = parentGroupInfo.Slot + e.NewStartingIndex + 1;
                    }
                }

                // This could not be found when new GroupDescriptions are added to the PagedCollectionView
                if (insertSlot != -1)
                {
                    bool isCollapsed = (parentGroupInfo != null) && ((parentGroupInfo.Visibility == Visibility.Collapsed) || _collapsedSlotsTable.Contains(parentGroupInfo.Slot));
                    if (group != null)
                    {
                        if (group.Items != null)
                        {
                            ((INotifyCollectionChanged)group.Items).CollectionChanged += CollectionViewGroup_CollectionChanged;
                        }
                        DataGridRowGroupInfo newGroupInfo = new DataGridRowGroupInfo(group, Visibility.Visible, parentGroupInfo.Level + 1, insertSlot, insertSlot);
                        InsertElementAt(insertSlot, -1 /*rowIndex*/, null /*item*/, newGroupInfo, isCollapsed);
                        this.RowGroupHeadersTable.AddValue(insertSlot, newGroupInfo);
                    }
                    else
                    {
                        // Assume we're adding a new row
                        int rowIndex = this.DataConnection.IndexOf(e.NewItems[0]);
                        Debug.Assert(rowIndex != -1);
                        if (this.SlotCount == 0 && this.DataConnection.ShouldAutoGenerateColumns)
                        {
                            AutoGenerateColumnsPrivate();
                        }
                        InsertElementAt(insertSlot, rowIndex, e.NewItems[0]/*item*/, null/*rowGroupInfo*/, isCollapsed);
                    }

                    CorrectLastSubItemSlotsAfterInsertion(parentGroupInfo);
                    if (parentGroupInfo.LastSubItemSlot - parentGroupInfo.Slot == 1)
                    {
                        // We just added the first item to a RowGroup so the header should transition from Empty to either Expanded or Collapsed
                        EnsureAnscestorsExpanderButtonChecked(parentGroupInfo);
                    }
                }
            }
        }
        // This method is necessary for incrementing the LastSubItemSlot property of the group ancestors
        // because CorrectSlotsAfterInsertion only increments those that come after the specified group
        private void CorrectLastSubItemSlotsAfterInsertion(DataGridRowGroupInfo subGroupInfo)
        {
            int subGroupSlot;
            int subGroupLevel;
            while (subGroupInfo != null)
            {
                subGroupLevel = subGroupInfo.Level;
                subGroupInfo.LastSubItemSlot++;

                while (subGroupInfo != null && subGroupInfo.Level >= subGroupLevel)
                {
                    subGroupSlot = this.RowGroupHeadersTable.GetPreviousIndex(subGroupInfo.Slot);
                    subGroupInfo = this.RowGroupHeadersTable.GetValueAt(subGroupSlot);
                }
            }
        }