//--------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; using EGC = ExtendedGrid.Microsoft.Windows.Controls; using MS.Internal; namespace ExtendedGrid.Microsoft.Windows.Controls { /// /// Panel that lays out both cells and column headers. This stacks cells in the horizontal direction and communicates with the /// relevant DataGridColumn to ensure all rows give cells in a given column the same size. /// It is hardcoded against DataGridCell and DataGridColumnHeader. /// public class DataGridCellsPanel : VirtualizingPanel { #region Constructors static DataGridCellsPanel() { KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(EGC.DataGridCellsPanel), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local)); } public DataGridCellsPanel() { IsVirtualizing = false; InRecyclingMode = false; } #endregion #region Measure /// /// Measure /// /// The logic is to see determinition of realized blocks is needed and do it. /// If not, iterate over the realized block list and for each block generate the /// children and measure them. /// /// Size constraint /// protected override Size MeasureOverride(Size constraint) { Size measureSize = new Size(); DetermineVirtualizationState(); // Acessing this property will initilize things appropriately, // both for virtualization and non-virtualization cases EnsureRealizedChildren(); IList children = RealizedChildren; if (RebuildRealizedColumnsBlockList) { measureSize = DetermineRealizedColumnsBlockList(constraint); } else { // This is the case of remaining cells panels which directly use the list // in parent rows presenter for virtualization, rather than doing the // computation themselves. measureSize = GenerateAndMeasureChildrenForRealizedColumns(constraint); } // Disconnect any left over recycled children if (IsVirtualizing && InRecyclingMode) { DisconnectRecycledContainers(); } return measureSize; } /// /// Method which measures a given child based on its column width /// /// For auto kind columns it may actually end up measuring twice /// once with positive infinity to determine its actual desired width /// and then again with the available space constraints /// private static void MeasureChild(UIElement child, Size constraint) { EGC.IProvideDataGridColumn cell = child as EGC.IProvideDataGridColumn; bool isColumnHeader = (child is EGC.DataGridColumnHeader); Size childMeasureConstraint = new Size(double.PositiveInfinity, constraint.Height); double desiredWidth = 0.0; bool remeasure = false; // Allow the column to affect the constraint. if (cell != null) { // For auto kind columns measure with infinity to find the actual desired width of the cell. EGC.DataGridColumn column = cell.Column; EGC.DataGridLength width = column.Width; if (width.IsAuto || (width.IsSizeToHeader && isColumnHeader) || (width.IsSizeToCells && !isColumnHeader)) { child.Measure(childMeasureConstraint); desiredWidth = child.DesiredSize.Width; remeasure = true; } childMeasureConstraint.Width = column.GetConstraintWidth(isColumnHeader); } if (DoubleUtil.AreClose(desiredWidth, 0.0)) { child.Measure(childMeasureConstraint); } Size childDesiredSize = child.DesiredSize; if (cell != null) { EGC.DataGridColumn column = cell.Column; // Allow the column to process the desired size column.UpdateDesiredWidthForAutoColumn( isColumnHeader, DoubleUtil.AreClose(desiredWidth, 0.0) ? childDesiredSize.Width : desiredWidth); // For auto kind columns measure again with display value if // the desired width is greater than display value. EGC.DataGridLength width = column.Width; if (remeasure && !DoubleUtil.IsNaN(width.DisplayValue) && DoubleUtil.GreaterThan(desiredWidth, width.DisplayValue)) { childMeasureConstraint.Width = width.DisplayValue; child.Measure(childMeasureConstraint); } } } /// /// Generates children and measures them based on RealizedColumnList of /// ancestor rows presenter. /// private Size GenerateAndMeasureChildrenForRealizedColumns(Size constraint) { double measureWidth = 0.0; double measureHeight = 0.0; EGC.DataGrid parentDataGrid = ParentDataGrid; double averageColumnWidth = parentDataGrid.InternalColumns.AverageColumnWidth; IItemContainerGenerator generator = ItemContainerGenerator; List blockList = RealizedColumnsBlockList; Debug.Assert(blockList != null, "RealizedColumnsBlockList shouldn't be null at this point."); // Virtualize the children which are not necessary VirtualizeChildren(blockList, generator); // Realize the required children if (blockList.Count > 0) { for (int i = 0, count = blockList.Count; i < count; i++) { RealizedColumnsBlock rcb = blockList[i]; Size blockMeasureSize = GenerateChildren( generator, rcb.StartIndex, rcb.EndIndex, constraint); measureWidth += blockMeasureSize.Width; measureHeight = Math.Max(measureHeight, blockMeasureSize.Height); if (i != count - 1) { RealizedColumnsBlock nextRcb = blockList[i + 1]; measureWidth += GetColumnEstimatedMeasureWidthSum(rcb.EndIndex + 1, nextRcb.StartIndex - 1, averageColumnWidth); } } measureWidth += GetColumnEstimatedMeasureWidthSum(0, blockList[0].StartIndex - 1, averageColumnWidth); measureWidth += GetColumnEstimatedMeasureWidthSum(blockList[blockList.Count - 1].EndIndex + 1, parentDataGrid.Columns.Count - 1, averageColumnWidth); } else { measureWidth = 0.0; } return new Size(measureWidth, measureHeight); } /// /// Method which determines the realized columns list and /// stores it in ancestor rowpresenter which is to be used /// by other cellpanels to virtualize without recomputation. /// Simultaneously measures the children of this panel. /// /// If the datagrid has star columns, then all cells for all /// realized rows are generated. /// /// For remaining case the logic is to iterate over columns of /// datagrid in DisplayIndex order, as if one is actually arranging /// them, to determine which of them actually fall in viewport. /// private Size DetermineRealizedColumnsBlockList(Size constraint) { List realizedColumnIndices = new List(); List realizedColumnDisplayIndices = new List(); Size measureSize = new Size(); EGC.DataGrid parentDataGrid = ParentDataGrid; double horizontalOffset = parentDataGrid.HorizontalScrollOffset; double cellsPanelOffset = parentDataGrid.CellsPanelHorizontalOffset; // indicates cellspanel's offset in a row double nextFrozenCellStart = horizontalOffset; // indicates the start position for next frozen cell double nextNonFrozenCellStart = -cellsPanelOffset; // indicates the start position for next non-frozen cell double viewportStartX = horizontalOffset - cellsPanelOffset; // indicates the start of viewport with respect to coordinate system of cell panel int firstVisibleNonFrozenDisplayIndex = -1; int lastVisibleNonFrozenDisplayIndex = -1; double totalAvailableSpace = GetViewportWidth() - cellsPanelOffset; double allocatedSpace = 0.0; if (DoubleUtil.LessThanOrClose(totalAvailableSpace, 0.0)) { return measureSize; } bool hasStarColumns = parentDataGrid.InternalColumns.HasVisibleStarColumns; double averageColumnWidth = parentDataGrid.InternalColumns.AverageColumnWidth; bool invalidAverage = DoubleUtil.AreClose(averageColumnWidth, 0.0); bool notVirtualizing = !IsVirtualizing; bool generateAll = invalidAverage || hasStarColumns || notVirtualizing; int frozenColumnCount = parentDataGrid.FrozenColumnCount; bool redeterminationNeeded = false; Size childSize; IItemContainerGenerator generator = ItemContainerGenerator; IDisposable generatorState = null; int childIndex = 0; try { for (int i = 0, count = parentDataGrid.Columns.Count; i < count; i++) { EGC.DataGridColumn column = parentDataGrid.ColumnFromDisplayIndex(i); if (!column.IsVisible) { continue; } // Dispose the generator state if the child generation is not in // sequence either because of gaps in childs to be generated or // due to mismatch in the order of column index and displayindex int columnIndex = parentDataGrid.ColumnIndexFromDisplayIndex(i); if (columnIndex != childIndex) { childIndex = columnIndex; if (generatorState != null) { generatorState.Dispose(); generatorState = null; } } // Generate the child if the all the children are to be generated, // initialize the child size. if (generateAll) { if (null == GenerateChild(generator, constraint, column, ref generatorState, ref childIndex, out childSize)) { break; } } else { childSize = new Size(GetColumnEstimatedMeasureWidth(column, averageColumnWidth), 0.0); } if (notVirtualizing || hasStarColumns || DoubleUtil.LessThan(allocatedSpace, totalAvailableSpace)) { // Frozen children are realized provided they are in viewport if (i < frozenColumnCount) { if (!generateAll && null == GenerateChild(generator, constraint, column, ref generatorState, ref childIndex, out childSize)) { break; } realizedColumnIndices.Add(columnIndex); realizedColumnDisplayIndices.Add(i); allocatedSpace += childSize.Width; nextFrozenCellStart += childSize.Width; } else { if (DoubleUtil.LessThanOrClose(nextNonFrozenCellStart, viewportStartX)) { // Non-Frozen children to the left of viewport are not realized, // unless we are dealing with star columns. if (DoubleUtil.LessThanOrClose(nextNonFrozenCellStart + childSize.Width, viewportStartX)) { if (generateAll) { if (notVirtualizing || hasStarColumns) { realizedColumnIndices.Add(columnIndex); realizedColumnDisplayIndices.Add(i); } else if (invalidAverage) { redeterminationNeeded = true; } } else if (generatorState != null) { generatorState.Dispose(); generatorState = null; } nextNonFrozenCellStart += childSize.Width; } else { // First visible non frozen child is realized if (!generateAll && null == GenerateChild(generator, constraint, column, ref generatorState, ref childIndex, out childSize)) { break; } double cellChoppedWidth = viewportStartX - nextNonFrozenCellStart; if (DoubleUtil.AreClose(cellChoppedWidth, 0.0)) { nextNonFrozenCellStart = nextFrozenCellStart + childSize.Width; allocatedSpace += childSize.Width; } else { double clipWidth = childSize.Width - cellChoppedWidth; nextNonFrozenCellStart = nextFrozenCellStart + clipWidth; allocatedSpace += clipWidth; } realizedColumnIndices.Add(columnIndex); realizedColumnDisplayIndices.Add(i); firstVisibleNonFrozenDisplayIndex = i; lastVisibleNonFrozenDisplayIndex = i; } } else { // All the remaining non-frozen children are realized provided they are in viewport if (!generateAll && null == GenerateChild(generator, constraint, column, ref generatorState, ref childIndex, out childSize)) { break; } if (firstVisibleNonFrozenDisplayIndex < 0) { firstVisibleNonFrozenDisplayIndex = i; } lastVisibleNonFrozenDisplayIndex = i; nextNonFrozenCellStart += childSize.Width; allocatedSpace += childSize.Width; realizedColumnIndices.Add(columnIndex); realizedColumnDisplayIndices.Add(i); } } } measureSize.Width += childSize.Width; measureSize.Height = Math.Max(measureSize.Height, childSize.Height); } } finally { if (generatorState != null) { generatorState.Dispose(); generatorState = null; } } // If we are virtualizing and datagrid doesnt have any star columns // then ensure the focus trail for navigational purposes. if (!hasStarColumns && !notVirtualizing) { bool isColumnHeader = ParentPresenter is EGC.DataGridColumnHeadersPresenter; if (isColumnHeader) { redeterminationNeeded = true; } else { EnsureFocusTrail(realizedColumnIndices, realizedColumnDisplayIndices, firstVisibleNonFrozenDisplayIndex, lastVisibleNonFrozenDisplayIndex, constraint); } } UpdateRealizedBlockLists(realizedColumnIndices, realizedColumnDisplayIndices, redeterminationNeeded); // Virtualize the children which are determined to be unused VirtualizeChildren(RealizedColumnsBlockList, generator); return measureSize; } private void UpdateRealizedBlockLists( List realizedColumnIndices, List realizedColumnDisplayIndices, bool redeterminationNeeded) { realizedColumnIndices.Sort(); // TODO: PERF: An option here is to apply some heuristics and add some indices // to optimize the generation by avoiding multiple generation sequences // with in a single generation sequence // Combine the realized indices into blocks, so that it is easy to use for later purposes RealizedColumnsBlockList = BuildRealizedColumnsBlockList(realizedColumnIndices); // TODO: PERF: Uncomment the statement below if needed once the heuristics // mentioned above are implemented // realizedColumnDisplayIndices.Sort(); // Combine the realized disply indices into blocks, so that it is easy to use for later purposes RealizedColumnsDisplayIndexBlockList = BuildRealizedColumnsBlockList(realizedColumnDisplayIndices); if (!redeterminationNeeded) { RebuildRealizedColumnsBlockList = false; } } /// /// Helper method which creates a list of RealizedColumnsBlock struct /// out of a list on integer indices. /// private static List BuildRealizedColumnsBlockList(List indexList) { List resultList = new List(); if (indexList.Count == 1) { resultList.Add(new RealizedColumnsBlock(indexList[0], indexList[0], 0)); } else if (indexList.Count > 0) { int startIndex = indexList[0]; for (int i = 1, count = indexList.Count; i < count; i++) { if (indexList[i] != indexList[i - 1] + 1) { if (resultList.Count == 0) { resultList.Add(new RealizedColumnsBlock(startIndex, indexList[i - 1], 0)); } else { RealizedColumnsBlock lastRealizedColumnsBlock = resultList[resultList.Count - 1]; int startIndexOffset = lastRealizedColumnsBlock.StartIndexOffset + lastRealizedColumnsBlock.EndIndex - lastRealizedColumnsBlock.StartIndex + 1; resultList.Add(new RealizedColumnsBlock(startIndex, indexList[i - 1], startIndexOffset)); } startIndex = indexList[i]; } if (i == count - 1) { if (resultList.Count == 0) { resultList.Add(new RealizedColumnsBlock(startIndex, indexList[i], 0)); } else { RealizedColumnsBlock lastRealizedColumnsBlock = resultList[resultList.Count - 1]; int startIndexOffset = lastRealizedColumnsBlock.StartIndexOffset + lastRealizedColumnsBlock.EndIndex - lastRealizedColumnsBlock.StartIndex + 1; resultList.Add(new RealizedColumnsBlock(startIndex, indexList[i], startIndexOffset)); } } } } return resultList; } /// /// Helper method to build generator position out to index /// private static GeneratorPosition IndexToGeneratorPositionForStart(IItemContainerGenerator generator, int index, out int childIndex) { GeneratorPosition position = (generator != null) ? generator.GeneratorPositionFromIndex(index) : new GeneratorPosition(-1, index + 1); // Determine the position in the children collection for the first // generated container. This assumes that generator.StartAt will be called // with direction=Forward and allowStartAtRealizedItem=true. childIndex = (position.Offset == 0) ? position.Index : position.Index + 1; return position; } /// /// Helper method which generates and measures a /// child of given index /// private UIElement GenerateChild( IItemContainerGenerator generator, Size constraint, EGC.DataGridColumn column, ref IDisposable generatorState, ref int childIndex, out Size childSize) { if (generatorState == null) { generatorState = generator.StartAt(IndexToGeneratorPositionForStart(generator, childIndex, out childIndex), GeneratorDirection.Forward, true); } return GenerateChild(generator, constraint, column, ref childIndex, out childSize); } /// /// Helper method which generates and measures a /// child of given index /// private UIElement GenerateChild( IItemContainerGenerator generator, Size constraint, EGC.DataGridColumn column, ref int childIndex, out Size childSize) { bool newlyRealized; UIElement child = generator.GenerateNext(out newlyRealized) as UIElement; if (child == null) { childSize = new Size(); return null; } AddContainerFromGenerator(childIndex, child, newlyRealized); childIndex++; MeasureChild(child, constraint); EGC.DataGridLength width = column.Width; childSize = child.DesiredSize; if (!DoubleUtil.IsNaN(width.DisplayValue)) { childSize = new Size(width.DisplayValue, childSize.Height); } return child; } /// /// Helper method which generates and measures children of /// a given block of indices /// private Size GenerateChildren( IItemContainerGenerator generator, int startIndex, int endIndex, Size constraint) { double measureWidth = 0.0; double measureHeight = 0.0; int childIndex; GeneratorPosition startPos = IndexToGeneratorPositionForStart(generator, startIndex, out childIndex); EGC.DataGrid parentDataGrid = ParentDataGrid; using (generator.StartAt(startPos, GeneratorDirection.Forward, true)) { Size childSize; for (int i = startIndex; i <= endIndex; i++) { if (!parentDataGrid.Columns[i].IsVisible) { continue; } if (null == GenerateChild(generator, constraint, parentDataGrid.Columns[i], ref childIndex, out childSize)) { return new Size(measureWidth, measureHeight); } measureWidth += childSize.Width; measureHeight = Math.Max(measureHeight, childSize.Height); } } return new Size(measureWidth, measureHeight); } /// /// Method which adds the given container at the given index /// private void AddContainerFromGenerator(int childIndex, UIElement child, bool newlyRealized) { if (!newlyRealized) { // Container is either realized or recycled. If it's realized do nothing; it already exists in the visual // tree in the proper place. if (InRecyclingMode) { IList children = RealizedChildren; if (childIndex >= children.Count || !(children[childIndex] == child)) { Debug.Assert(!children.Contains(child), "we incorrectly identified a recycled container"); // We have a recycled container (if it was a realized container it would have been returned in the // proper location). Note also that recycled containers are NOT in the _realizedChildren list. InsertRecycledContainer(childIndex, child); child.Measure(new Size()); } else { // previously realized child, so do nothing. } } else { // Not recycling; realized container Debug.Assert(child == InternalChildren[childIndex], "Wrong child was generated"); } } else { InsertNewContainer(childIndex, child); } } /// /// Inserts a recycled container in the visual tree /// private void InsertRecycledContainer(int childIndex, UIElement container) { InsertContainer(childIndex, container, true); } /// /// Inserts a new container in the visual tree /// private void InsertNewContainer(int childIndex, UIElement container) { InsertContainer(childIndex, container, false); } /// /// Inserts a container into the Children collection. The container is either new or recycled. /// private void InsertContainer(int childIndex, UIElement container, bool isRecycled) { Debug.Assert(container != null, "Null container was generated"); UIElementCollection children = InternalChildren; // Find the index in the Children collection where we hope to insert the container. // This is done by looking up the index of the container BEFORE the one we hope to insert. // // We have to do it this way because there could be recycled containers between the container we're looking for and the one before it. // By finding the index before the place we want to insert and adding one, we ensure that we'll insert the new container in the // proper location. // // In recycling mode childIndex is the index in the _realizedChildren list, not the index in the // Children collection. We have to convert the index; we'll call the index in the Children collection // the visualTreeIndex. int visualTreeIndex = 0; if (childIndex > 0) { visualTreeIndex = ChildIndexFromRealizedIndex(childIndex - 1); visualTreeIndex++; } if (isRecycled && visualTreeIndex < children.Count && children[visualTreeIndex] == container) { // Don't insert if a recycled container is in the proper place already } else { if (visualTreeIndex < children.Count) { int insertIndex = visualTreeIndex; if (isRecycled && VisualTreeHelper.GetParent(container) != null) { // If the container is recycled we have to remove it from its place in the visual tree and // insert it in the proper location. We cant use an internal Move api, so we are removing // and inserting the container Debug.Assert(children[visualTreeIndex] != null, "MoveVisualChild interprets a null destination as 'move to end'"); int containerIndex = children.IndexOf(container); RemoveInternalChildRange(containerIndex, 1); if (containerIndex < insertIndex) { insertIndex--; } InsertInternalChild(insertIndex, container); } else { InsertInternalChild(insertIndex, container); } } else { if (isRecycled && VisualTreeHelper.GetParent(container) != null) { // Recycled container is still in the tree; move it to the end int originalIndex = children.IndexOf(container); RemoveInternalChildRange(originalIndex, 1); AddInternalChild(container); } else { AddInternalChild(container); } } } // Keep realizedChildren in sync w/ the visual tree. if (IsVirtualizing && InRecyclingMode) { _realizedChildren.Insert(childIndex, container); } ItemContainerGenerator.PrepareItemContainer(container); } /// /// Takes an index from the realized list and returns the corresponding index in the Children collection /// private int ChildIndexFromRealizedIndex(int realizedChildIndex) { // If we're not recycling containers then we're not using a realizedChild index and no translation is necessary if (IsVirtualizing && InRecyclingMode) { if (realizedChildIndex < _realizedChildren.Count) { UIElement child = _realizedChildren[realizedChildIndex]; UIElementCollection children = InternalChildren; for (int i = realizedChildIndex; i < children.Count; i++) { if (children[i] == child) { return i; } } Debug.Assert(false, "We should have found a child"); } } return realizedChildIndex; } /// /// Helper method which determines if the given in index /// falls in the given block or in the next block /// private static bool InBlockOrNextBlock(List blockList, int index, ref int blockIndex, ref RealizedColumnsBlock block, out bool pastLastBlock) { pastLastBlock = false; bool exists = true; if (index < block.StartIndex) { exists = false; } else if (index > block.EndIndex) { if (blockIndex == blockList.Count - 1) { blockIndex++; pastLastBlock = true; exists = false; } else { block = blockList[++blockIndex]; if (index < block.StartIndex || index > block.EndIndex) { exists = false; } } } return exists; } /// /// Method which ensures that all the appropriate /// focus trail cells are realized such that tabbing /// works. /// private void EnsureFocusTrail( List realizedColumnIndices, List realizedColumnDisplayIndices, int firstVisibleNonFrozenDisplayIndex, int lastVisibleNonFrozenDisplayIndex, Size constraint) { if (firstVisibleNonFrozenDisplayIndex < 0) { // Non frozen columns can never be brought into viewport. // Hence tabbing is supported only among visible frozen cells // which should already be realized. return; } int frozenColumnCount = ParentDataGrid.FrozenColumnCount; int columnCount = Columns.Count; ItemsControl parentPresenter = ParentPresenter; if (parentPresenter == null) { return; } ItemContainerGenerator generator = parentPresenter.ItemContainerGenerator; int displayIndexListIterator = 0; int previousFocusTrailIndex = -1; // Realizing the child for first visible column for (int i = 0; i < firstVisibleNonFrozenDisplayIndex; i++) { if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator)) { previousFocusTrailIndex = i; break; } } // Realizing the child for first non-frozen column if (previousFocusTrailIndex < frozenColumnCount) { for (int i = frozenColumnCount; i < columnCount; i++) { if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator)) { previousFocusTrailIndex = i; break; } } } // Realizing the preceding child of first visible non-frozen column for (int i = firstVisibleNonFrozenDisplayIndex - 1; i > previousFocusTrailIndex; i--) { if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator)) { previousFocusTrailIndex = i; break; } } // Realizing the suceeding child of last visible non-frozen column for (int i = lastVisibleNonFrozenDisplayIndex + 1; i < columnCount; i++) { if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator)) { previousFocusTrailIndex = i; break; } } // Realizing the child for last column for (int i = columnCount - 1; i > previousFocusTrailIndex; i--) { if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator)) { break; } } return; } /// /// Method which generates the focus trail cell /// if it is not already generated and adds it to /// the block lists appropriately. /// private bool GenerateChildForFocusTrail( ItemContainerGenerator generator, List realizedColumnIndices, List realizedColumnDisplayIndices, Size constraint, int displayIndex, ref int displayIndexListIterator) { EGC.DataGrid dataGrid = ParentDataGrid; EGC.DataGridColumn column = dataGrid.ColumnFromDisplayIndex(displayIndex); if (column.IsVisible) { int columnIndex = dataGrid.ColumnIndexFromDisplayIndex(displayIndex); UIElement child = generator.ContainerFromIndex(columnIndex) as UIElement; if (child == null) { int childIndex = columnIndex; Size childSize; using (((IItemContainerGenerator)generator).StartAt(IndexToGeneratorPositionForStart(generator, childIndex, out childIndex), GeneratorDirection.Forward, true)) { child = GenerateChild(generator, constraint, column, ref childIndex, out childSize); } } if (child != null && EGC.DataGridHelper.TreeHasFocusAndTabStop(child)) { AddToIndicesListIfNeeded( realizedColumnIndices, realizedColumnDisplayIndices, columnIndex, displayIndex, ref displayIndexListIterator); return true; } } return false; } /// /// Helper method which adds the generated /// display index to the the block lists if not /// already present. /// private static void AddToIndicesListIfNeeded( List realizedColumnIndices, List realizedColumnDisplayIndices, int columnIndex, int displayIndex, ref int displayIndexListIterator) { for (int count = realizedColumnDisplayIndices.Count; displayIndexListIterator < count; displayIndexListIterator++) { if (realizedColumnDisplayIndices[displayIndexListIterator] == displayIndex) { return; } else if (realizedColumnDisplayIndices[displayIndexListIterator] > displayIndex) { realizedColumnDisplayIndices.Insert(displayIndexListIterator, displayIndex); realizedColumnIndices.Add(columnIndex); return; } } realizedColumnIndices.Add(columnIndex); realizedColumnDisplayIndices.Add(displayIndex); return; } /// /// Method which virtualizes the children which are determined to be unused. /// Eligible candidates for virtualization are those which are not in block list. /// Some exceptions to the criterion are the cells which are in edit mode, or /// if item is its own container. /// private void VirtualizeChildren(List blockList, IItemContainerGenerator generator) { ObservableCollection columns = ParentDataGrid.Columns; int columnCount = columns.Count; int columnIterator = 0; IList children = RealizedChildren; int childrenCount = children.Count; if (childrenCount == 0) { return; } int blockIndex = 0; int blockCount = blockList.Count; RealizedColumnsBlock block = (blockCount > 0 ? blockList[blockIndex] : new RealizedColumnsBlock(-1, -1, -1)); bool pastLastBlock = (blockCount > 0 ? false : true); int cleanupRangeStart = -1; int cleanupCount = 0; int lastVirtualizedColumnIndex = -1; ItemsControl parentPresenter = ParentPresenter; EGC.DataGridCellsPresenter cellsPresenter = parentPresenter as EGC.DataGridCellsPresenter; EGC.DataGridColumnHeadersPresenter headersPresenter = parentPresenter as EGC.DataGridColumnHeadersPresenter; for (int i = 0; i < childrenCount; i++) { int columnIndex = i; UIElement child = children[i] as UIElement; EGC.IProvideDataGridColumn columnProvider = child as EGC.IProvideDataGridColumn; if (columnProvider != null) { EGC.DataGridColumn column = columnProvider.Column; for (; columnIterator < columnCount; columnIterator++) { if (column == columns[columnIterator]) { break; } } columnIndex = columnIterator++; Debug.Assert(columnIndex < columnCount, "columnIndex should be less than column count"); } bool virtualizeChild = pastLastBlock || !InBlockOrNextBlock(blockList, columnIndex, ref blockIndex, ref block, out pastLastBlock); EGC.DataGridCell cell = child as EGC.DataGridCell; if ((cell != null && (cell.IsEditing || cell.IsKeyboardFocusWithin)) || (cellsPresenter != null && cellsPresenter.IsItemItsOwnContainerInternal(cellsPresenter.Items[columnIndex])) || (headersPresenter != null && headersPresenter.IsItemItsOwnContainerInternal(headersPresenter.Items[columnIndex]))) { virtualizeChild = false; } if (!columns[columnIndex].IsVisible) { virtualizeChild = true; } if (virtualizeChild) { if (cleanupRangeStart == -1) { cleanupRangeStart = i; cleanupCount = 1; } else if (lastVirtualizedColumnIndex == columnIndex - 1) { cleanupCount++; } else { // Meaning that two consecutive children to be virtualized are not corresponding to // two consecutive columns CleanupRange(children, generator, cleanupRangeStart, cleanupCount); childrenCount -= cleanupCount; i -= cleanupCount; cleanupCount = 1; cleanupRangeStart = i; } lastVirtualizedColumnIndex = columnIndex; } else { if (cleanupCount > 0) { CleanupRange(children, generator, cleanupRangeStart, cleanupCount); childrenCount -= cleanupCount; i -= cleanupCount; cleanupCount = 0; cleanupRangeStart = -1; } } } if (cleanupCount > 0) { CleanupRange(children, generator, cleanupRangeStart, cleanupCount); } } /// /// Method which cleans up a given range of children /// private void CleanupRange(IList children, IItemContainerGenerator generator, int startIndex, int count) { if (count <= 0) { return; } if (IsVirtualizing && InRecyclingMode) { Debug.Assert(startIndex >= 0); Debug.Assert(children == _realizedChildren, "the given child list must be the _realizedChildren list when recycling"); // Recycle and remove the children from realized list GeneratorPosition position = new GeneratorPosition(startIndex, 0); ((IRecyclingItemContainerGenerator)generator).Recycle(position, count); _realizedChildren.RemoveRange(startIndex, count); } else { // Remove the desired range of children RemoveInternalChildRange(startIndex, count); generator.Remove(new GeneratorPosition(startIndex, 0), count); } } /// /// Recycled containers still in the InternalChildren collection at the end of Measure should be disconnected /// from the visual tree. Otherwise they're still visible to things like Arrange, keyboard navigation, etc. /// private void DisconnectRecycledContainers() { int realizedIndex = 0; UIElement visualChild; UIElement realizedChild = _realizedChildren.Count > 0 ? _realizedChildren[0] : null; UIElementCollection children = InternalChildren; int removeStartRange = -1; int removalCount = 0; for (int i = 0; i < children.Count; i++) { visualChild = children[i]; if (visualChild == realizedChild) { if (removalCount > 0) { RemoveInternalChildRange(removeStartRange, removalCount); i -= removalCount; removalCount = 0; removeStartRange = -1; } realizedIndex++; if (realizedIndex < _realizedChildren.Count) { realizedChild = _realizedChildren[realizedIndex]; } else { realizedChild = null; } } else { if (removeStartRange == -1) { removeStartRange = i; } removalCount++; } } if (removalCount > 0) { RemoveInternalChildRange(removeStartRange, removalCount); } } #endregion #region Arrange /// /// Private class used to maintain state between arrange /// of multiple children. /// private class ArrangeState { public ArrangeState() { FrozenColumnCount = 0; ChildHeight = 0.0; NextFrozenCellStart = 0.0; NextNonFrozenCellStart = 0.0; ViewportStartX = 0.0; DataGridHorizontalScrollStartX = 0.0; OldClippedChild = null; NewClippedChild = null; } public int FrozenColumnCount { get; set; } public double ChildHeight { get; set; } public double NextFrozenCellStart { get; set; } public double NextNonFrozenCellStart { get; set; } public double ViewportStartX { get; set; } public double DataGridHorizontalScrollStartX { get; set; } public UIElement OldClippedChild { get; set; } public UIElement NewClippedChild { get; set; } } /// /// Helper method to initialize the arrange state /// /// private void InitializeArrangeState(ArrangeState arrangeState) { EGC.DataGrid parentDataGrid = ParentDataGrid; double horizontalOffset = parentDataGrid.HorizontalScrollOffset; double cellsPanelOffset = parentDataGrid.CellsPanelHorizontalOffset; arrangeState.NextFrozenCellStart = horizontalOffset; arrangeState.NextNonFrozenCellStart -= cellsPanelOffset; arrangeState.ViewportStartX = horizontalOffset - cellsPanelOffset; arrangeState.FrozenColumnCount = parentDataGrid.FrozenColumnCount; } /// /// Helper method which which ends the arrange by setting values /// from arrange state to appropriate fields. /// /// private void FinishArrange(ArrangeState arrangeState) { EGC.DataGrid parentDataGrid = ParentDataGrid; // Update the NonFrozenColumnsViewportHorizontalOffset property of datagrid if (parentDataGrid != null) { parentDataGrid.NonFrozenColumnsViewportHorizontalOffset = arrangeState.DataGridHorizontalScrollStartX; } // Remove the clip on previous clipped child if (arrangeState.OldClippedChild != null) { arrangeState.OldClippedChild.CoerceValue(ClipProperty); } // Add the clip on new child to be clipped for the sake of frozen columns. _clippedChildForFrozenBehaviour = arrangeState.NewClippedChild; if (_clippedChildForFrozenBehaviour != null) { _clippedChildForFrozenBehaviour.CoerceValue(ClipProperty); } } private void SetDataGridCellPanelWidth(IList children, double newWidth) { if (children.Count != 0 && children[0] is EGC.DataGridColumnHeader && !DoubleUtil.AreClose(ParentDataGrid.CellsPanelActualWidth, newWidth)) { // Set the CellsPanelActualWidth property of the datagrid ParentDataGrid.CellsPanelActualWidth = newWidth; } } [Conditional("DEBUG")] private static void Debug_VerifyRealizedIndexCountVsDisplayIndexCount(List blockList, List displayIndexBlockList) { Debug.Assert( blockList != null && blockList.Count > 0, "RealizedColumnsBlockList should not be null or empty"); RealizedColumnsBlock lastBlock = blockList[blockList.Count - 1]; RealizedColumnsBlock lastDisplayIndexBlock = displayIndexBlockList[displayIndexBlockList.Count - 1]; Debug.Assert( (lastBlock.StartIndexOffset + lastBlock.EndIndex - lastBlock.StartIndex) == (lastDisplayIndexBlock.StartIndexOffset + lastDisplayIndexBlock.EndIndex - lastDisplayIndexBlock.StartIndex), "RealizedBlockList and DisplayIndex list should indicate same number of elements"); } /// /// Arrange /// /// Iterates over the columns in the display index order and looks if /// it the corresponding child is realized. If yes then arranges it. /// protected override Size ArrangeOverride(Size arrangeSize) { IList children = RealizedChildren; ArrangeState arrangeState = new ArrangeState(); arrangeState.ChildHeight = arrangeSize.Height; EGC.DataGrid parentDataGrid = ParentDataGrid; /* * determine the horizontal offset, cells panel offset and other coordinates used for arrange of children */ if (parentDataGrid != null) { parentDataGrid.QueueInvalidateCellsPanelHorizontalOffset(); SetDataGridCellPanelWidth(children, arrangeSize.Width); InitializeArrangeState(arrangeState); } double averageColumnWidth = parentDataGrid.InternalColumns.AverageColumnWidth; List displayIndexBlockList = RealizedColumnsDisplayIndexBlockList; if (displayIndexBlockList != null && displayIndexBlockList.Count > 0) { List blockList = RealizedColumnsBlockList; Debug_VerifyRealizedIndexCountVsDisplayIndexCount(blockList, displayIndexBlockList); // Get realized children not in realized list, so that they dont participate in arrange List additionalChildIndices = GetRealizedChildrenNotInBlockList(blockList, children); int displayIndexBlockIndex = -1; RealizedColumnsBlock displayIndexBlock = displayIndexBlockList[++displayIndexBlockIndex]; bool pastLastBlock = false; for (int i = 0, count = parentDataGrid.Columns.Count; i < count; i++) { bool realizedChild = InBlockOrNextBlock(displayIndexBlockList, i, ref displayIndexBlockIndex, ref displayIndexBlock, out pastLastBlock); if (pastLastBlock) { break; } // Arrange the child if it is realized if (realizedChild) { int columnIndex = parentDataGrid.ColumnIndexFromDisplayIndex(i); RealizedColumnsBlock block = GetRealizedBlockForColumn(blockList, columnIndex); int childIndex = block.StartIndexOffset + columnIndex - block.StartIndex; if (additionalChildIndices != null) { for (int j = 0, additionalChildrenCount = additionalChildIndices.Count; j < additionalChildrenCount && additionalChildIndices[j] <= childIndex; j++) { childIndex++; } } if (childIndex < children.Count) ArrangeChild(children[childIndex] as UIElement, i, arrangeState); } else { EGC.DataGridColumn column = parentDataGrid.ColumnFromDisplayIndex(i); if (!column.IsVisible) { continue; } double childSize = GetColumnEstimatedMeasureWidth(column, averageColumnWidth); Debug.Assert(i >= arrangeState.FrozenColumnCount, "Frozen cells should have been realized or not visible"); arrangeState.NextNonFrozenCellStart += childSize; } } if (additionalChildIndices != null) { for (int i = 0, count = additionalChildIndices.Count; i < count; i++) { UIElement child = children[additionalChildIndices[i]] as UIElement; child.Arrange(new Rect()); } } } FinishArrange(arrangeState); return arrangeSize; } /// /// Method which arranges the give child /// based on given arrange state. /// /// Determines the start position of the child /// based on its display index, frozen count of /// datagrid, current horizontal offset etc. /// private void ArrangeChild( UIElement child, int displayIndex, ArrangeState arrangeState) { Debug.Assert(child != null, "child cannot be null."); double childWidth = 0.0; EGC.IProvideDataGridColumn cell = child as EGC.IProvideDataGridColumn; // Determine if this child was clipped in last arrange for the sake of frozen columns if (child == _clippedChildForFrozenBehaviour) { arrangeState.OldClippedChild = child; _clippedChildForFrozenBehaviour = null; } // Width determinition of the child to be arranged. It is // display value if available else the ActualWidth if (cell != null) { Debug.Assert(cell.Column != null, "column cannot be null."); childWidth = cell.Column.Width.DisplayValue; if (DoubleUtil.IsNaN(childWidth)) { childWidth = cell.Column.ActualWidth; } } else { childWidth = child.DesiredSize.Width; } Rect rcChild = new Rect(new Size(childWidth, arrangeState.ChildHeight)); // Determinition of start point for children to arrange. Lets say the there are 5 columns of which 2 are frozen. // If the datagrid is scrolled horizontally. Following is the snapshot of arrange /* * * *| | | | | | | |* * | |* */ if (displayIndex < arrangeState.FrozenColumnCount) { // For all the frozen children start from the horizontal offset // and arrange increamentally rcChild.X = arrangeState.NextFrozenCellStart; arrangeState.NextFrozenCellStart += childWidth; arrangeState.DataGridHorizontalScrollStartX += childWidth; } else { // For arranging non frozen children arrange which ever can be arranged // from the start to horizontal offset. This would fill out the space left by // frozen children. The next one child will be arranged and clipped accordingly past frozen // children. The remaining children will arranged in the remaining space. if (DoubleUtil.LessThanOrClose(arrangeState.NextNonFrozenCellStart, arrangeState.ViewportStartX)) { if (DoubleUtil.LessThanOrClose(arrangeState.NextNonFrozenCellStart + childWidth, arrangeState.ViewportStartX)) { rcChild.X = arrangeState.NextNonFrozenCellStart; arrangeState.NextNonFrozenCellStart += childWidth; } else { double cellChoppedWidth = arrangeState.ViewportStartX - arrangeState.NextNonFrozenCellStart; if (DoubleUtil.AreClose(cellChoppedWidth, 0.0)) { rcChild.X = arrangeState.NextFrozenCellStart; arrangeState.NextNonFrozenCellStart = arrangeState.NextFrozenCellStart + childWidth; } else { rcChild.X = arrangeState.NextFrozenCellStart - cellChoppedWidth; double clipWidth = childWidth - cellChoppedWidth; arrangeState.NewClippedChild = child; _childClipForFrozenBehavior.Rect = new Rect(cellChoppedWidth, 0, clipWidth, rcChild.Height); arrangeState.NextNonFrozenCellStart = arrangeState.NextFrozenCellStart + clipWidth; } } } else { rcChild.X = arrangeState.NextNonFrozenCellStart; arrangeState.NextNonFrozenCellStart += childWidth; } } child.Arrange(rcChild); } /// /// Method which gets the realized block for a given index /// /// /// /// private static RealizedColumnsBlock GetRealizedBlockForColumn(List blockList, int columnIndex) { for (int i = 0, count = blockList.Count; i < count; i++) { RealizedColumnsBlock block = blockList[i]; if (columnIndex >= block.StartIndex && columnIndex <= block.EndIndex) { return block; } } return new RealizedColumnsBlock(-1, -1, -1); } /// /// Determines the list of children which are realized, but /// shouldnt be as per the state stored at rows presenter /// private List GetRealizedChildrenNotInBlockList(List blockList, IList children) { EGC.DataGrid parentDataGrid = ParentDataGrid; RealizedColumnsBlock lastBlock = blockList[blockList.Count - 1]; int blockElementCount = lastBlock.StartIndexOffset + lastBlock.EndIndex - lastBlock.StartIndex + 1; if (children.Count == blockElementCount || children.Count == 0) { return null; } Debug.Assert(children.Count > blockElementCount, "Element count from blocks can't be less than total children count"); List additionalChildIndices = new List(); if (blockList.Count == 0) { for (int i = 0, count = children.Count; i < count; i++) { additionalChildIndices.Add(i); } } else { int blockIndex = 0; RealizedColumnsBlock block = blockList[blockIndex++]; for (int i = 0, count = children.Count; i < count; i++) { EGC.IProvideDataGridColumn cell = children[i] as EGC.IProvideDataGridColumn; int columnIndex = i; if (cell != null) { columnIndex = parentDataGrid.Columns.IndexOf(cell.Column); } if (columnIndex < block.StartIndex) { additionalChildIndices.Add(i); } else if (columnIndex > block.EndIndex) { if (blockIndex >= blockList.Count) { for (int j = i; j < count; j++) { additionalChildIndices.Add(j); } break; } block = blockList[blockIndex++]; Debug.Assert(columnIndex <= block.EndIndex, "Missing children for index in block list"); if (columnIndex < block.StartIndex) { additionalChildIndices.Add(i); } } } } return additionalChildIndices; } #endregion #region Column Virtualization private bool RebuildRealizedColumnsBlockList { get { EGC.DataGrid dataGrid = ParentDataGrid; if (dataGrid != null) { EGC.DataGridColumnCollection columns = dataGrid.InternalColumns; return IsVirtualizing ? columns.RebuildRealizedColumnsBlockListForVirtualizedRows : columns.RebuildRealizedColumnsBlockListForNonVirtualizedRows; } return true; } set { EGC.DataGrid dataGrid = ParentDataGrid; if (dataGrid != null) { if (IsVirtualizing) { dataGrid.InternalColumns.RebuildRealizedColumnsBlockListForVirtualizedRows = value; } else { dataGrid.InternalColumns.RebuildRealizedColumnsBlockListForNonVirtualizedRows = value; } } } } private List RealizedColumnsBlockList { get { EGC.DataGrid dataGrid = ParentDataGrid; if (dataGrid != null) { EGC.DataGridColumnCollection columns = dataGrid.InternalColumns; return IsVirtualizing ? columns.RealizedColumnsBlockListForVirtualizedRows : columns.RealizedColumnsBlockListForNonVirtualizedRows; } return null; } set { EGC.DataGrid dataGrid = ParentDataGrid; if (dataGrid != null) { if (IsVirtualizing) { dataGrid.InternalColumns.RealizedColumnsBlockListForVirtualizedRows = value; } else { dataGrid.InternalColumns.RealizedColumnsBlockListForNonVirtualizedRows = value; } } } } private List RealizedColumnsDisplayIndexBlockList { get { EGC.DataGrid dataGrid = ParentDataGrid; if (dataGrid != null) { EGC.DataGridColumnCollection columns = dataGrid.InternalColumns; return IsVirtualizing ? columns.RealizedColumnsDisplayIndexBlockListForVirtualizedRows : columns.RealizedColumnsDisplayIndexBlockListForNonVirtualizedRows; } return null; } set { EGC.DataGrid dataGrid = ParentDataGrid; if (dataGrid != null) { if (IsVirtualizing) { dataGrid.InternalColumns.RealizedColumnsDisplayIndexBlockListForVirtualizedRows = value; } else { dataGrid.InternalColumns.RealizedColumnsDisplayIndexBlockListForNonVirtualizedRows = value; } } } } /// /// This method is invoked when the IsItemsHost property changes. /// /// The old value of the IsItemsHost property. /// The new value of the IsItemsHost property. protected override void OnIsItemsHostChanged(bool oldIsItemsHost, bool newIsItemsHost) { base.OnIsItemsHostChanged(oldIsItemsHost, newIsItemsHost); if (newIsItemsHost) { ItemsControl parentPresenter = ParentPresenter; if (parentPresenter != null) { IItemContainerGenerator generator = parentPresenter.ItemContainerGenerator as IItemContainerGenerator; if (generator != null && generator == generator.GetItemContainerGeneratorForPanel(this)) { EGC.DataGridCellsPresenter cellsPresenter = parentPresenter as EGC.DataGridCellsPresenter; if (cellsPresenter != null) { cellsPresenter.InternalItemsHost = this; } else { EGC.DataGridColumnHeadersPresenter headersPresenter = parentPresenter as EGC.DataGridColumnHeadersPresenter; if (headersPresenter != null) { headersPresenter.InternalItemsHost = this; } } } } } else { ItemsControl parentPresenter = ParentPresenter; if (parentPresenter != null) { EGC.DataGridCellsPresenter cellsPresenter = parentPresenter as EGC.DataGridCellsPresenter; if (cellsPresenter != null) { if (cellsPresenter.InternalItemsHost == this) { cellsPresenter.InternalItemsHost = null; } } else { EGC.DataGridColumnHeadersPresenter headersPresenter = parentPresenter as EGC.DataGridColumnHeadersPresenter; if (headersPresenter != null && headersPresenter.InternalItemsHost == this) { headersPresenter.InternalItemsHost = null; } } } } } /// /// The property which returns DataGridRowsPresenter ancestor of this panel /// private EGC.DataGridRowsPresenter ParentRowsPresenter { get { EGC.DataGrid parentDataGrid = ParentDataGrid; if (parentDataGrid == null) { return null; } return parentDataGrid.InternalItemsHost as EGC.DataGridRowsPresenter; } } private void DetermineVirtualizationState() { ItemsControl parentPresenter = ParentPresenter; if (parentPresenter != null) { IsVirtualizing = VirtualizingStackPanel.GetIsVirtualizing(parentPresenter); InRecyclingMode = (VirtualizingStackPanel.GetVirtualizationMode(parentPresenter) == VirtualizationMode.Recycling); } } /// /// Property which determines if one has to virtualize the cells /// private bool IsVirtualizing { get; set; } /// /// Property which determines if one is in recycling mode. /// private bool InRecyclingMode { get; set; } /// /// Helper method which estimates the width of the column /// private static double GetColumnEstimatedMeasureWidth(EGC.DataGridColumn column, double averageColumnWidth) { if (!column.IsVisible) { return 0.0; } double childMeasureWidth = column.Width.DisplayValue; if (DoubleUtil.IsNaN(childMeasureWidth)) { childMeasureWidth = Math.Max(averageColumnWidth, column.MinWidth); childMeasureWidth = Math.Min(childMeasureWidth, column.MaxWidth); } return childMeasureWidth; } /// /// Helper method which estimates the sum of widths of /// a given block of columns. /// private double GetColumnEstimatedMeasureWidthSum(int startIndex, int endIndex, double averageColumnWidth) { double measureWidth = 0.0; EGC.DataGrid parentDataGrid = ParentDataGrid; for (int i = startIndex; i <= endIndex; i++) { measureWidth += GetColumnEstimatedMeasureWidth(parentDataGrid.Columns[i], averageColumnWidth); } return measureWidth; } /// /// Returns the list of childen that have been realized by the Generator. /// We must use this method whenever we interact with the Generator's index. /// In recycling mode the Children collection also contains recycled containers and thus does /// not map to the Generator's list. /// private IList RealizedChildren { get { if (IsVirtualizing && InRecyclingMode) { return _realizedChildren; } else { return InternalChildren; } } } /// /// Helper method which ensures that the _realizedChildren /// member is properly initialized /// private void EnsureRealizedChildren() { if (IsVirtualizing && InRecyclingMode) { if (_realizedChildren == null) { UIElementCollection children = InternalChildren; _realizedChildren = new List(children.Count); for (int i = 0; i < children.Count; i++) { _realizedChildren.Add(children[i]); } } } else { _realizedChildren = null; } } /// /// Helper method to compute the cells panel horizontal offset /// internal double ComputeCellsPanelHorizontalOffset() { Debug.Assert(ParentDataGrid != null, "ParentDataGrid should not be null"); double cellsPanelOffset = 0.0; EGC.DataGrid dataGrid = ParentDataGrid; double horizontalOffset = dataGrid.HorizontalScrollOffset; ScrollViewer scrollViewer = dataGrid.InternalScrollHost; if (scrollViewer != null) { cellsPanelOffset = horizontalOffset + TransformToAncestor(scrollViewer).Transform(new Point()).X; } return cellsPanelOffset; } /// /// Helper method which returns the viewport width /// private double GetViewportWidth() { double availableViewportWidth = 0.0; EGC.DataGrid parentDataGrid = ParentDataGrid; if (parentDataGrid != null) { ScrollContentPresenter scrollContentPresenter = parentDataGrid.InternalScrollContentPresenter; if (scrollContentPresenter != null && !scrollContentPresenter.CanContentScroll) { availableViewportWidth = scrollContentPresenter.ViewportWidth; } else { IScrollInfo scrollInfo = parentDataGrid.InternalItemsHost as IScrollInfo; if (scrollInfo != null) { availableViewportWidth = scrollInfo.ViewportWidth; } } } EGC.DataGridRowsPresenter parentRowsPresenter = ParentRowsPresenter; if (DoubleUtil.AreClose(availableViewportWidth, 0.0) && parentRowsPresenter != null) { Size rowPresenterAvailableSize = parentRowsPresenter.AvailableSize; if (!DoubleUtil.IsNaN(rowPresenterAvailableSize.Width) && !Double.IsInfinity(rowPresenterAvailableSize.Width)) { availableViewportWidth = rowPresenterAvailableSize.Width; } } return availableViewportWidth; } /// /// Called when the Items collection associated with the containing ItemsControl changes. /// /// sender /// Event arguments protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args) { base.OnItemsChanged(sender, args); switch (args.Action) { case NotifyCollectionChangedAction.Remove: OnItemsRemove(args); break; case NotifyCollectionChangedAction.Replace: OnItemsReplace(args); break; case NotifyCollectionChangedAction.Move: OnItemsMove(args); break; case NotifyCollectionChangedAction.Reset: break; } } private void OnItemsRemove(ItemsChangedEventArgs args) { RemoveChildRange(args.Position, args.ItemCount, args.ItemUICount); } private void OnItemsReplace(ItemsChangedEventArgs args) { RemoveChildRange(args.Position, args.ItemCount, args.ItemUICount); } private void OnItemsMove(ItemsChangedEventArgs args) { RemoveChildRange(args.OldPosition, args.ItemCount, args.ItemUICount); } private void RemoveChildRange(GeneratorPosition position, int itemCount, int itemUICount) { if (IsItemsHost) { UIElementCollection children = InternalChildren; int pos = position.Index; if (position.Offset > 0) { // An item is being removed after the one at the index pos++; } if (pos < children.Count) { Debug.Assert((itemCount == itemUICount) || (itemUICount == 0), "Both ItemUICount and ItemCount should be equal or ItemUICount should be 0."); if (itemUICount > 0) { RemoveInternalChildRange(pos, itemUICount); if (IsVirtualizing && InRecyclingMode) { // No need to call EnsureRealizedChildren because at this point it is expected that // the _realizedChildren collection is already initialized (because of previous measures). _realizedChildren.RemoveRange(pos, itemUICount); } } } } } /// /// Sets the _realizedChildren in sync when children are cleared /// protected override void OnClearChildren() { base.OnClearChildren(); _realizedChildren = null; } /// /// A workaround method to access BringIndexIntoView method in this assembly. /// /// internal void InternalBringIndexIntoView(int index) { BringIndexIntoView(index); } /// /// Determines the position of the child and sets the horizontal /// offset appropriately. /// /// Specify the item index that should become visible /// /// Thrown if index is out of range /// protected override void BringIndexIntoView(int index) { EGC.DataGrid parentDataGrid = ParentDataGrid; if (parentDataGrid == null) { base.BringIndexIntoView(index); return; } if (index < 0 || index >= parentDataGrid.Columns.Count) { throw new ArgumentOutOfRangeException("index"); } ScrollContentPresenter scrollContentPresenter = parentDataGrid.InternalScrollContentPresenter; IScrollInfo scrollInfo = null; if (scrollContentPresenter != null && !scrollContentPresenter.CanContentScroll) { scrollInfo = scrollContentPresenter; } else { EGC.DataGridRowsPresenter rowsPresenter = ParentRowsPresenter; if (rowsPresenter != null) { scrollInfo = rowsPresenter; } } if (scrollInfo == null) { base.BringIndexIntoView(index); return; } double newHorizontalOffset = 0.0; double oldHorizontalOffset = parentDataGrid.HorizontalScrollOffset; while (!IsChildInView(index, out newHorizontalOffset) && !DoubleUtil.AreClose(oldHorizontalOffset, newHorizontalOffset)) { scrollInfo.SetHorizontalOffset(newHorizontalOffset); UpdateLayout(); oldHorizontalOffset = newHorizontalOffset; } } /// /// Method which determines if the child at given index is already in view. /// Also returns the appropriate estimated horizontal offset to bring the /// child into view if it is not already in view. /// private bool IsChildInView(int index, out double newHorizontalOffset) { EGC.DataGrid parentDataGrid = ParentDataGrid; double horizontalOffset = parentDataGrid.HorizontalScrollOffset; newHorizontalOffset = horizontalOffset; double averageColumnWidth = parentDataGrid.InternalColumns.AverageColumnWidth; int frozenColumnCount = parentDataGrid.FrozenColumnCount; double cellsPanelOffset = parentDataGrid.CellsPanelHorizontalOffset; double availableViewportWidth = GetViewportWidth(); double nextFrozenCellStart = horizontalOffset; // indicates the start position for next frozen cell double nextNonFrozenCellStart = -cellsPanelOffset; // indicates the start position for next non-frozen cell double viewportStartX = horizontalOffset - cellsPanelOffset; // indicates the start of viewport with respect to coordinate system of cell panel int displayIndex = Columns[index].DisplayIndex; double columnStart = 0.0; double columnEnd = 0.0; // Determine the start and end position of the concerned column in horizontal direction for (int i = 0; i <= displayIndex; i++) { EGC.DataGridColumn column = parentDataGrid.ColumnFromDisplayIndex(i); if (!column.IsVisible) { continue; } double columnWidth = GetColumnEstimatedMeasureWidth(column, averageColumnWidth); if (i < frozenColumnCount) { columnStart = nextFrozenCellStart; columnEnd = columnStart + columnWidth; nextFrozenCellStart += columnWidth; } else { if (DoubleUtil.LessThanOrClose(nextNonFrozenCellStart, viewportStartX)) { if (DoubleUtil.LessThanOrClose(nextNonFrozenCellStart + columnWidth, viewportStartX)) { columnStart = nextNonFrozenCellStart; columnEnd = columnStart + columnWidth; nextNonFrozenCellStart += columnWidth; } else { columnStart = nextFrozenCellStart; double cellChoppedWidth = viewportStartX - nextNonFrozenCellStart; if (DoubleUtil.AreClose(cellChoppedWidth, 0.0)) { columnEnd = columnStart + columnWidth; nextNonFrozenCellStart = nextFrozenCellStart + columnWidth; } else { // If the concerned child is clipped for the sake of frozen columns // then bring the start of child into view double clipWidth = columnWidth - cellChoppedWidth; columnEnd = columnStart + clipWidth; nextNonFrozenCellStart = nextFrozenCellStart + clipWidth; if (i == displayIndex) { newHorizontalOffset = horizontalOffset - cellChoppedWidth; return false; } } } } else { columnStart = nextNonFrozenCellStart; columnEnd = columnStart + columnWidth; nextNonFrozenCellStart += columnWidth; } } } double viewportEndX = viewportStartX + availableViewportWidth; if (DoubleUtil.LessThan(columnStart, viewportStartX)) { newHorizontalOffset = columnStart + cellsPanelOffset; } else if (DoubleUtil.GreaterThan(columnEnd, viewportEndX)) { double offsetChange = columnEnd - viewportEndX; if (displayIndex < frozenColumnCount) { nextFrozenCellStart -= (columnEnd - columnStart); } if (DoubleUtil.LessThan(columnStart - offsetChange, nextFrozenCellStart)) { offsetChange = columnStart - nextFrozenCellStart; } if (DoubleUtil.AreClose(offsetChange, 0.0)) { return true; } else { newHorizontalOffset = horizontalOffset + offsetChange; } } else { return true; } return false; } #endregion #region Frozen Columns /// /// Method which returns the clip for the child which overlaps with frozen column /// /// /// internal Geometry GetFrozenClipForChild(UIElement child) { if (child == _clippedChildForFrozenBehaviour) { return _childClipForFrozenBehavior; } return null; } #endregion #region Helpers /// /// Returns the columns on the parent DataGrid. /// private ObservableCollection Columns { get { EGC.DataGrid parentDataGrid = ParentDataGrid; if (parentDataGrid != null) { return parentDataGrid.Columns; } return null; } } /// /// The row that this panel presents belongs to the DataGrid returned from this property. /// private EGC.DataGrid ParentDataGrid { get { if (_parentDataGrid == null) { EGC.DataGridCellsPresenter presenter = ParentPresenter as EGC.DataGridCellsPresenter; if (presenter != null) { EGC.DataGridRow row = presenter.DataGridRowOwner; if (row != null) { _parentDataGrid = row.DataGridOwner; } } else { EGC.DataGridColumnHeadersPresenter headersPresenter = ParentPresenter as EGC.DataGridColumnHeadersPresenter; if (headersPresenter != null) { _parentDataGrid = headersPresenter.ParentDataGrid; } } } return _parentDataGrid; } } private ItemsControl ParentPresenter { get { FrameworkElement itemsPresenter = TemplatedParent as FrameworkElement; if (itemsPresenter != null) { return itemsPresenter.TemplatedParent as ItemsControl; } return null; } } #endregion #region Data private EGC.DataGrid _parentDataGrid; private UIElement _clippedChildForFrozenBehaviour; private RectangleGeometry _childClipForFrozenBehavior = new RectangleGeometry(); private List _realizedChildren; #endregion } }