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); } } }