//--------------------------------------------------------------------------- // // 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.Threading; using MS.Internal; using EGC = ExtendedGrid.Microsoft.Windows.Controls; namespace ExtendedGrid.Microsoft.Windows.Controls { /// /// Internal class that holds the DataGrid's column collection. Handles error-checking columns as they come in. /// internal class DataGridColumnCollection : ObservableCollection { internal DataGridColumnCollection(EGC.DataGrid dataGridOwner) { Debug.Assert(dataGridOwner != null, "We should have a valid DataGrid"); DisplayIndexMap = new List(5); _dataGridOwner = dataGridOwner; RealizedColumnsBlockListForNonVirtualizedRows = null; RealizedColumnsDisplayIndexBlockListForNonVirtualizedRows = null; RebuildRealizedColumnsBlockListForNonVirtualizedRows = true; RealizedColumnsBlockListForVirtualizedRows = null; RealizedColumnsDisplayIndexBlockListForVirtualizedRows = null; RebuildRealizedColumnsBlockListForVirtualizedRows = true; } #region Protected Overrides protected override void InsertItem(int index, EGC.DataGridColumn item) { if (item == null) { throw new ArgumentNullException("item", SR.Get(SRID.DataGrid_NullColumn)); } if (item.DataGridOwner != null) { throw new ArgumentException(SR.Get(SRID.DataGrid_InvalidColumnReuse, item.Header), "item"); } if (DisplayIndexMapInitialized) { ValidateDisplayIndex(item, item.DisplayIndex, true); } base.InsertItem(index, item); item.CoerceValue(EGC.DataGridColumn.IsFrozenProperty); } protected override void SetItem(int index, EGC.DataGridColumn item) { if (item == null) { throw new ArgumentNullException("item", SR.Get(SRID.DataGrid_NullColumn)); } if (index >= Count || index < 0) { throw new ArgumentOutOfRangeException("index", SR.Get(SRID.DataGrid_ColumnIndexOutOfRange, item.Header)); } if (item.DataGridOwner != null && this[index] != item) { throw new ArgumentException(SR.Get(SRID.DataGrid_InvalidColumnReuse, item.Header), "item"); } if (DisplayIndexMapInitialized) { ValidateDisplayIndex(item, item.DisplayIndex); } base.SetItem(index, item); item.CoerceValue(EGC.DataGridColumn.IsFrozenProperty); } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: if (DisplayIndexMapInitialized) { UpdateDisplayIndexForNewColumns(e.NewItems, e.NewStartingIndex); } InvalidateHasVisibleStarColumns(); break; case NotifyCollectionChangedAction.Move: if (DisplayIndexMapInitialized) { UpdateDisplayIndexForMovedColumn(e.OldStartingIndex, e.NewStartingIndex); } break; case NotifyCollectionChangedAction.Remove: if (DisplayIndexMapInitialized) { UpdateDisplayIndexForRemovedColumns(e.OldItems, e.OldStartingIndex); } ClearDisplayIndex(e.OldItems, e.NewItems); InvalidateHasVisibleStarColumns(); break; case NotifyCollectionChangedAction.Replace: if (DisplayIndexMapInitialized) { UpdateDisplayIndexForReplacedColumn(e.OldItems, e.NewItems); } ClearDisplayIndex(e.OldItems, e.NewItems); InvalidateHasVisibleStarColumns(); break; case NotifyCollectionChangedAction.Reset: // We dont ClearDisplayIndex here because we no longer have access to the old items. // Instead this is handled in ClearItems. if (DisplayIndexMapInitialized) { DisplayIndexMap.Clear(); DataGridOwner.UpdateColumnsOnVirtualizedCellInfoCollections(NotifyCollectionChangedAction.Reset, -1, null, -1); } HasVisibleStarColumns = false; break; } base.OnCollectionChanged(e); } /// /// Clear's all the columns from this collection and resets DisplayIndex to its default value. /// protected override void ClearItems() { ClearDisplayIndex(this, null); // Clear DataGrid reference is on all columns. // Doing it here since CollectionChanged notification wouldn't // propagate the cleared columns list. DataGridOwner.UpdateDataGridReference(this, true); base.ClearItems(); } #endregion #region Notification Propagation internal void NotifyPropertyChanged(DependencyObject d, string propertyName, DependencyPropertyChangedEventArgs e, NotificationTarget target) { if (EGC.DataGridHelper.ShouldNotifyColumnCollection(target)) { if (e.Property == EGC.DataGridColumn.DisplayIndexProperty) { OnColumnDisplayIndexChanged((EGC.DataGridColumn)d, (int)e.OldValue, (int)e.NewValue); if (((EGC.DataGridColumn)d).IsVisible) { InvalidateColumnRealization(true); } } else if (e.Property == EGC.DataGridColumn.WidthProperty) { if (((EGC.DataGridColumn)d).IsVisible) { InvalidateColumnRealization(false); } } else if (e.Property == EGC.DataGrid.FrozenColumnCountProperty) { InvalidateColumnRealization(false); OnDataGridFrozenColumnCountChanged((int)e.OldValue, (int)e.NewValue); } else if (e.Property == EGC.DataGridColumn.VisibilityProperty) { InvalidateHasVisibleStarColumns(); InvalidateColumnWidthsComputation(); InvalidateColumnRealization(true); } else if (e.Property == EGC.DataGrid.EnableColumnVirtualizationProperty) { InvalidateColumnRealization(true); } else if (e.Property == EGC.DataGrid.CellsPanelHorizontalOffsetProperty) { OnCellsPanelHorizontalOffsetChanged(e); } else if (e.Property == EGC.DataGrid.HorizontalScrollOffsetProperty || string.Compare(propertyName, "ViewportWidth", StringComparison.Ordinal) == 0) { InvalidateColumnRealization(false); } } if (EGC.DataGridHelper.ShouldNotifyColumns(target)) { int count = this.Count; for (int i = 0; i < count; i++) { // Passing in NotificationTarget.Columns directly to ensure the notification doesn't // bounce back to the collection. this[i].NotifyPropertyChanged(d, e, NotificationTarget.Columns); } } } #endregion #region Display Index /// /// Returns the DataGridColumn with the given DisplayIndex /// internal EGC.DataGridColumn ColumnFromDisplayIndex(int displayIndex) { Debug.Assert(displayIndex >= 0 && displayIndex < DisplayIndexMap.Count, "displayIndex should have already been validated"); return this[DisplayIndexMap[displayIndex]]; } /// /// A map of display index (key) to index in the column collection (value). Used to quickly find a column from its display index. /// internal List DisplayIndexMap { get { if (!DisplayIndexMapInitialized) { InitializeDisplayIndexMap(); } return _displayIndexMap; } private set { _displayIndexMap = value; } } /// /// Used to guard against re-entrancy when changing the DisplayIndex of a column. /// private bool IsUpdatingDisplayIndex { get { return _isUpdatingDisplayIndex; } set { _isUpdatingDisplayIndex = value; } } private int CoerceDefaultDisplayIndex(EGC.DataGridColumn column) { return CoerceDefaultDisplayIndex(column, IndexOf(column)); } /// /// This takes a column and checks that if its DisplayIndex is the default value. If so, it coerces /// the DisplayIndex to be its location in the columns collection. /// We can't do this in CoerceValue because the callback isn't called for default values. Instead we call this /// whenever a column is added or replaced in the collection or when the DisplayIndex of an existing column has changed. /// /// The column /// The DisplayIndex the column should have /// The DisplayIndex of the column private int CoerceDefaultDisplayIndex(EGC.DataGridColumn column, int newDisplayIndex) { if (EGC.DataGridHelper.IsDefaultValue(column, EGC.DataGridColumn.DisplayIndexProperty)) { bool isUpdating = IsUpdatingDisplayIndex; try { IsUpdatingDisplayIndex = true; column.DisplayIndex = newDisplayIndex; } finally { IsUpdatingDisplayIndex = isUpdating; } return newDisplayIndex; } return column.DisplayIndex; } /// /// Called when a column's display index has changed. /// the old display index of the column /// the new display index of the column private void OnColumnDisplayIndexChanged(EGC.DataGridColumn column, int oldDisplayIndex, int newDisplayIndex) { // Handle ClearValue. -1 is the default value and really means 'DisplayIndex should be the index of the column in the column collection'. // We immediately replace the display index without notifying anyone. if (oldDisplayIndex == -1 || _isClearingDisplayIndex) { // change from -1 to the new value; the OnColumnDisplayIndexChanged further down the stack (from old value to -1) will handle // notifying the user and updating columns. return; } // The DisplayIndex may have changed to the default value. newDisplayIndex = CoerceDefaultDisplayIndex(column); if (newDisplayIndex == oldDisplayIndex) { return; } // Our coerce value callback should have validated the DisplayIndex. Fire the virtual. Debug.Assert(newDisplayIndex >= 0 && newDisplayIndex < Count, "The new DisplayIndex should have already been validated"); DataGridOwner.OnColumnDisplayIndexChanged(new EGC.DataGridColumnEventArgs(column)); // Call our helper to walk through all other columns and adjust their display indices. UpdateDisplayIndexForChangedColumn(oldDisplayIndex, newDisplayIndex); } /// /// Called when the DisplayIndex for a single column has changed. The other columns may have conflicting display indices, so /// we walk through them and adjust. This method does nothing if we're already updating display index as part of a larger /// operation (such as add or remove). This is both for re-entrancy and to avoid modifying the display index map as we walk over /// the columns. /// private void UpdateDisplayIndexForChangedColumn(int oldDisplayIndex, int newDisplayIndex) { // The code below adjusts the DisplayIndex of other columns and shouldn't happen if this column's display index is changed // to account for the change in another. if (IsUpdatingDisplayIndex) { // Avoid re-entrancy; setting DisplayIndex on columns causes their OnDisplayIndexChanged to fire. return; } try { IsUpdatingDisplayIndex = true; Debug.Assert(oldDisplayIndex != newDisplayIndex, "A column's display index must have changed for us to call OnColumnDisplayIndexChanged"); Debug.Assert(oldDisplayIndex >= 0 && oldDisplayIndex < Count, "The old DisplayIndex should be valid"); // Update the display index mapping for all affected columns. int columnIndex = DisplayIndexMap[oldDisplayIndex]; DisplayIndexMap.RemoveAt(oldDisplayIndex); DisplayIndexMap.Insert(newDisplayIndex, columnIndex); // Update the display index of other columns. if (newDisplayIndex < oldDisplayIndex) { // DisplayIndex decreased. All columns with DisplayIndex >= newDisplayIndex and < oldDisplayIndex // get their DisplayIndex incremented. for (int i = newDisplayIndex + 1; i <= oldDisplayIndex; i++) { ColumnFromDisplayIndex(i).DisplayIndex++; } } else { // DisplayIndex increased. All columns with DisplayIndex <= newDisplayIndex and > oldDisplayIndex get their DisplayIndex decremented. for (int i = oldDisplayIndex; i < newDisplayIndex; i++) { ColumnFromDisplayIndex(i).DisplayIndex--; } } Debug_VerifyDisplayIndexMap(); DataGridOwner.UpdateColumnsOnVirtualizedCellInfoCollections(NotifyCollectionChangedAction.Move, oldDisplayIndex, null, newDisplayIndex); } finally { IsUpdatingDisplayIndex = false; } } private void UpdateDisplayIndexForMovedColumn(int oldColumnIndex, int newColumnIndex) { int displayIndex = RemoveFromDisplayIndexMap(oldColumnIndex); InsertInDisplayIndexMap(displayIndex, newColumnIndex); DataGridOwner.UpdateColumnsOnVirtualizedCellInfoCollections(NotifyCollectionChangedAction.Move, oldColumnIndex, null, newColumnIndex); } /// /// Sets the DisplayIndex on all newly inserted or added columns and updates the existing columns as necessary. /// private void UpdateDisplayIndexForNewColumns(IList newColumns, int startingIndex) { EGC.DataGridColumn column; int newDisplayIndex, columnIndex; Debug.Assert( newColumns.Count == 1, "This derives from ObservableCollection; it is impossible to add multiple columns at once"); Debug.Assert(IsUpdatingDisplayIndex == false, "We don't add new columns as part of a display index update operation"); try { IsUpdatingDisplayIndex = true; // Set the display index of the new columns and add them to the DisplayIndexMap column = (EGC.DataGridColumn)newColumns[0]; columnIndex = startingIndex; newDisplayIndex = CoerceDefaultDisplayIndex(column, columnIndex); // Inserting the column in the map means that all columns with display index >= the new column's display index // were given a higher display index. This is perfect, except that the column indices have changed due to the insert // in the column collection. We need to iterate over the column indices and increment them appropriately. We also // need to give each changed column a new display index. InsertInDisplayIndexMap(newDisplayIndex, columnIndex); for (int i = 0; i < DisplayIndexMap.Count; i++) { if (i > newDisplayIndex) { // All columns with DisplayIndex higher than the newly inserted columns // need to have their DisplayIndex adiusted. column = ColumnFromDisplayIndex(i); column.DisplayIndex++; } } Debug_VerifyDisplayIndexMap(); DataGridOwner.UpdateColumnsOnVirtualizedCellInfoCollections(NotifyCollectionChangedAction.Add, -1, null, newDisplayIndex); } finally { IsUpdatingDisplayIndex = false; } } // This method is called in first DataGrid measure call // It needs to populate DisplayIndexMap and validate the DisplayIndex of all columns internal void InitializeDisplayIndexMap() { if (_displayIndexMapInitialized) { return; } _displayIndexMapInitialized = true; Debug.Assert(DisplayIndexMap.Count == 0, "DisplayIndexMap should be empty until first measure call."); int columnCount = Count; Dictionary assignedDisplayIndexMap = new Dictionary(); // // First loop: // 1. Validate all columns DisplayIndex // 2. Add columns with DisplayIndex!=default to the assignedDisplayIndexMap for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) { EGC.DataGridColumn currentColumn = this[columnIndex]; int currentColumnDisplayIndex = currentColumn.DisplayIndex; ValidateDisplayIndex(currentColumn, currentColumnDisplayIndex); if (currentColumnDisplayIndex >= 0) { if (assignedDisplayIndexMap.ContainsKey(currentColumnDisplayIndex)) { throw new ArgumentException(SR.Get(SRID.DataGrid_DuplicateDisplayIndex)); } assignedDisplayIndexMap.Add(currentColumnDisplayIndex, columnIndex); } } // Second loop: // Assign DisplayIndex to the columns with default values int nextAvailableColumnIndex = 0; for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) { EGC.DataGridColumn currentColumn = this[columnIndex]; int currentColumnDisplayIndex = currentColumn.DisplayIndex; bool hasDefaultDisplayIndex = EGC.DataGridHelper.IsDefaultValue(currentColumn, EGC.DataGridColumn.DisplayIndexProperty); if (hasDefaultDisplayIndex) { while (assignedDisplayIndexMap.ContainsKey(nextAvailableColumnIndex)) { nextAvailableColumnIndex++; } CoerceDefaultDisplayIndex(currentColumn, nextAvailableColumnIndex); assignedDisplayIndexMap.Add(nextAvailableColumnIndex, columnIndex); nextAvailableColumnIndex++; } } // Third loop: // Copy generated assignedDisplayIndexMap into DisplayIndexMap for (int displayIndex = 0; displayIndex < columnCount; displayIndex++) { Debug.Assert(assignedDisplayIndexMap.ContainsKey(displayIndex)); DisplayIndexMap.Add(assignedDisplayIndexMap[displayIndex]); } } /// /// Updates the display index for all columns affected by the removal of a set of columns. /// private void UpdateDisplayIndexForRemovedColumns(IList oldColumns, int startingIndex) { EGC.DataGridColumn column; Debug.Assert( oldColumns.Count == 1, "This derives from ObservableCollection; it is impossible to remove multiple columns at once"); Debug.Assert(IsUpdatingDisplayIndex == false, "We don't remove columns as part of a display index update operation"); try { IsUpdatingDisplayIndex = true; Debug.Assert(DisplayIndexMap.Count > Count, "Columns were just removed: the display index map shouldn't have yet been updated"); int removedDisplayIndex = RemoveFromDisplayIndexMap(startingIndex); // Removing the column in the map means that all columns with display index >= the new column's display index // were given a lower display index. This is perfect, except that the column indices have changed due to the insert // in the column collection. We need to iterate over the column indices and decrement them appropriately. We also // need to give each changed column a new display index. for (int i = 0; i < DisplayIndexMap.Count; i++) { if (i >= removedDisplayIndex) { // All columns with DisplayIndex higher than the newly deleted columns need to have their DisplayIndex adiusted // (we use >= because a column will have been decremented to have the same display index as the deleted column). column = ColumnFromDisplayIndex(i); column.DisplayIndex--; } } Debug_VerifyDisplayIndexMap(); DataGridOwner.UpdateColumnsOnVirtualizedCellInfoCollections(NotifyCollectionChangedAction.Remove, removedDisplayIndex, (EGC.DataGridColumn)oldColumns[0], -1); } finally { IsUpdatingDisplayIndex = false; } } /// /// Updates the display index for the column that was just replaced and adjusts the other columns if necessary /// private void UpdateDisplayIndexForReplacedColumn(IList oldColumns, IList newColumns) { if (oldColumns != null && oldColumns.Count > 0 && newColumns != null && newColumns.Count > 0) { Debug.Assert(oldColumns.Count == 1 && newColumns.Count == 1, "Multi replace isn't possible with ObservableCollection"); EGC.DataGridColumn oldColumn = (EGC.DataGridColumn)oldColumns[0]; EGC.DataGridColumn newColumn = (EGC.DataGridColumn)newColumns[0]; if (oldColumn != null && newColumn != null) { int newDisplayIndex = CoerceDefaultDisplayIndex(newColumn); if (oldColumn.DisplayIndex != newDisplayIndex) { // Update the display index of other columns to adjust for that of the new one. UpdateDisplayIndexForChangedColumn(oldColumn.DisplayIndex, newDisplayIndex); } DataGridOwner.UpdateColumnsOnVirtualizedCellInfoCollections(NotifyCollectionChangedAction.Replace, newDisplayIndex, oldColumn, newDisplayIndex); } } } /// /// Clears the DisplayIndexProperty on each of the columns. /// private void ClearDisplayIndex(IList oldColumns, IList newColumns) { if (oldColumns != null) { try { _isClearingDisplayIndex = true; var count = oldColumns.Count; for (int i = 0; i < count; i++) { var column = (EGC.DataGridColumn)oldColumns[i]; // Only clear the old column's index if its not in newColumns if (newColumns != null && newColumns.Contains(column)) { continue; } column.ClearValue(EGC.DataGridColumn.DisplayIndexProperty); } } finally { _isClearingDisplayIndex = false; } } } /// /// Returns true if the display index is valid for the given column /// private bool IsDisplayIndexValid(EGC.DataGridColumn column, int displayIndex, bool isAdding) { // -1 is legal only as a default value if (displayIndex == -1 && EGC.DataGridHelper.IsDefaultValue(column, EGC.DataGridColumn.DisplayIndexProperty)) { return true; } // If we're adding a column the count will soon be increased by one -- so a DisplayIndex == Count is ok. return displayIndex >= 0 && (isAdding ? displayIndex <= Count : displayIndex < Count); } /// /// Inserts the given columnIndex in the DisplayIndexMap at the given display index. /// private void InsertInDisplayIndexMap(int newDisplayIndex, int columnIndex) { DisplayIndexMap.Insert(newDisplayIndex, columnIndex); for (int i = 0; i < DisplayIndexMap.Count; i++) { if (DisplayIndexMap[i] >= columnIndex && i != newDisplayIndex) { // These are columns that are after the inserted item in the column collection; we have to adiust // to account for the shifted column index. DisplayIndexMap[i]++; } } } /// /// Removes the given column index from the DisplayIndexMap /// private int RemoveFromDisplayIndexMap(int columnIndex) { int removedDisplayIndex = DisplayIndexMap.IndexOf(columnIndex); Debug.Assert(removedDisplayIndex >= 0); DisplayIndexMap.RemoveAt(removedDisplayIndex); for (int i = 0; i < DisplayIndexMap.Count; i++) { if (DisplayIndexMap[i] >= columnIndex) { // These are columns that are after the removed item in the column collection; we have to adiust // to account for the shifted column index. DisplayIndexMap[i]--; } } return removedDisplayIndex; } /// /// Throws an ArgumentOutOfRangeException if the given displayIndex is invalid for the given column. /// internal void ValidateDisplayIndex(EGC.DataGridColumn column, int displayIndex) { ValidateDisplayIndex(column, displayIndex, false); } /// /// Throws an ArgumentOutOfRangeException if the given displayIndex is invalid for the given column. /// internal void ValidateDisplayIndex(EGC.DataGridColumn column, int displayIndex, bool isAdding) { if (!IsDisplayIndexValid(column, displayIndex, isAdding)) { throw new ArgumentOutOfRangeException("displayIndex", displayIndex, SR.Get(SRID.DataGrid_ColumnDisplayIndexOutOfRange, column.Header)); } } [Conditional("DEBUG")] private void Debug_VerifyDisplayIndexMap() { Debug.Assert(Count == DisplayIndexMap.Count, "Display Index map is of the wrong size"); for (int i = 0; i < DisplayIndexMap.Count; i++) { Debug.Assert(DisplayIndexMap[i] >= 0 && DisplayIndexMap[i] < Count, "DisplayIndex map entry doesn't point to a valid column"); Debug.Assert(ColumnFromDisplayIndex(i).DisplayIndex == i, "DisplayIndex map doesn't match column indices"); } } #endregion #region Frozen Columns /// /// Method which sets / resets the IsFrozen property of columns based on DataGrid's FrozenColumnCount. /// It is possible that the FrozenColumnCount change could be a result of column count itself, in /// which case only the columns which are in the collection at the moment are to be considered. /// /// /// private void OnDataGridFrozenColumnCountChanged(int oldFrozenCount, int newFrozenCount) { if (newFrozenCount > oldFrozenCount) { int columnCount = Math.Min(newFrozenCount, Count); for (int i = oldFrozenCount; i < columnCount; i++) { ColumnFromDisplayIndex(i).IsFrozen = true; } } else { int columnCount = Math.Min(oldFrozenCount, Count); for (int i = newFrozenCount; i < columnCount; i++) { ColumnFromDisplayIndex(i).IsFrozen = false; } } } #endregion #region Helpers private EGC.DataGrid DataGridOwner { get { return _dataGridOwner; } } // Used by DataGridColumnCollection to delay the validation of DisplayIndex // Validation should be delayed because we in the process of adding columns we may have DisplayIndex less that current columns number // After all columns are generated or added in xaml we can do the validation internal bool DisplayIndexMapInitialized { get { return _displayIndexMapInitialized; } } #endregion #region Star Column Helper /// /// Method which determines if there are any /// star columns in datagrid except the given column and also returns perStarWidth /// private bool HasVisibleStarColumnsInternal(EGC.DataGridColumn ignoredColumn, out double perStarWidth) { bool hasStarColumns = false; perStarWidth = 0.0; foreach (EGC.DataGridColumn column in this) { if (column == ignoredColumn || !column.IsVisible) { continue; } EGC.DataGridLength width = column.Width; if (width.IsStar) { hasStarColumns = true; if (!DoubleUtil.AreClose(width.Value, 0.0) && !DoubleUtil.AreClose(width.DesiredValue, 0.0)) { perStarWidth = width.DesiredValue / width.Value; break; } } } return hasStarColumns; } /// /// Method which determines if there are any /// star columns in datagrid and also returns perStarWidth /// private bool HasVisibleStarColumnsInternal(out double perStarWidth) { return HasVisibleStarColumnsInternal(null, out perStarWidth); } /// /// Method which determines if there are any /// star columns in datagrid except the given column /// private bool HasVisibleStarColumnsInternal(EGC.DataGridColumn ignoredColumn) { double perStarWidth; return HasVisibleStarColumnsInternal(ignoredColumn, out perStarWidth); } /// /// Property which determines if there are any star columns /// in the datagrid. /// internal bool HasVisibleStarColumns { get; private set; } /// /// Method which redetermines if the collection has any star columns are not. /// internal void InvalidateHasVisibleStarColumns() { HasVisibleStarColumns = HasVisibleStarColumnsInternal(null); } /// /// Method which redistributes the width of star columns among them selves /// private void RecomputeStarColumnWidths() { double totalDisplaySpace = DataGridOwner.GetViewportWidthForColumns(); double nonStarSpace = 0.0; foreach (EGC.DataGridColumn column in this) { EGC.DataGridLength width = column.Width; if (column.IsVisible && !width.IsStar) { nonStarSpace += width.DisplayValue; } } if (DoubleUtil.IsNaN(nonStarSpace)) { return; } ComputeStarColumnWidths(totalDisplaySpace - nonStarSpace); } /// /// Helper method which computes the widths of all the star columns /// private double ComputeStarColumnWidths(double availableStarSpace) { Debug.Assert( !DoubleUtil.IsNaN(availableStarSpace) && !Double.IsNegativeInfinity(availableStarSpace) && !Double.IsPositiveInfinity(availableStarSpace), "availableStarSpace is not valid"); List unResolvedColumns = new List(); List partialResolvedColumns = new List(); double totalFactors = 0.0; double totalMinWidths = 0.0; double totalMaxWidths = 0.0; double utilizedStarSpace = 0.0; // Accumulate all the star columns into unResolvedColumns in the beginning foreach (EGC.DataGridColumn column in this) { EGC.DataGridLength width = column.Width; if (column.IsVisible && width.IsStar) { unResolvedColumns.Add(column); totalFactors += width.Value; totalMinWidths += column.MinWidth; totalMaxWidths += column.MaxWidth; } } if (DoubleUtil.LessThan(availableStarSpace, totalMinWidths)) { availableStarSpace = totalMinWidths; } if (DoubleUtil.GreaterThan(availableStarSpace, totalMaxWidths)) { availableStarSpace = totalMaxWidths; } while (unResolvedColumns.Count > 0) { double starValue = availableStarSpace / totalFactors; // Find all the columns whose star share is less than thier min width and move such columns // into partialResolvedColumns giving them atleast the minwidth and there by reducing the availableSpace and totalFactors for (int i = 0, count = unResolvedColumns.Count; i < count; i++) { EGC.DataGridColumn column = unResolvedColumns[i]; EGC.DataGridLength width = column.Width; double columnMinWidth = column.MinWidth; double starColumnWidth = availableStarSpace * width.Value / totalFactors; if (DoubleUtil.GreaterThan(columnMinWidth, starColumnWidth)) { availableStarSpace = Math.Max(0.0, availableStarSpace - columnMinWidth); totalFactors -= width.Value; unResolvedColumns.RemoveAt(i); i--; count--; partialResolvedColumns.Add(column); } } // With the remaining space determine in any columns star share is more than maxwidth. // If such columns are found give them their max width and remove them from unResolvedColumns // there by reducing the availablespace and totalfactors. If such column is found, the remaining columns are to be recomputed bool iterationRequired = false; for (int i = 0, count = unResolvedColumns.Count; i < count; i++) { EGC.DataGridColumn column = unResolvedColumns[i]; EGC.DataGridLength width = column.Width; double columnMaxWidth = column.MaxWidth; double starColumnWidth = availableStarSpace * width.Value / totalFactors; if (DoubleUtil.LessThan(columnMaxWidth, starColumnWidth)) { iterationRequired = true; unResolvedColumns.RemoveAt(i); availableStarSpace -= columnMaxWidth; utilizedStarSpace += columnMaxWidth; totalFactors -= width.Value; column.UpdateWidthForStarColumn(columnMaxWidth, starValue * width.Value, width.Value); break; } } // If it was determined by the previous step that another iteration is needed // then move all the partialResolvedColumns back to unResolvedColumns and there by // restoring availablespace and totalfactors. // If another iteration is not needed then allocate min widths to all columns in // partial resolved columns and star share to all unresolved columns there by // ending the loop if (iterationRequired) { for (int i = 0, count = partialResolvedColumns.Count; i < count; i++) { EGC.DataGridColumn column = partialResolvedColumns[i]; unResolvedColumns.Add(column); availableStarSpace += column.MinWidth; totalFactors += column.Width.Value; } partialResolvedColumns.Clear(); } else { for (int i = 0, count = partialResolvedColumns.Count; i < count; i++) { EGC.DataGridColumn column = partialResolvedColumns[i]; EGC.DataGridLength width = column.Width; double columnMinWidth = column.MinWidth; column.UpdateWidthForStarColumn(columnMinWidth, width.Value * starValue, width.Value); utilizedStarSpace += columnMinWidth; } partialResolvedColumns.Clear(); for (int i = 0, count = unResolvedColumns.Count; i < count; i++) { EGC.DataGridColumn column = unResolvedColumns[i]; EGC.DataGridLength width = column.Width; double starColumnWidth = availableStarSpace * width.Value / totalFactors; column.UpdateWidthForStarColumn(starColumnWidth, width.Value * starValue, width.Value); utilizedStarSpace += starColumnWidth; } unResolvedColumns.Clear(); } } return utilizedStarSpace; } #endregion #region Column Width Computation Helper /// /// Method which handles the column widths computation for CellsPanelHorizontalOffset change /// private void OnCellsPanelHorizontalOffsetChanged(DependencyPropertyChangedEventArgs e) { InvalidateColumnRealization(false); // Change in CellsPanelOffset width has an opposite effect on Column // width distribution. Hence widthChange is (oldvalue - newvalue) double totalAvailableWidth = DataGridOwner.GetViewportWidthForColumns(); RedistributeColumnWidthsOnAvailableSpaceChange((double)e.OldValue - (double)e.NewValue, totalAvailableWidth); } /// /// Helper method to invalidate the average width computation /// internal void InvalidateAverageColumnWidth() { _averageColumnWidth = null; } /// /// Property holding the average widht of columns /// internal double AverageColumnWidth { get { if (!_averageColumnWidth.HasValue) { _averageColumnWidth = ComputeAverageColumnWidth(); } return _averageColumnWidth.Value; } } /// /// Helper method which determines the average with of all the columns /// private double ComputeAverageColumnWidth() { double eligibleDisplayValue = 0.0; int totalFactors = 0; foreach (EGC.DataGridColumn column in this) { EGC.DataGridLength width = column.Width; if (column.IsVisible && !DoubleUtil.IsNaN(width.DisplayValue)) { eligibleDisplayValue += width.DisplayValue; totalFactors++; } } if (totalFactors != 0) { return eligibleDisplayValue / totalFactors; } return 0.0; } /// /// Property indicating whether the column width computation opertaion is pending /// internal bool ColumnWidthsComputationPending { get { return _columnWidthsComputationPending; } } /// /// Helper method to invalidate the column width computation /// internal void InvalidateColumnWidthsComputation() { if (_columnWidthsComputationPending) { return; } DataGridOwner.Dispatcher.BeginInvoke(new DispatcherOperationCallback(ComputeColumnWidths), DispatcherPriority.Render, this); _columnWidthsComputationPending = true; } /// /// Helper method which computes the widths of the columns. Used as a callback /// to dispatcher operation /// private object ComputeColumnWidths(object arg) { ComputeColumnWidths(); DataGridOwner.NotifyPropertyChanged( DataGridOwner, "DelayedColumnWidthComputation", new DependencyPropertyChangedEventArgs(), NotificationTarget.CellsPresenter | NotificationTarget.ColumnHeadersPresenter); return null; } /// /// Method which computes the widths of the columns /// private void ComputeColumnWidths() { if (HasVisibleStarColumns) { InitializeColumnDisplayValues(); DistributeSpaceAmongColumns(DataGridOwner.GetViewportWidthForColumns()); } else { ExpandAllColumnWidthsToDesiredValue(); } _columnWidthsComputationPending = false; } /// /// Method which initializes the column width's diplay value to its desired value /// private void InitializeColumnDisplayValues() { foreach (EGC.DataGridColumn column in this) { if (!column.IsVisible) { continue; } EGC.DataGridLength width = column.Width; if (!width.IsStar) { double minWidth = column.MinWidth; double displayValue = EGC.DataGridHelper.CoerceToMinMax(DoubleUtil.IsNaN(width.DesiredValue) ? minWidth : width.DesiredValue, minWidth, column.MaxWidth); if (!DoubleUtil.AreClose(width.DisplayValue, displayValue)) { column.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, displayValue)); } } } } /// /// Method which redistributes the column widths based on change in MinWidth of a column /// internal void RedistributeColumnWidthsOnMinWidthChangeOfColumn(EGC.DataGridColumn changedColumn, double oldMinWidth) { EGC.DataGridLength width = changedColumn.Width; double minWidth = changedColumn.MinWidth; if (DoubleUtil.GreaterThan(minWidth, width.DisplayValue)) { if (HasVisibleStarColumns) { TakeAwayWidthFromColumns(changedColumn, minWidth - width.DisplayValue, false); } changedColumn.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, minWidth)); } else if (DoubleUtil.LessThan(minWidth, oldMinWidth)) { if (width.IsStar) { if (DoubleUtil.AreClose(width.DisplayValue, oldMinWidth)) { GiveAwayWidthToColumns(changedColumn, oldMinWidth - minWidth, true); } } else if (DoubleUtil.GreaterThan(oldMinWidth, width.DesiredValue)) { double displayValue = Math.Max(width.DesiredValue, minWidth); if (HasVisibleStarColumns) { GiveAwayWidthToColumns(changedColumn, oldMinWidth - displayValue); } changedColumn.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, displayValue)); } } } /// /// Method which redistributes the column widths based on change in MaxWidth of a column /// internal void RedistributeColumnWidthsOnMaxWidthChangeOfColumn(EGC.DataGridColumn changedColumn, double oldMaxWidth) { EGC.DataGridLength width = changedColumn.Width; double maxWidth = changedColumn.MaxWidth; if (DoubleUtil.LessThan(maxWidth, width.DisplayValue)) { if (HasVisibleStarColumns) { GiveAwayWidthToColumns(changedColumn, width.DisplayValue - maxWidth); } changedColumn.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, maxWidth)); } else if (DoubleUtil.GreaterThan(maxWidth, oldMaxWidth)) { if (width.IsStar) { RecomputeStarColumnWidths(); } else if (DoubleUtil.LessThan(oldMaxWidth, width.DesiredValue)) { double displayValue = Math.Min(width.DesiredValue, maxWidth); if (HasVisibleStarColumns) { double leftOverSpace = TakeAwayWidthFromUnusedSpace(false, displayValue - oldMaxWidth); leftOverSpace = TakeAwayWidthFromStarColumns(changedColumn, leftOverSpace); displayValue -= leftOverSpace; } changedColumn.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, displayValue)); } } } /// /// Method which redistributes the column widths based on change in Width of a column /// internal void RedistributeColumnWidthsOnWidthChangeOfColumn(EGC.DataGridColumn changedColumn, EGC.DataGridLength oldWidth) { EGC.DataGridLength width = changedColumn.Width; bool hasStarColumns = HasVisibleStarColumns; if (oldWidth.IsStar && !width.IsStar && !hasStarColumns) { ExpandAllColumnWidthsToDesiredValue(); } else if (width.IsStar && !oldWidth.IsStar) { if (!HasVisibleStarColumnsInternal(changedColumn)) { ComputeColumnWidths(); } else { double minWidth = changedColumn.MinWidth; double leftOverSpace = GiveAwayWidthToNonStarColumns(null, oldWidth.DisplayValue - minWidth); changedColumn.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, minWidth + leftOverSpace)); RecomputeStarColumnWidths(); } } else if (width.IsStar && oldWidth.IsStar) { RecomputeStarColumnWidths(); } else if (hasStarColumns) { RedistributeColumnWidthsOnNonStarWidthChange( changedColumn, oldWidth); } } /// /// Method which redistributes the column widths based on change in available space of a column /// internal void RedistributeColumnWidthsOnAvailableSpaceChange(double availableSpaceChange, double newTotalAvailableSpace) { if (!ColumnWidthsComputationPending && HasVisibleStarColumns) { if (DoubleUtil.GreaterThan(availableSpaceChange, 0.0)) { GiveAwayWidthToColumns(null, availableSpaceChange); } else if (DoubleUtil.LessThan(availableSpaceChange, 0.0)) { TakeAwayWidthFromColumns(null, Math.Abs(availableSpaceChange), false, newTotalAvailableSpace); } } } /// /// Method which expands the display values of widths of all columns to /// their desired values. Usually used when the last star column's width /// is changed to non-star /// private void ExpandAllColumnWidthsToDesiredValue() { foreach (EGC.DataGridColumn column in this) { if (!column.IsVisible) { continue; } EGC.DataGridLength width = column.Width; double maxWidth = column.MaxWidth; if (DoubleUtil.GreaterThan(width.DesiredValue, width.DisplayValue) && !DoubleUtil.AreClose(width.DisplayValue, maxWidth)) { column.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, Math.Min(width.DesiredValue, maxWidth))); } } } /// /// Method which redistributes widths of columns on change of a column's width /// when datagrid itself has star columns, but neither the oldwidth or the newwidth /// of changed column is star. /// private void RedistributeColumnWidthsOnNonStarWidthChange(EGC.DataGridColumn changedColumn, EGC.DataGridLength oldWidth) { EGC.DataGridLength width = changedColumn.Width; if (DoubleUtil.GreaterThan(width.DesiredValue, oldWidth.DisplayValue)) { double nonRetrievableSpace = TakeAwayWidthFromColumns(changedColumn, width.DesiredValue - oldWidth.DisplayValue, changedColumn != null); if (DoubleUtil.GreaterThan(nonRetrievableSpace, 0.0)) { changedColumn.SetWidthInternal(new EGC.DataGridLength( width.Value, width.UnitType, width.DesiredValue, Math.Max(width.DisplayValue - nonRetrievableSpace, changedColumn.MinWidth))); } } else if (DoubleUtil.LessThan(width.DesiredValue, oldWidth.DisplayValue)) { double newDesiredValue = EGC.DataGridHelper.CoerceToMinMax(width.DesiredValue, changedColumn.MinWidth, changedColumn.MaxWidth); GiveAwayWidthToColumns(changedColumn, oldWidth.DisplayValue - newDesiredValue); } } /// /// Method which distributes a given amount of width among all the columns /// private void DistributeSpaceAmongColumns(double availableSpace) { double sumOfMinWidths = 0.0; double sumOfMaxWidths = 0.0; double sumOfStarMinWidths = 0.0; foreach (EGC.DataGridColumn column in this) { if (!column.IsVisible) { continue; } sumOfMinWidths += column.MinWidth; sumOfMaxWidths += column.MaxWidth; if (column.Width.IsStar) { sumOfStarMinWidths += column.MinWidth; } } if (DoubleUtil.LessThan(availableSpace, sumOfMinWidths)) { availableSpace = sumOfMinWidths; } if (DoubleUtil.GreaterThan(availableSpace, sumOfMaxWidths)) { availableSpace = sumOfMaxWidths; } double nonStarSpaceLeftOver = DistributeSpaceAmongNonStarColumns(availableSpace - sumOfStarMinWidths); ComputeStarColumnWidths(sumOfStarMinWidths + nonStarSpaceLeftOver); } /// /// Helper method which distributes a given amount of width among all non star columns /// private double DistributeSpaceAmongNonStarColumns(double availableSpace) { double requiredSpace = 0.0; foreach (EGC.DataGridColumn column in this) { EGC.DataGridLength width = column.Width; if (!column.IsVisible || width.IsStar) { continue; } requiredSpace += width.DisplayValue; } if (DoubleUtil.LessThan(availableSpace, requiredSpace)) { double spaceDeficit = requiredSpace - availableSpace; TakeAwayWidthFromNonStarColumns(null, spaceDeficit); } return Math.Max(availableSpace - requiredSpace, 0.0); } #endregion #region Column Resizing Helper /// /// Method which is called when user resize of column starts /// internal void OnColumnResizeStarted() { _originalWidthsForResize = new Dictionary(); foreach (EGC.DataGridColumn column in this) { _originalWidthsForResize[column] = column.Width; } } /// /// Method which is called when user resize of column ends /// internal void OnColumnResizeCompleted(bool cancel) { if (cancel && _originalWidthsForResize != null) { foreach (EGC.DataGridColumn column in this) { if (_originalWidthsForResize.ContainsKey(column)) { column.Width = _originalWidthsForResize[column]; } } } _originalWidthsForResize = null; } /// /// Method which recomputes the widths of columns on resize of column /// internal void RecomputeColumnWidthsOnColumnResize(EGC.DataGridColumn resizingColumn, double horizontalChange, bool retainAuto) { EGC.DataGridLength resizingColumnWidth = resizingColumn.Width; double expectedRezingColumnWidth = resizingColumnWidth.DisplayValue + horizontalChange; if (DoubleUtil.LessThan(expectedRezingColumnWidth, resizingColumn.MinWidth)) { horizontalChange = resizingColumn.MinWidth - resizingColumnWidth.DisplayValue; } else if (DoubleUtil.GreaterThan(expectedRezingColumnWidth, resizingColumn.MaxWidth)) { horizontalChange = resizingColumn.MaxWidth - resizingColumnWidth.DisplayValue; } int resizingColumnIndex = resizingColumn.DisplayIndex; if (DoubleUtil.GreaterThan(horizontalChange, 0.0)) { RecomputeColumnWidthsOnColumnPositiveResize(horizontalChange, resizingColumnIndex, retainAuto); } else if (DoubleUtil.LessThan(horizontalChange, 0.0)) { RecomputeColumnWidthsOnColumnNegativeResize(-horizontalChange, resizingColumnIndex, retainAuto); } } /// /// Method which computes widths of columns on positive resize of a column /// private void RecomputeColumnWidthsOnColumnPositiveResize( double horizontalChange, int resizingColumnIndex, bool retainAuto) { double perStarWidth = 0.0; if (HasVisibleStarColumnsInternal(out perStarWidth)) { // reuse unused space horizontalChange = TakeAwayUnusedSpaceOnColumnPositiveResize(horizontalChange, resizingColumnIndex, retainAuto); // reducing star columns to right horizontalChange = RecomputeStarColumnWidthsOnColumnPositiveResize(horizontalChange, resizingColumnIndex, perStarWidth, retainAuto); // reducing columns to the right which are greater than the min size horizontalChange = RecomputeNonStarColumnWidthsOnColumnPositiveResize(horizontalChange, resizingColumnIndex, retainAuto); } else { EGC.DataGridColumn column = ColumnFromDisplayIndex(resizingColumnIndex); SetResizedColumnWidth(column, horizontalChange, retainAuto); } } /// /// Method which resizes the widths of star columns on positive resize of a column /// private double RecomputeStarColumnWidthsOnColumnPositiveResize( double horizontalChange, int resizingColumnIndex, double perStarWidth, bool retainAuto) { while (DoubleUtil.GreaterThan(horizontalChange, 0.0)) { double minPerStarExcessRatio = Double.PositiveInfinity; double rightStarFactors = GetStarFactorsForPositiveResize(resizingColumnIndex + 1, out minPerStarExcessRatio); if (DoubleUtil.GreaterThan(rightStarFactors, 0.0)) { horizontalChange = ReallocateStarValuesForPositiveResize( resizingColumnIndex, horizontalChange, minPerStarExcessRatio, rightStarFactors, perStarWidth, retainAuto); if (DoubleUtil.AreClose(horizontalChange, 0.0)) { break; } } else { break; } } return horizontalChange; } private static bool CanColumnParticipateInResize(EGC.DataGridColumn column) { return column.IsVisible && column.CanUserResize; } /// /// Method which returns the total of star factors of the columns which could be resized on positive resize of a column /// private double GetStarFactorsForPositiveResize(int startIndex, out double minPerStarExcessRatio) { minPerStarExcessRatio = Double.PositiveInfinity; double rightStarFactors = 0.0; for (int i = startIndex, count = Count; i < count; i++) { EGC.DataGridColumn column = ColumnFromDisplayIndex(i); if (!CanColumnParticipateInResize(column)) { continue; } EGC.DataGridLength width = column.Width; if (width.IsStar && !DoubleUtil.AreClose(width.Value, 0.0)) { if (DoubleUtil.GreaterThan(width.DisplayValue, column.MinWidth)) { rightStarFactors += width.Value; double excessRatio = (width.DisplayValue - column.MinWidth) / width.Value; if (DoubleUtil.LessThan(excessRatio, minPerStarExcessRatio)) { minPerStarExcessRatio = excessRatio; } } } } return rightStarFactors; } /// /// Method which reallocated the star factors of star columns on /// positive resize of a column /// private double ReallocateStarValuesForPositiveResize( int startIndex, double horizontalChange, double perStarExcessRatio, double totalStarFactors, double perStarWidth, bool retainAuto) { double changePerStar = 0.0; double horizontalChangeForIteration = 0.0; if (DoubleUtil.LessThan(horizontalChange, perStarExcessRatio * totalStarFactors)) { changePerStar = horizontalChange / totalStarFactors; horizontalChangeForIteration = horizontalChange; horizontalChange = 0.0; } else { changePerStar = perStarExcessRatio; horizontalChangeForIteration = changePerStar * totalStarFactors; horizontalChange -= horizontalChangeForIteration; } for (int i = startIndex, count = Count; i < count; i++) { EGC.DataGridColumn column = ColumnFromDisplayIndex(i); EGC.DataGridLength width = column.Width; if (i == startIndex) { SetResizedColumnWidth(column, horizontalChangeForIteration, retainAuto); } else if (column.Width.IsStar && CanColumnParticipateInResize(column) && DoubleUtil.GreaterThan(width.DisplayValue, column.MinWidth)) { double columnDesiredWidth = width.DisplayValue - (width.Value * changePerStar); column.UpdateWidthForStarColumn(Math.Max(columnDesiredWidth, column.MinWidth), columnDesiredWidth, columnDesiredWidth / perStarWidth); } } return horizontalChange; } /// /// Method which recomputes widths of non star columns on positive resize of a column /// private double RecomputeNonStarColumnWidthsOnColumnPositiveResize( double horizontalChange, int resizingColumnIndex, bool retainAuto) { if (DoubleUtil.GreaterThan(horizontalChange, 0.0)) { double totalExcessWidth = 0.0; bool iterationNeeded = true; for (int i = Count - 1; iterationNeeded && i > resizingColumnIndex; i--) { EGC.DataGridColumn column = ColumnFromDisplayIndex(i); if (!CanColumnParticipateInResize(column)) { continue; } EGC.DataGridLength width = column.Width; double minWidth = column.MinWidth; if (!width.IsStar && DoubleUtil.GreaterThan(width.DisplayValue, minWidth)) { double columnExcessWidth = width.DisplayValue - minWidth; if (DoubleUtil.GreaterThanOrClose(totalExcessWidth + columnExcessWidth, horizontalChange)) { columnExcessWidth = horizontalChange - totalExcessWidth; iterationNeeded = false; } column.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, width.DisplayValue - columnExcessWidth)); totalExcessWidth += columnExcessWidth; } } if (DoubleUtil.GreaterThan(totalExcessWidth, 0.0)) { EGC.DataGridColumn column = ColumnFromDisplayIndex(resizingColumnIndex); SetResizedColumnWidth(column, totalExcessWidth, retainAuto); horizontalChange -= totalExcessWidth; } } return horizontalChange; } /// /// Method which recomputes the widths of columns on negative resize of a column /// private void RecomputeColumnWidthsOnColumnNegativeResize( double horizontalChange, int resizingColumnIndex, bool retainAuto) { double perStarWidth = 0.0; if (HasVisibleStarColumnsInternal(out perStarWidth)) { // increasing columns to the right which are less than the desired size horizontalChange = RecomputeNonStarColumnWidthsOnColumnNegativeResize(horizontalChange, resizingColumnIndex, retainAuto); // increasing star columns to the right horizontalChange = RecomputeStarColumnWidthsOnColumnNegativeResize(horizontalChange, resizingColumnIndex, perStarWidth, retainAuto); if (DoubleUtil.GreaterThan(horizontalChange, 0.0)) { EGC.DataGridColumn resizingColumn = ColumnFromDisplayIndex(resizingColumnIndex); if (!resizingColumn.Width.IsStar) { SetResizedColumnWidth(resizingColumn, -horizontalChange, retainAuto); } } } else { EGC.DataGridColumn column = ColumnFromDisplayIndex(resizingColumnIndex); SetResizedColumnWidth(column, -horizontalChange, retainAuto); } } /// /// Method which recomputes widths of non star columns on negative resize of a column /// private double RecomputeNonStarColumnWidthsOnColumnNegativeResize( double horizontalChange, int resizingColumnIndex, bool retainAuto) { if (DoubleUtil.GreaterThan(horizontalChange, 0.0)) { double totalLagWidth = 0.0; bool iterationNeeded = true; for (int i = resizingColumnIndex + 1, count = Count; iterationNeeded && i < count; i++) { EGC.DataGridColumn column = ColumnFromDisplayIndex(i); if (!CanColumnParticipateInResize(column)) { continue; } EGC.DataGridLength width = column.Width; if (!width.IsStar && DoubleUtil.LessThan(width.DisplayValue, width.DesiredValue) && !DoubleUtil.AreClose(width.DisplayValue, column.MaxWidth)) { double columnLagWidth = width.DesiredValue - width.DisplayValue; if (DoubleUtil.GreaterThanOrClose(totalLagWidth + columnLagWidth, horizontalChange)) { columnLagWidth = horizontalChange - totalLagWidth; iterationNeeded = false; } column.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, width.DisplayValue + columnLagWidth)); totalLagWidth += columnLagWidth; } } if (DoubleUtil.GreaterThan(totalLagWidth, 0.0)) { EGC.DataGridColumn column = ColumnFromDisplayIndex(resizingColumnIndex); SetResizedColumnWidth(column, -totalLagWidth, retainAuto); horizontalChange -= totalLagWidth; } } return horizontalChange; } /// /// Method which recomputes widths on star columns on negative resize of a column /// private double RecomputeStarColumnWidthsOnColumnNegativeResize( double horizontalChange, int resizingColumnIndex, double perStarWidth, bool retainAuto) { while (DoubleUtil.GreaterThan(horizontalChange, 0.0)) { double minPerStarLagRatio = Double.PositiveInfinity; double rightStarFactors = GetStarFactorsForNegativeResize(resizingColumnIndex + 1, out minPerStarLagRatio); if (DoubleUtil.GreaterThan(rightStarFactors, 0.0)) { horizontalChange = ReallocateStarValuesForNegativeResize( resizingColumnIndex, horizontalChange, minPerStarLagRatio, rightStarFactors, perStarWidth, retainAuto); if (DoubleUtil.AreClose(horizontalChange, 0.0)) { break; } } else { break; } } return horizontalChange; } /// /// Method which returns the total star factors of columns which resize of negative resize of a column /// private double GetStarFactorsForNegativeResize(int startIndex, out double minPerStarLagRatio) { minPerStarLagRatio = Double.PositiveInfinity; double rightStarFactors = 0.0; for (int i = startIndex, count = Count; i < count; i++) { EGC.DataGridColumn column = ColumnFromDisplayIndex(i); if (!CanColumnParticipateInResize(column)) { continue; } EGC.DataGridLength width = column.Width; if (width.IsStar && !DoubleUtil.AreClose(width.Value, 0.0)) { if (DoubleUtil.LessThan(width.DisplayValue, column.MaxWidth)) { rightStarFactors += width.Value; double lagRatio = (column.MaxWidth - width.DisplayValue) / width.Value; if (DoubleUtil.LessThan(lagRatio, minPerStarLagRatio)) { minPerStarLagRatio = lagRatio; } } } } return rightStarFactors; } /// /// Method which reallocates star factors of columns on negative resize of a column /// private double ReallocateStarValuesForNegativeResize( int startIndex, double horizontalChange, double perStarLagRatio, double totalStarFactors, double perStarWidth, bool retainAuto) { double changePerStar = 0.0; double horizontalChangeForIteration = 0.0; if (DoubleUtil.LessThan(horizontalChange, perStarLagRatio * totalStarFactors)) { changePerStar = horizontalChange / totalStarFactors; horizontalChangeForIteration = horizontalChange; horizontalChange = 0.0; } else { changePerStar = perStarLagRatio; horizontalChangeForIteration = changePerStar * totalStarFactors; horizontalChange -= horizontalChangeForIteration; } for (int i = startIndex, count = Count; i < count; i++) { EGC.DataGridColumn column = ColumnFromDisplayIndex(i); EGC.DataGridLength width = column.Width; if (i == startIndex) { SetResizedColumnWidth(column, -horizontalChangeForIteration, retainAuto); } else if (column.Width.IsStar && CanColumnParticipateInResize(column) && DoubleUtil.LessThan(width.DisplayValue, column.MaxWidth)) { double columnDesiredWidth = width.DisplayValue + (width.Value * changePerStar); column.UpdateWidthForStarColumn(Math.Min(columnDesiredWidth, column.MaxWidth), columnDesiredWidth, columnDesiredWidth / perStarWidth); } } return horizontalChange; } /// /// Helper method which sets the width of the column which is currently getting resized /// private static void SetResizedColumnWidth(EGC.DataGridColumn column, double widthDelta, bool retainAuto) { EGC.DataGridLength width = column.Width; double columnDisplayWidth = EGC.DataGridHelper.CoerceToMinMax(width.DisplayValue + widthDelta, column.MinWidth, column.MaxWidth); if (width.IsStar) { double starValue = width.DesiredValue / width.Value; column.UpdateWidthForStarColumn(columnDisplayWidth, columnDisplayWidth, columnDisplayWidth / starValue); } else if (!width.IsAbsolute && retainAuto) { column.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, columnDisplayWidth)); } else { column.SetWidthInternal(new EGC.DataGridLength(columnDisplayWidth, EGC.DataGridLengthUnitType.Pixel, columnDisplayWidth, columnDisplayWidth)); } } #endregion #region Width Give Away Methods /// /// Method which tries to give away the given amount of width /// among all the columns except the ignored column /// private double GiveAwayWidthToColumns(EGC.DataGridColumn ignoredColumn, double giveAwayWidth) { return GiveAwayWidthToColumns(ignoredColumn, giveAwayWidth, false); } /// /// Method which tries to give away the given amount of width /// among all the columns except the ignored column /// private double GiveAwayWidthToColumns(EGC.DataGridColumn ignoredColumn, double giveAwayWidth, bool recomputeStars) { double originalGiveAwayWidth = giveAwayWidth; giveAwayWidth = GiveAwayWidthToScrollViewerExcess(giveAwayWidth); giveAwayWidth = GiveAwayWidthToNonStarColumns(ignoredColumn, giveAwayWidth); if (DoubleUtil.GreaterThan(giveAwayWidth, 0.0) || recomputeStars) { double sumOfStarDisplayWidths = 0.0; double sumOfStarMaxWidths = 0.0; bool giveAwayWidthIncluded = false; foreach (EGC.DataGridColumn column in this) { EGC.DataGridLength width = column.Width; if (width.IsStar && column.IsVisible) { if (column == ignoredColumn) { giveAwayWidthIncluded = true; } sumOfStarDisplayWidths += width.DisplayValue; sumOfStarMaxWidths += column.MaxWidth; } } double expectedStarSpace = sumOfStarDisplayWidths; if (!giveAwayWidthIncluded) { expectedStarSpace += giveAwayWidth; } else if (!DoubleUtil.AreClose(originalGiveAwayWidth, giveAwayWidth)) { expectedStarSpace -= (originalGiveAwayWidth - giveAwayWidth); } double usedStarSpace = ComputeStarColumnWidths(Math.Min(expectedStarSpace, sumOfStarMaxWidths)); giveAwayWidth = Math.Max(usedStarSpace - expectedStarSpace, 0.0); } return giveAwayWidth; } /// /// Method which tries to give away the given amount of width /// among all non star columns except the ignored column /// private double GiveAwayWidthToNonStarColumns(EGC.DataGridColumn ignoredColumn, double giveAwayWidth) { while (DoubleUtil.GreaterThan(giveAwayWidth, 0.0)) { int countOfParticipatingColumns = 0; double minLagWidth = FindMinimumLaggingWidthOfNonStarColumns( ignoredColumn, out countOfParticipatingColumns); if (countOfParticipatingColumns == 0) { break; } double minTotalLagWidth = minLagWidth * countOfParticipatingColumns; if (DoubleUtil.GreaterThanOrClose(minTotalLagWidth, giveAwayWidth)) { minLagWidth = giveAwayWidth / countOfParticipatingColumns; giveAwayWidth = 0.0; } else { giveAwayWidth -= minTotalLagWidth; } GiveAwayWidthToEveryNonStarColumn(ignoredColumn, minLagWidth); } return giveAwayWidth; } /// /// Helper method which finds the minimum non-zero difference between displayvalue and desiredvalue /// among all non star columns /// private double FindMinimumLaggingWidthOfNonStarColumns( EGC.DataGridColumn ignoredColumn, out int countOfParticipatingColumns) { double minLagWidth = Double.PositiveInfinity; countOfParticipatingColumns = 0; foreach (EGC.DataGridColumn column in this) { if (ignoredColumn == column || !column.IsVisible) { continue; } EGC.DataGridLength width = column.Width; if (width.IsStar) { continue; } double columnMaxWidth = column.MaxWidth; if (DoubleUtil.LessThan(width.DisplayValue, width.DesiredValue) && !DoubleUtil.AreClose(width.DisplayValue, columnMaxWidth)) { countOfParticipatingColumns++; double lagWidth = Math.Min(width.DesiredValue, columnMaxWidth) - width.DisplayValue; if (DoubleUtil.LessThan(lagWidth, minLagWidth)) { minLagWidth = lagWidth; } } } return minLagWidth; } /// /// Helper method which gives away the given amount of width to /// every non star column whose display value is less than its desired value /// private void GiveAwayWidthToEveryNonStarColumn(EGC.DataGridColumn ignoredColumn, double perColumnGiveAwayWidth) { foreach (EGC.DataGridColumn column in this) { if (ignoredColumn == column || !column.IsVisible) { continue; } EGC.DataGridLength width = column.Width; if (width.IsStar) { continue; } if (DoubleUtil.LessThan(width.DisplayValue, Math.Min(width.DesiredValue, column.MaxWidth))) { column.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, width.DisplayValue + perColumnGiveAwayWidth)); } } } /// /// Helper method which gives away width to scroll viewer /// if its extent width is greater than viewport width /// private double GiveAwayWidthToScrollViewerExcess(double giveAwayWidth) { double totalSpace = DataGridOwner.GetViewportWidthForColumns(); double usedSpace = 0.0; foreach (EGC.DataGridColumn column in this) { if (column.IsVisible) { usedSpace += column.Width.DisplayValue; } } if (DoubleUtil.GreaterThan(usedSpace, totalSpace)) { double contributingSpace = usedSpace - totalSpace; giveAwayWidth -= Math.Min(contributingSpace, giveAwayWidth); } return giveAwayWidth; } #endregion #region Width Take Away Methods /// /// Method which tries to get the unused column space when another column tries to positive resize /// private double TakeAwayUnusedSpaceOnColumnPositiveResize(double horizontalChange, int resizingColumnIndex, bool retainAuto) { double spaceNeeded = TakeAwayWidthFromUnusedSpace(false, horizontalChange); if (DoubleUtil.LessThan(spaceNeeded, horizontalChange)) { EGC.DataGridColumn resizingColumn = ColumnFromDisplayIndex(resizingColumnIndex); SetResizedColumnWidth(resizingColumn, horizontalChange - spaceNeeded, retainAuto); } return spaceNeeded; } /// /// Helper method which tries to take away width from unused space /// private double TakeAwayWidthFromUnusedSpace(bool spaceAlreadyUtilized, double takeAwayWidth, double totalAvailableWidth) { double usedSpace = 0.0; foreach (EGC.DataGridColumn column in this) { if (column.IsVisible) { usedSpace += column.Width.DisplayValue; } } if (spaceAlreadyUtilized) { if (DoubleUtil.GreaterThanOrClose(totalAvailableWidth, usedSpace)) { return 0.0; } else { return Math.Min(usedSpace - totalAvailableWidth, takeAwayWidth); } } else { double unusedSpace = totalAvailableWidth - usedSpace; if (DoubleUtil.GreaterThan(unusedSpace, 0.0)) { takeAwayWidth = Math.Max(0.0, takeAwayWidth - unusedSpace); } return takeAwayWidth; } } /// /// Helper method which tries to take away width from unused space /// private double TakeAwayWidthFromUnusedSpace(bool spaceAlreadyUtilized, double takeAwayWidth) { double totalAvailableWidth = DataGridOwner.GetViewportWidthForColumns(); if (DoubleUtil.GreaterThan(totalAvailableWidth, 0.0)) { return TakeAwayWidthFromUnusedSpace(spaceAlreadyUtilized, takeAwayWidth, totalAvailableWidth); } return takeAwayWidth; } /// /// Method which tries to take away the given amount of width from columns /// except the ignored column /// private double TakeAwayWidthFromColumns(EGC.DataGridColumn ignoredColumn, double takeAwayWidth, bool widthAlreadyUtilized) { double totalAvailableWidth = DataGridOwner.GetViewportWidthForColumns(); return TakeAwayWidthFromColumns(ignoredColumn, takeAwayWidth, widthAlreadyUtilized, totalAvailableWidth); } /// /// Method which tries to take away the given amount of width from columns /// except the ignored column /// private double TakeAwayWidthFromColumns(EGC.DataGridColumn ignoredColumn, double takeAwayWidth, bool widthAlreadyUtilized, double totalAvailableWidth) { takeAwayWidth = TakeAwayWidthFromUnusedSpace(widthAlreadyUtilized, takeAwayWidth, totalAvailableWidth); takeAwayWidth = TakeAwayWidthFromStarColumns(ignoredColumn, takeAwayWidth); takeAwayWidth = TakeAwayWidthFromNonStarColumns(ignoredColumn, takeAwayWidth); return takeAwayWidth; } /// /// Method which tries to take away the given amount of width form /// the star columns /// private double TakeAwayWidthFromStarColumns(EGC.DataGridColumn ignoredColumn, double takeAwayWidth) { if (DoubleUtil.GreaterThan(takeAwayWidth, 0.0)) { double sumOfStarDisplayWidths = 0.0; double sumOfStarMinWidths = 0.0; foreach (EGC.DataGridColumn column in this) { EGC.DataGridLength width = column.Width; if (width.IsStar && column.IsVisible) { if (column == ignoredColumn) { sumOfStarDisplayWidths += takeAwayWidth; } sumOfStarDisplayWidths += width.DisplayValue; sumOfStarMinWidths += column.MinWidth; } } double expectedStarSpace = sumOfStarDisplayWidths - takeAwayWidth; double usedStarSpace = ComputeStarColumnWidths(Math.Max(expectedStarSpace, sumOfStarMinWidths)); takeAwayWidth = Math.Max(usedStarSpace - expectedStarSpace, 0.0); } return takeAwayWidth; } /// /// Method which tries to take away the given amount of width /// among all non star columns except the ignored column /// private double TakeAwayWidthFromNonStarColumns(EGC.DataGridColumn ignoredColumn, double takeAwayWidth) { while (DoubleUtil.GreaterThan(takeAwayWidth, 0.0)) { int countOfParticipatingColumns = 0; double minExcessWidth = FindMinimumExcessWidthOfNonStarColumns( ignoredColumn, out countOfParticipatingColumns); if (countOfParticipatingColumns == 0) { break; } double minTotalExcessWidth = minExcessWidth * countOfParticipatingColumns; if (DoubleUtil.GreaterThanOrClose(minTotalExcessWidth, takeAwayWidth)) { minExcessWidth = takeAwayWidth / countOfParticipatingColumns; takeAwayWidth = 0.0; } else { takeAwayWidth -= minTotalExcessWidth; } TakeAwayWidthFromEveryNonStarColumn(ignoredColumn, minExcessWidth); } return takeAwayWidth; } /// /// Helper method which finds the minimum non-zero difference between displayvalue and minwidth /// among all non star columns /// private double FindMinimumExcessWidthOfNonStarColumns( EGC.DataGridColumn ignoredColumn, out int countOfParticipatingColumns) { double minExcessWidth = Double.PositiveInfinity; countOfParticipatingColumns = 0; foreach (EGC.DataGridColumn column in this) { if (ignoredColumn == column || !column.IsVisible) { continue; } EGC.DataGridLength width = column.Width; if (width.IsStar) { continue; } double minWidth = column.MinWidth; if (DoubleUtil.GreaterThan(width.DisplayValue, minWidth)) { countOfParticipatingColumns++; double excessWidth = width.DisplayValue - minWidth; if (DoubleUtil.LessThan(excessWidth, minExcessWidth)) { minExcessWidth = excessWidth; } } } return minExcessWidth; } /// /// Helper method which takes away the given amount of width from /// every non star column whose display value is greater than its minwidth /// private void TakeAwayWidthFromEveryNonStarColumn( EGC.DataGridColumn ignoredColumn, double perColumnTakeAwayWidth) { foreach (EGC.DataGridColumn column in this) { if (ignoredColumn == column || !column.IsVisible) { continue; } EGC.DataGridLength width = column.Width; if (width.IsStar) { continue; } if (DoubleUtil.GreaterThan(width.DisplayValue, column.MinWidth)) { column.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, width.DesiredValue, width.DisplayValue - perColumnTakeAwayWidth)); } } } #endregion #region Column Virtualization /// /// Property which indicates that the RealizedColumnsBlockList /// is dirty and needs to be rebuilt for non-column virtualized rows /// internal bool RebuildRealizedColumnsBlockListForNonVirtualizedRows { get; set; } /// /// List of realized column index blocks for non-column virtualized rows /// internal List RealizedColumnsBlockListForNonVirtualizedRows { get { return _realizedColumnsBlockListForNonVirtualizedRows; } set { _realizedColumnsBlockListForNonVirtualizedRows = value; // Notify other rows and column header row to // remeasure their child panel's in order to be // in sync with latest column realization computations EGC.DataGrid dataGrid = DataGridOwner; dataGrid.NotifyPropertyChanged( dataGrid, "RealizedColumnsBlockListForNonVirtualizedRows", new DependencyPropertyChangedEventArgs(), NotificationTarget.CellsPresenter | NotificationTarget.ColumnHeadersPresenter); } } /// /// List of realized column display index blocks for non-column virtualized rows /// internal List RealizedColumnsDisplayIndexBlockListForNonVirtualizedRows { get; set; } /// /// Property which indicates that the RealizedColumnsBlockList /// is dirty and needs to be rebuilt for column virtualized rows /// internal bool RebuildRealizedColumnsBlockListForVirtualizedRows { get; set; } /// /// List of realized column index blocks for column virtualized rows /// internal List RealizedColumnsBlockListForVirtualizedRows { get { return _realizedColumnsBlockListForVirtualizedRows; } set { _realizedColumnsBlockListForVirtualizedRows = value; // Notify other rows and column header row to // remeasure their child panel's in order to be // in sync with latest column realization computations EGC.DataGrid dataGrid = DataGridOwner; dataGrid.NotifyPropertyChanged( dataGrid, "RealizedColumnsBlockListForVirtualizedRows", new DependencyPropertyChangedEventArgs(), NotificationTarget.CellsPresenter | NotificationTarget.ColumnHeadersPresenter); } } /// /// List of realized column display index blocks for column virtualized rows /// internal List RealizedColumnsDisplayIndexBlockListForVirtualizedRows { get; set; } /// /// Called when properties which affect the realized columns namely /// Column Width, FrozenColumnCount, DisplayIndex etc. are changed. /// internal void InvalidateColumnRealization(bool invalidateForNonVirtualizedRows) { RebuildRealizedColumnsBlockListForVirtualizedRows = true; if (invalidateForNonVirtualizedRows) { RebuildRealizedColumnsBlockListForNonVirtualizedRows = true; } } #endregion #region Hidden Columns /// /// Helper property to return the display index of first visible column /// internal int FirstVisibleDisplayIndex { get { for (int i = 0, count = this.Count; i < count; i++) { EGC.DataGridColumn column = ColumnFromDisplayIndex(i); if (column.IsVisible) { return i; } } return -1; } } /// /// Helper property to return the display index of last visible column /// internal int LastVisibleDisplayIndex { get { for (int i = this.Count - 1; i >= 0; i--) { EGC.DataGridColumn column = ColumnFromDisplayIndex(i); if (column.IsVisible) { return i; } } return -1; } } #endregion #region Data private EGC.DataGrid _dataGridOwner; private bool _isUpdatingDisplayIndex; // true if we're in the middle of updating the display index of each column. private List _displayIndexMap; // maps a DisplayIndex to an index in the _columns collection. private bool _displayIndexMapInitialized; // Flag is used to delay the validation of DisplayIndex until the first measure private bool _isClearingDisplayIndex; // Flag indicating that we're currently clearing the display index. We should not coerce default display index's during this time. private bool _columnWidthsComputationPending; // Flag indicating whether the columns width computaion operation is pending private Dictionary _originalWidthsForResize; // Dictionary to hold the original widths of columns for resize operation private double? _averageColumnWidth = null; // average width of all visible columns private List _realizedColumnsBlockListForNonVirtualizedRows = null; // Realized columns for non-virtualized rows private List _realizedColumnsBlockListForVirtualizedRows = null; // Realized columns for virtualized rows #endregion } }