// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using Avalonia.Controls.Utils; using Avalonia.Data; using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Utilities; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Reflection; namespace Avalonia.Controls { public partial class DataGrid { protected virtual void OnColumnDisplayIndexChanged(DataGridColumnEventArgs e) { ColumnDisplayIndexChanged?.Invoke(this, e); } protected internal virtual void OnColumnReordered(DataGridColumnEventArgs e) { EnsureVerticalGridLines(); ColumnReordered?.Invoke(this, e); } protected internal virtual void OnColumnReordering(DataGridColumnReorderingEventArgs e) { ColumnReordering?.Invoke(this, e); } protected internal virtual void OnColumnSorting(DataGridColumnEventArgs e) { Sorting?.Invoke(this, e); } /// /// Adjusts the widths of all columns with DisplayIndex >= displayIndex such that the total /// width is adjusted by the given amount, if possible. If the total desired adjustment amount /// could not be met, the remaining amount of adjustment is returned. /// /// Starting column DisplayIndex. /// Adjustment amount (positive for increase, negative for decrease). /// Whether or not this adjustment was initiated by a user action. /// The remaining amount of adjustment. internal double AdjustColumnWidths(int displayIndex, double amount, bool userInitiated) { if (!MathUtilities.IsZero(amount)) { if (amount < 0) { amount = DecreaseColumnWidths(displayIndex, amount, userInitiated); } else { amount = IncreaseColumnWidths(displayIndex, amount, userInitiated); } } return amount; } /// /// Grows an auto-column's width to the desired width. /// /// Auto-column to adjust. /// The new desired width of the column. internal void AutoSizeColumn(DataGridColumn column, double desiredWidth) { Debug.Assert(column.Width.IsAuto || column.Width.IsSizeToCells || column.Width.IsSizeToHeader || (!UsesStarSizing && column.Width.IsStar)); // If we're using star sizing and this is the first time we've measured this particular auto-column, // we want to allow all rows to get measured before we setup the star widths. We won't know the final // desired value of the column until all rows have been measured. Because of this, we wait until // an Arrange occurs before we adjust star widths. if (UsesStarSizing && !column.IsInitialDesiredWidthDetermined) { AutoSizingColumns = true; } // Update the column's DesiredValue if it needs to grow to fit the new desired value if (desiredWidth > column.Width.DesiredValue || double.IsNaN(column.Width.DesiredValue)) { // If this auto-growth occurs after the column's initial desired width has been determined, // then the growth should act like a resize (squish columns to the right). Otherwise, if // this column is newly added, we'll just set its display value directly. if (UsesStarSizing && column.IsInitialDesiredWidthDetermined) { column.Resize(column.Width.Value, column.Width.UnitType, desiredWidth, desiredWidth, false); } else { column.SetWidthInternalNoCallback(new DataGridLength(column.Width.Value, column.Width.UnitType, desiredWidth, desiredWidth)); OnColumnWidthChanged(column); } } } internal bool ColumnRequiresRightGridLine(DataGridColumn dataGridColumn, bool includeLastRightGridLineWhenPresent) { return (GridLinesVisibility == DataGridGridLinesVisibility.Vertical || GridLinesVisibility == DataGridGridLinesVisibility.All) && VerticalGridLinesBrush != null && (dataGridColumn != ColumnsInternal.LastVisibleColumn || (includeLastRightGridLineWhenPresent && ColumnsInternal.FillerColumn.IsActive)); } internal DataGridColumnCollection CreateColumnsInstance() { return new DataGridColumnCollection(this); } /// /// Decreases the widths of all columns with DisplayIndex >= displayIndex such that the total /// width is decreased by the given amount, if possible. If the total desired adjustment amount /// could not be met, the remaining amount of adjustment is returned. /// /// Starting column DisplayIndex. /// Amount to decrease (in pixels). /// Whether or not this adjustment was initiated by a user action. /// The remaining amount of adjustment. internal double DecreaseColumnWidths(int displayIndex, double amount, bool userInitiated) { // 1. Take space from non-star columns with widths larger than desired widths (left to right). amount = DecreaseNonStarColumnWidths(displayIndex, c => c.Width.DesiredValue, amount, false, false); // 2. Take space from star columns until they reach their min. amount = AdjustStarColumnWidths(displayIndex, amount, userInitiated); // 3. Take space from non-star columns that have already been initialized, until they reach their min (right to left). amount = DecreaseNonStarColumnWidths(displayIndex, c => c.ActualMinWidth, amount, true, false); // 4. Take space from all non-star columns until they reach their min, even if they are new (right to left). amount = DecreaseNonStarColumnWidths(displayIndex, c => c.ActualMinWidth, amount, true, true); return amount; } internal bool GetColumnReadOnlyState(DataGridColumn dataGridColumn, bool isReadOnly) { Debug.Assert(dataGridColumn != null); if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn && dataGridBoundColumn.Binding is BindingBase binding) { var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString(); if (string.IsNullOrWhiteSpace(path)) { return true; } else { return DataConnection.GetPropertyIsReadOnly(path) || isReadOnly; } } return isReadOnly; } // Returns the column's width internal static double GetEdgedColumnWidth(DataGridColumn dataGridColumn) { Debug.Assert(dataGridColumn != null); return dataGridColumn.ActualWidth; } /// /// Increases the widths of all columns with DisplayIndex >= displayIndex such that the total /// width is increased by the given amount, if possible. If the total desired adjustment amount /// could not be met, the remaining amount of adjustment is returned. /// /// Starting column DisplayIndex. /// Amount of increase (in pixels). /// Whether or not this adjustment was initiated by a user action. /// The remaining amount of adjustment. internal double IncreaseColumnWidths(int displayIndex, double amount, bool userInitiated) { // 1. Give space to non-star columns that are smaller than their desired widths (left to right). amount = IncreaseNonStarColumnWidths(displayIndex, c => c.Width.DesiredValue, amount, false, false); // 2. Give space to star columns until they reach their max. amount = AdjustStarColumnWidths(displayIndex, amount, userInitiated); // 3. Give space to non-star columns that have already been initialized, until they reach their max (right to left). amount = IncreaseNonStarColumnWidths(displayIndex, c => c.ActualMaxWidth, amount, true, false); // 4. Give space to all non-star columns until they reach their max, even if they are new (right to left). amount = IncreaseNonStarColumnWidths(displayIndex, c => c.ActualMaxWidth, amount, true, false); return amount; } internal void OnClearingColumns() { // Rows need to be cleared first. There cannot be rows without also having columns. ClearRows(false); // Removing all the column header cells RemoveDisplayedColumnHeaders(); _horizontalOffset = _negHorizontalOffset = 0; if (_hScrollBar != null && _hScrollBar.IsVisible) // { _hScrollBar.Value = 0; } } /// /// Invalidates the widths of all columns because the resizing behavior of an individual column has changed. /// /// Column with CanUserResize property that has changed. internal void OnColumnCanUserResizeChanged(DataGridColumn column) { if (column.IsVisible) { EnsureHorizontalLayout(); } } internal void OnColumnCollectionChanged_PostNotification(bool columnsGrew) { if (columnsGrew && CurrentColumnIndex == -1) { MakeFirstDisplayedCellCurrentCell(); } if (_autoGeneratingColumnOperationCount == 0) { EnsureRowsPresenterVisibility(); InvalidateRowHeightEstimate(); } } internal void OnColumnCollectionChanged_PreNotification(bool columnsGrew) { // dataGridColumn==null means the collection was refreshed. if (columnsGrew && _autoGeneratingColumnOperationCount == 0 && ColumnsItemsInternal.Count == 1) { RefreshRows(false /*recycleRows*/, true /*clearRows*/); } else { InvalidateMeasure(); } } internal void OnColumnDisplayIndexChanged(DataGridColumn dataGridColumn) { Debug.Assert(dataGridColumn != null); DataGridColumnEventArgs e = new DataGridColumnEventArgs(dataGridColumn); // Call protected method to raise event if (dataGridColumn != ColumnsInternal.RowGroupSpacerColumn) { OnColumnDisplayIndexChanged(e); } } internal void OnColumnDisplayIndexChanged_PostNotification() { // Notifications for adjusted display indexes. FlushDisplayIndexChanged(true /*raiseEvent*/); // Our displayed columns may have changed so recompute them UpdateDisplayedColumns(); // Invalidate layout CorrectColumnFrozenStates(); EnsureHorizontalLayout(); } internal void OnColumnDisplayIndexChanging(DataGridColumn targetColumn, int newDisplayIndex) { Debug.Assert(targetColumn != null); Debug.Assert(newDisplayIndex != targetColumn.DisplayIndexWithFiller); if (InDisplayIndexAdjustments) { // We are within columns display indexes adjustments. We do not allow changing display indexes while adjusting them. throw DataGridError.DataGrid.CannotChangeColumnCollectionWhileAdjustingDisplayIndexes(); } try { InDisplayIndexAdjustments = true; bool trackChange = targetColumn != ColumnsInternal.RowGroupSpacerColumn; DataGridColumn column; // Move is legal - let's adjust the affected display indexes. if (newDisplayIndex < targetColumn.DisplayIndexWithFiller) { // DisplayIndex decreases. All columns with newDisplayIndex <= DisplayIndex < targetColumn.DisplayIndex // get their DisplayIndex incremented. for (int i = newDisplayIndex; i < targetColumn.DisplayIndexWithFiller; i++) { column = ColumnsInternal.GetColumnAtDisplayIndex(i); column.DisplayIndexWithFiller = column.DisplayIndexWithFiller + 1; if (trackChange) { column.DisplayIndexHasChanged = true; // OnColumnDisplayIndexChanged needs to be raised later on } } } else { // DisplayIndex increases. All columns with targetColumn.DisplayIndex < DisplayIndex <= newDisplayIndex // get their DisplayIndex decremented. for (int i = newDisplayIndex; i > targetColumn.DisplayIndexWithFiller; i--) { column = ColumnsInternal.GetColumnAtDisplayIndex(i); column.DisplayIndexWithFiller = column.DisplayIndexWithFiller - 1; if (trackChange) { column.DisplayIndexHasChanged = true; // OnColumnDisplayIndexChanged needs to be raised later on } } } // Now let's actually change the order of the DisplayIndexMap if (targetColumn.DisplayIndexWithFiller != -1) { ColumnsInternal.DisplayIndexMap.Remove(targetColumn.Index); } ColumnsInternal.DisplayIndexMap.Insert(newDisplayIndex, targetColumn.Index); } finally { InDisplayIndexAdjustments = false; } // Note that displayIndex of moved column is updated by caller. } internal void OnColumnBindingChanged(DataGridBoundColumn column) { // Update Binding in Displayed rows by regenerating the affected elements if (_rowsPresenter != null) { foreach (DataGridRow row in GetAllRows()) { PopulateCellContent(false /*isCellEdited*/, column, row, row.Cells[column.Index]); } } } /// /// Adjusts the specified column's width according to its new maximum value. /// /// The column to adjust. /// The old ActualMaxWidth of the column. internal void OnColumnMaxWidthChanged(DataGridColumn column, double oldValue) { Debug.Assert(column != null); if (column.IsVisible && oldValue != column.ActualMaxWidth) { if (column.ActualMaxWidth < column.Width.DisplayValue) { // If the maximum width has caused the column to decrease in size, try first to resize // the columns to the right to make up for the difference in width, but don't limit the column's // final display value to how much they could be resized. AdjustColumnWidths(column.DisplayIndex + 1, column.Width.DisplayValue - column.ActualMaxWidth, false); column.SetWidthDisplayValue(column.ActualMaxWidth); } else if (column.Width.DisplayValue == oldValue && column.Width.DesiredValue > column.Width.DisplayValue) { // If the column was previously limited by its maximum value but has more room now, // attempt to resize the column to its desired width. column.Resize(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue, false); } OnColumnWidthChanged(column); } } /// /// Adjusts the specified column's width according to its new minimum value. /// /// The column to adjust. /// The old ActualMinWidth of the column. internal void OnColumnMinWidthChanged(DataGridColumn column, double oldValue) { Debug.Assert(column != null); if (column.IsVisible && oldValue != column.ActualMinWidth) { if (column.ActualMinWidth > column.Width.DisplayValue) { // If the minimum width has caused the column to increase in size, try first to resize // the columns to the right to make up for the difference in width, but don't limit the column's // final display value to how much they could be resized. AdjustColumnWidths(column.DisplayIndex + 1, column.Width.DisplayValue - column.ActualMinWidth, false); column.SetWidthDisplayValue(column.ActualMinWidth); } else if (column.Width.DisplayValue == oldValue && column.Width.DesiredValue < column.Width.DisplayValue) { // If the column was previously limited by its minimum value but but can be smaller now, // attempt to resize the column to its desired width. column.Resize(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue, false); } OnColumnWidthChanged(column); } } internal void OnColumnReadOnlyStateChanging(DataGridColumn dataGridColumn, bool isReadOnly) { Debug.Assert(dataGridColumn != null); if (isReadOnly && CurrentColumnIndex == dataGridColumn.Index) { // Edited column becomes read-only. Exit editing mode. if (!EndCellEdit(DataGridEditAction.Commit, true /*exitEditingMode*/, ContainsFocus /*keepFocus*/, true /*raiseEvents*/)) { EndCellEdit(DataGridEditAction.Cancel, true /*exitEditingMode*/, ContainsFocus /*keepFocus*/, false /*raiseEvents*/); } } } internal void OnColumnVisibleStateChanged(DataGridColumn updatedColumn) { Debug.Assert(updatedColumn != null); CorrectColumnFrozenStates(); UpdateDisplayedColumns(); EnsureRowsPresenterVisibility(); EnsureHorizontalLayout(); InvalidateColumnHeadersMeasure(); if (updatedColumn.IsVisible && ColumnsInternal.VisibleColumnCount == 1 && CurrentColumnIndex == -1) { Debug.Assert(SelectedIndex == DataConnection.IndexOf(SelectedItem)); if (SelectedIndex != -1) { SetAndSelectCurrentCell(updatedColumn.Index, SelectedIndex, true /*forceCurrentCellSelection*/); } else { MakeFirstDisplayedCellCurrentCell(); } } // We need to explicitly collapse the cells of the invisible column because layout only goes through // visible ones if (!updatedColumn.IsVisible) { foreach (DataGridRow row in GetAllRows()) { row.Cells[updatedColumn.Index].IsVisible = false; } } } internal void OnColumnVisibleStateChanging(DataGridColumn targetColumn) { Debug.Assert(targetColumn != null); if (targetColumn.IsVisible && CurrentColumn == targetColumn) { // Column of the current cell is made invisible. Trying to move the current cell to a neighbor column. May throw an exception. DataGridColumn dataGridColumn = ColumnsInternal.GetNextVisibleColumn(targetColumn); if (dataGridColumn == null) { dataGridColumn = ColumnsInternal.GetPreviousVisibleNonFillerColumn(targetColumn); } if (dataGridColumn == null) { SetCurrentCellCore(-1, -1); } else { SetCurrentCellCore(dataGridColumn.Index, CurrentSlot); } } } internal void OnColumnWidthChanged(DataGridColumn updatedColumn) { Debug.Assert(updatedColumn != null); if (updatedColumn.IsVisible) { EnsureHorizontalLayout(); } } internal void OnFillerColumnWidthNeeded(double finalWidth) { DataGridFillerColumn fillerColumn = ColumnsInternal.FillerColumn; double totalColumnsWidth = ColumnsInternal.VisibleEdgedColumnsWidth; if (finalWidth > totalColumnsWidth) { fillerColumn.FillerWidth = finalWidth - totalColumnsWidth; } else { fillerColumn.FillerWidth = 0; } } internal void OnInsertedColumn_PostNotification(DataGridCellCoordinates newCurrentCellCoordinates, int newDisplayIndex) { // Update current cell if needed if (newCurrentCellCoordinates.ColumnIndex != -1) { Debug.Assert(CurrentColumnIndex == -1); SetAndSelectCurrentCell(newCurrentCellCoordinates.ColumnIndex, newCurrentCellCoordinates.Slot, ColumnsInternal.VisibleColumnCount == 1 /*forceCurrentCellSelection*/); if (newDisplayIndex < FrozenColumnCountWithFiller) { CorrectColumnFrozenStates(); } } } internal void OnInsertedColumn_PreNotification(DataGridColumn insertedColumn) { // Fix the Index of all following columns CorrectColumnIndexesAfterInsertion(insertedColumn, 1); Debug.Assert(insertedColumn.Index >= 0); Debug.Assert(insertedColumn.Index < ColumnsItemsInternal.Count); Debug.Assert(insertedColumn.OwningGrid == this); CorrectColumnDisplayIndexesAfterInsertion(insertedColumn); InsertDisplayedColumnHeader(insertedColumn); // Insert the missing data cells if (SlotCount > 0) { int newColumnCount = ColumnsItemsInternal.Count; foreach (DataGridRow row in GetAllRows()) { if (row.Cells.Count < newColumnCount) { AddNewCellPrivate(row, insertedColumn); } } } if (insertedColumn.IsVisible) { EnsureHorizontalLayout(); } if (insertedColumn is DataGridBoundColumn boundColumn && !boundColumn.IsAutoGenerated) { boundColumn.SetHeaderFromBinding(); } } internal DataGridCellCoordinates OnInsertingColumn(int columnIndexInserted, DataGridColumn insertColumn) { DataGridCellCoordinates newCurrentCellCoordinates; Debug.Assert(insertColumn != null); if (insertColumn.OwningGrid != null && insertColumn != ColumnsInternal.RowGroupSpacerColumn) { throw DataGridError.DataGrid.ColumnCannotBeReassignedToDifferentDataGrid(); } // Reset current cell if there is one, no matter the relative position of the columns involved if (CurrentColumnIndex != -1) { _temporarilyResetCurrentCell = true; newCurrentCellCoordinates = new DataGridCellCoordinates(columnIndexInserted <= CurrentColumnIndex ? CurrentColumnIndex + 1 : CurrentColumnIndex, CurrentSlot); ResetCurrentCellCore(); } else { newCurrentCellCoordinates = new DataGridCellCoordinates(-1, -1); } return newCurrentCellCoordinates; } internal void OnRemovedColumn_PostNotification(DataGridCellCoordinates newCurrentCellCoordinates) { // Update current cell if needed if (newCurrentCellCoordinates.ColumnIndex != -1) { Debug.Assert(CurrentColumnIndex == -1); SetAndSelectCurrentCell(newCurrentCellCoordinates.ColumnIndex, newCurrentCellCoordinates.Slot, false /*forceCurrentCellSelection*/); } } internal void OnRemovedColumn_PreNotification(DataGridColumn removedColumn) { Debug.Assert(removedColumn.Index >= 0); Debug.Assert(removedColumn.OwningGrid == null); // Intentionally keep the DisplayIndex intact after detaching the column. CorrectColumnIndexesAfterDeletion(removedColumn); CorrectColumnDisplayIndexesAfterDeletion(removedColumn); // If the detached column was frozen, a new column needs to take its place if (removedColumn.IsFrozen) { removedColumn.IsFrozen = false; CorrectColumnFrozenStates(); } UpdateDisplayedColumns(); // Fix the existing rows by removing cells at correct index int newColumnCount = ColumnsItemsInternal.Count; if (_rowsPresenter != null) { foreach (DataGridRow row in GetAllRows()) { if (row.Cells.Count > newColumnCount) { row.Cells.RemoveAt(removedColumn.Index); } } _rowsPresenter.InvalidateArrange(); } RemoveDisplayedColumnHeader(removedColumn); } internal DataGridCellCoordinates OnRemovingColumn(DataGridColumn dataGridColumn) { Debug.Assert(dataGridColumn != null); Debug.Assert(dataGridColumn.Index >= 0 && dataGridColumn.Index < ColumnsItemsInternal.Count); DataGridCellCoordinates newCurrentCellCoordinates; _temporarilyResetCurrentCell = false; int columnIndex = dataGridColumn.Index; // Reset the current cell's address if there is one. if (CurrentColumnIndex != -1) { int newCurrentColumnIndex = CurrentColumnIndex; if (columnIndex == newCurrentColumnIndex) { DataGridColumn dataGridColumnNext = ColumnsInternal.GetNextVisibleColumn(ColumnsItemsInternal[columnIndex]); if (dataGridColumnNext != null) { if (dataGridColumnNext.Index > columnIndex) { newCurrentColumnIndex = dataGridColumnNext.Index - 1; } else { newCurrentColumnIndex = dataGridColumnNext.Index; } } else { DataGridColumn dataGridColumnPrevious = ColumnsInternal.GetPreviousVisibleNonFillerColumn(ColumnsItemsInternal[columnIndex]); if (dataGridColumnPrevious != null) { if (dataGridColumnPrevious.Index > columnIndex) { newCurrentColumnIndex = dataGridColumnPrevious.Index - 1; } else { newCurrentColumnIndex = dataGridColumnPrevious.Index; } } else { newCurrentColumnIndex = -1; } } } else if (columnIndex < newCurrentColumnIndex) { newCurrentColumnIndex--; } newCurrentCellCoordinates = new DataGridCellCoordinates(newCurrentColumnIndex, (newCurrentColumnIndex == -1) ? -1 : CurrentSlot); if (columnIndex == CurrentColumnIndex) { // If the commit fails, force a cancel edit if (!CommitEdit(DataGridEditingUnit.Row, false /*exitEditingMode*/)) { CancelEdit(DataGridEditingUnit.Row, false /*raiseEvents*/); } bool success = SetCurrentCellCore(-1, -1); Debug.Assert(success); } else { // Underlying data of deleted column is gone. It cannot be accessed anymore. // Do not end editing mode so that CellValidation doesn't get raised, since that event needs the current formatted value. _temporarilyResetCurrentCell = true; bool success = SetCurrentCellCore(-1, -1); Debug.Assert(success); } } else { newCurrentCellCoordinates = new DataGridCellCoordinates(-1, -1); } // If the last column is removed, delete all the rows first. if (ColumnsItemsInternal.Count == 1) { ClearRows(false); } // Is deleted column scrolled off screen? if (dataGridColumn.IsVisible && !dataGridColumn.IsFrozen && DisplayData.FirstDisplayedScrollingCol >= 0) { // Deleted column is part of scrolling columns. if (DisplayData.FirstDisplayedScrollingCol == dataGridColumn.Index) { // Deleted column is first scrolling column _horizontalOffset -= _negHorizontalOffset; _negHorizontalOffset = 0; } else if (!ColumnsInternal.DisplayInOrder(DisplayData.FirstDisplayedScrollingCol, dataGridColumn.Index)) { // Deleted column is displayed before first scrolling column Debug.Assert(_horizontalOffset >= GetEdgedColumnWidth(dataGridColumn)); _horizontalOffset -= GetEdgedColumnWidth(dataGridColumn); } if (_hScrollBar != null && _hScrollBar.IsVisible) // { _hScrollBar.Value = _horizontalOffset; } } return newCurrentCellCoordinates; } /// /// Called when a column property changes, and its cells need to /// adjust that that column change. /// internal void RefreshColumnElements(DataGridColumn dataGridColumn, string propertyName) { Debug.Assert(dataGridColumn != null); // Take care of the non-displayed loaded rows for (int index = 0; index < _loadedRows.Count;) { DataGridRow dataGridRow = _loadedRows[index]; Debug.Assert(dataGridRow != null); if (!IsSlotVisible(dataGridRow.Slot)) { RefreshCellElement(dataGridColumn, dataGridRow, propertyName); } index++; } // Take care of the displayed rows if (_rowsPresenter != null) { foreach (DataGridRow row in GetAllRows()) { RefreshCellElement(dataGridColumn, row, propertyName); } // This update could change layout so we need to update our estimate and invalidate InvalidateRowHeightEstimate(); InvalidateMeasure(); } } /// /// Adjusts the widths of all star columns with DisplayIndex >= displayIndex such that the total /// width is adjusted by the given amount, if possible. If the total desired adjustment amount /// could not be met, the remaining amount of adjustment is returned. /// /// Starting column DisplayIndex. /// Adjustment amount (positive for increase, negative for decrease). /// Whether or not this adjustment was initiated by a user action. /// The remaining amount of adjustment. private double AdjustStarColumnWidths(int displayIndex, double adjustment, bool userInitiated) { double remainingAdjustment = adjustment; if (MathUtilities.IsZero(remainingAdjustment)) { return remainingAdjustment; } bool increase = remainingAdjustment > 0; // Make an initial pass through the star columns to total up some values. bool scaleStarWeights = false; double totalStarColumnsWidth = 0; double totalStarColumnsWidthLimit = 0; double totalStarWeights = 0; List starColumns = new List(); foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns(c => c.Width.IsStar && c.IsVisible && (c.ActualCanUserResize || !userInitiated))) { if (column.DisplayIndex < displayIndex) { scaleStarWeights = true; continue; } starColumns.Add(column); totalStarWeights += column.Width.Value; totalStarColumnsWidth += column.Width.DisplayValue; totalStarColumnsWidthLimit += increase ? column.ActualMaxWidth : column.ActualMinWidth; } // Set the new desired widths according to how much all the star columns can be adjusted without any // of them being limited by their minimum or maximum widths (as that would distort their ratios). double adjustmentLimit = totalStarColumnsWidthLimit - totalStarColumnsWidth; adjustmentLimit = increase ? Math.Min(adjustmentLimit, adjustment) : Math.Max(adjustmentLimit, adjustment); foreach (DataGridColumn starColumn in starColumns) { starColumn.SetWidthDesiredValue((totalStarColumnsWidth + adjustmentLimit) * starColumn.Width.Value / totalStarWeights); } // Adjust the star column widths first towards their desired values, and then towards their limits. remainingAdjustment = AdjustStarColumnWidths(displayIndex, remainingAdjustment, userInitiated, c => c.Width.DesiredValue); remainingAdjustment = AdjustStarColumnWidths(displayIndex, remainingAdjustment, userInitiated, c => increase ? c.ActualMaxWidth : c.ActualMinWidth); // Set the new star value weights according to how much the total column widths have changed. // Only do this if there were other star columns to the left, though. If there weren't any then that means // all the star columns were adjusted at the same time, and therefore, their ratios have not changed. if (scaleStarWeights) { double starRatio = (totalStarColumnsWidth + adjustment - remainingAdjustment) / totalStarColumnsWidth; foreach (DataGridColumn starColumn in starColumns) { starColumn.SetWidthStarValue(Math.Min(double.MaxValue, starRatio * starColumn.Width.Value)); } } return remainingAdjustment; } /// /// Adjusts the widths of all star columns with DisplayIndex >= displayIndex such that the total /// width is adjusted by the given amount, if possible. If the total desired adjustment amount /// could not be met, the remaining amount of adjustment is returned. The columns will stop adjusting /// once they hit their target widths. /// /// Starting column DisplayIndex. /// Adjustment amount (positive for increase, negative for decrease). /// Whether or not this adjustment was initiated by a user action. /// The target width of the column. /// The remaining amount of adjustment. private double AdjustStarColumnWidths(int displayIndex, double remainingAdjustment, bool userInitiated, Func targetWidth) { if (MathUtilities.IsZero(remainingAdjustment)) { return remainingAdjustment; } bool increase = remainingAdjustment > 0; double totalStarWeights = 0; double totalStarColumnsWidth = 0; // Order the star columns according to which one will hit their target width (or min/max limit) first. // Each KeyValuePair represents a column (as the key) and an ordering factor (as the value). The ordering factor // is computed based on the distance from each column's current display width to its target width. Because each column // could have different star ratios, though, this distance is then adjusted according to its star value. A column with // a larger star value, for example, will change size more rapidly than a column with a lower star value. List> starColumnPairs = new List>(); foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns( c => c.Width.IsStar && c.DisplayIndex >= displayIndex && c.IsVisible && c.Width.Value > 0 && (c.ActualCanUserResize || !userInitiated))) { int insertIndex = 0; double distanceToTarget = Math.Min(column.ActualMaxWidth, Math.Max(targetWidth(column), column.ActualMinWidth)) - column.Width.DisplayValue; double factor = (increase ? Math.Max(0, distanceToTarget) : Math.Min(0, distanceToTarget)) / column.Width.Value; foreach (KeyValuePair starColumnPair in starColumnPairs) { if (increase ? factor <= starColumnPair.Value : factor >= starColumnPair.Value) { break; } insertIndex++; } starColumnPairs.Insert(insertIndex, new KeyValuePair(column, factor)); totalStarWeights += column.Width.Value; totalStarColumnsWidth += column.Width.DisplayValue; } // Adjust the column widths one at a time until they either hit their individual target width // or the total remaining amount to adjust has been depleted. foreach (KeyValuePair starColumnPair in starColumnPairs) { double distanceToTarget = starColumnPair.Value * starColumnPair.Key.Width.Value; double distanceAvailable = (starColumnPair.Key.Width.Value * remainingAdjustment) / totalStarWeights; double adjustment = increase ? Math.Min(distanceToTarget, distanceAvailable) : Math.Max(distanceToTarget, distanceAvailable); remainingAdjustment -= adjustment; totalStarWeights -= starColumnPair.Key.Width.Value; starColumnPair.Key.SetWidthDisplayValue(Math.Max(DataGrid.DATAGRID_minimumStarColumnWidth, starColumnPair.Key.Width.DisplayValue + adjustment)); } return remainingAdjustment; } private bool ComputeDisplayedColumns() { bool invalidate = false; int numVisibleScrollingCols = 0; int visibleScrollingColumnsTmp = 0; double displayWidth = CellsWidth; double cx = 0; int firstDisplayedFrozenCol = -1; int firstDisplayedScrollingCol = DisplayData.FirstDisplayedScrollingCol; // the same problem with negative numbers: // if the width passed in is negative, then return 0 if (displayWidth <= 0 || ColumnsInternal.VisibleColumnCount == 0) { DisplayData.FirstDisplayedScrollingCol = -1; DisplayData.LastTotallyDisplayedScrollingCol = -1; return invalidate; } foreach (DataGridColumn dataGridColumn in ColumnsInternal.GetVisibleFrozenColumns()) { if (firstDisplayedFrozenCol == -1) { firstDisplayedFrozenCol = dataGridColumn.Index; } cx += GetEdgedColumnWidth(dataGridColumn); if (cx >= displayWidth) { break; } } Debug.Assert(cx <= ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth()); if (cx < displayWidth && firstDisplayedScrollingCol >= 0) { DataGridColumn dataGridColumn = ColumnsItemsInternal[firstDisplayedScrollingCol]; if (dataGridColumn.IsFrozen) { dataGridColumn = ColumnsInternal.FirstVisibleScrollingColumn; _negHorizontalOffset = 0; if (dataGridColumn == null) { DisplayData.FirstDisplayedScrollingCol = DisplayData.LastTotallyDisplayedScrollingCol = -1; return invalidate; } else { firstDisplayedScrollingCol = dataGridColumn.Index; } } cx -= _negHorizontalOffset; while (cx < displayWidth && dataGridColumn != null) { cx += GetEdgedColumnWidth(dataGridColumn); visibleScrollingColumnsTmp++; dataGridColumn = ColumnsInternal.GetNextVisibleColumn(dataGridColumn); } numVisibleScrollingCols = visibleScrollingColumnsTmp; // if we inflate the data area then we paint columns to the left of firstDisplayedScrollingCol if (cx < displayWidth) { Debug.Assert(firstDisplayedScrollingCol >= 0); //first minimize value of _negHorizontalOffset if (_negHorizontalOffset > 0) { invalidate = true; if (displayWidth - cx > _negHorizontalOffset) { cx += _negHorizontalOffset; _horizontalOffset -= _negHorizontalOffset; _negHorizontalOffset = 0; } else { _horizontalOffset -= displayWidth - cx; _negHorizontalOffset -= displayWidth - cx; cx = displayWidth; } } // second try to scroll entire columns if (cx < displayWidth && _horizontalOffset > 0) { Debug.Assert(_negHorizontalOffset == 0); dataGridColumn = ColumnsInternal.GetPreviousVisibleScrollingColumn(ColumnsItemsInternal[firstDisplayedScrollingCol]); while (dataGridColumn != null && cx + GetEdgedColumnWidth(dataGridColumn) <= displayWidth) { cx += GetEdgedColumnWidth(dataGridColumn); visibleScrollingColumnsTmp++; invalidate = true; firstDisplayedScrollingCol = dataGridColumn.Index; _horizontalOffset -= GetEdgedColumnWidth(dataGridColumn); dataGridColumn = ColumnsInternal.GetPreviousVisibleScrollingColumn(dataGridColumn); } } // third try to partially scroll in first scrolled off column if (cx < displayWidth && _horizontalOffset > 0) { Debug.Assert(_negHorizontalOffset == 0); dataGridColumn = ColumnsInternal.GetPreviousVisibleScrollingColumn(ColumnsItemsInternal[firstDisplayedScrollingCol]); Debug.Assert(dataGridColumn != null); Debug.Assert(GetEdgedColumnWidth(dataGridColumn) > displayWidth - cx); firstDisplayedScrollingCol = dataGridColumn.Index; _negHorizontalOffset = GetEdgedColumnWidth(dataGridColumn) - displayWidth + cx; _horizontalOffset -= displayWidth - cx; visibleScrollingColumnsTmp++; invalidate = true; cx = displayWidth; Debug.Assert(_negHorizontalOffset == GetNegHorizontalOffsetFromHorizontalOffset(_horizontalOffset)); } // update the number of visible columns to the new reality Debug.Assert(numVisibleScrollingCols <= visibleScrollingColumnsTmp, "the number of displayed columns can only grow"); numVisibleScrollingCols = visibleScrollingColumnsTmp; } int jumpFromFirstVisibleScrollingCol = numVisibleScrollingCols - 1; if (cx > displayWidth) { jumpFromFirstVisibleScrollingCol--; } Debug.Assert(jumpFromFirstVisibleScrollingCol >= -1); if (jumpFromFirstVisibleScrollingCol < 0) { DisplayData.LastTotallyDisplayedScrollingCol = -1; // no totally visible scrolling column at all } else { Debug.Assert(firstDisplayedScrollingCol >= 0); dataGridColumn = ColumnsItemsInternal[firstDisplayedScrollingCol]; for (int jump = 0; jump < jumpFromFirstVisibleScrollingCol; jump++) { dataGridColumn = ColumnsInternal.GetNextVisibleColumn(dataGridColumn); Debug.Assert(dataGridColumn != null); } DisplayData.LastTotallyDisplayedScrollingCol = dataGridColumn.Index; } } else { DisplayData.LastTotallyDisplayedScrollingCol = -1; } DisplayData.FirstDisplayedScrollingCol = firstDisplayedScrollingCol; return invalidate; } private int ComputeFirstVisibleScrollingColumn() { if (ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth() >= CellsWidth) { // Not enough room for scrolling columns. _negHorizontalOffset = 0; return -1; } DataGridColumn dataGridColumn = ColumnsInternal.FirstVisibleScrollingColumn; if (_horizontalOffset == 0) { _negHorizontalOffset = 0; return (dataGridColumn == null) ? -1 : dataGridColumn.Index; } double cx = 0; while (dataGridColumn != null) { cx += GetEdgedColumnWidth(dataGridColumn); if (cx > _horizontalOffset) { break; } dataGridColumn = ColumnsInternal.GetNextVisibleColumn(dataGridColumn); } if (dataGridColumn == null) { Debug.Assert(cx <= _horizontalOffset); dataGridColumn = ColumnsInternal.FirstVisibleScrollingColumn; if (dataGridColumn == null) { _negHorizontalOffset = 0; return -1; } else { if (_negHorizontalOffset != _horizontalOffset) { _negHorizontalOffset = 0; } return dataGridColumn.Index; } } else { _negHorizontalOffset = GetEdgedColumnWidth(dataGridColumn) - (cx - _horizontalOffset); return dataGridColumn.Index; } } private void CorrectColumnDisplayIndexesAfterDeletion(DataGridColumn deletedColumn) { // Column indexes have already been adjusted. // This column has already been detached and has retained its old Index and DisplayIndex Debug.Assert(deletedColumn != null); Debug.Assert(deletedColumn.OwningGrid == null); Debug.Assert(deletedColumn.Index >= 0); Debug.Assert(deletedColumn.DisplayIndexWithFiller >= 0); try { InDisplayIndexAdjustments = true; // The DisplayIndex of columns greater than the deleted column need to be decremented, // as do the DisplayIndexMap values of modified column Indexes DataGridColumn column; ColumnsInternal.DisplayIndexMap.RemoveAt(deletedColumn.DisplayIndexWithFiller); for (int displayIndex = 0; displayIndex < ColumnsInternal.DisplayIndexMap.Count; displayIndex++) { if (ColumnsInternal.DisplayIndexMap[displayIndex] > deletedColumn.Index) { ColumnsInternal.DisplayIndexMap[displayIndex]--; } if (displayIndex >= deletedColumn.DisplayIndexWithFiller) { column = ColumnsInternal.GetColumnAtDisplayIndex(displayIndex); column.DisplayIndexWithFiller = column.DisplayIndexWithFiller - 1; column.DisplayIndexHasChanged = true; // OnColumnDisplayIndexChanged needs to be raised later on } } // Now raise all the OnColumnDisplayIndexChanged events FlushDisplayIndexChanged(true /*raiseEvent*/); } finally { InDisplayIndexAdjustments = false; FlushDisplayIndexChanged(false /*raiseEvent*/); } } private void CorrectColumnDisplayIndexesAfterInsertion(DataGridColumn insertedColumn) { Debug.Assert(insertedColumn != null); Debug.Assert(insertedColumn.OwningGrid == this); if (insertedColumn.DisplayIndexWithFiller == -1 || insertedColumn.DisplayIndexWithFiller >= ColumnsItemsInternal.Count) { // Developer did not assign a DisplayIndex or picked a large number. // Choose the Index as the DisplayIndex. insertedColumn.DisplayIndexWithFiller = insertedColumn.Index; } try { InDisplayIndexAdjustments = true; // The DisplayIndex of columns greater than the inserted column need to be incremented, // as do the DisplayIndexMap values of modified column Indexes DataGridColumn column; for (int displayIndex = 0; displayIndex < ColumnsInternal.DisplayIndexMap.Count; displayIndex++) { if (ColumnsInternal.DisplayIndexMap[displayIndex] >= insertedColumn.Index) { ColumnsInternal.DisplayIndexMap[displayIndex]++; } if (displayIndex >= insertedColumn.DisplayIndexWithFiller) { column = ColumnsInternal.GetColumnAtDisplayIndex(displayIndex); column.DisplayIndexWithFiller++; column.DisplayIndexHasChanged = true; // OnColumnDisplayIndexChanged needs to be raised later on } } ColumnsInternal.DisplayIndexMap.Insert(insertedColumn.DisplayIndexWithFiller, insertedColumn.Index); // Now raise all the OnColumnDisplayIndexChanged events FlushDisplayIndexChanged(true /*raiseEvent*/); } finally { InDisplayIndexAdjustments = false; FlushDisplayIndexChanged(false /*raiseEvent*/); } } private void CorrectColumnFrozenStates() { int index = 0; double frozenColumnWidth = 0; double oldFrozenColumnWidth = 0; foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns()) { if (column.IsFrozen) { oldFrozenColumnWidth += column.ActualWidth; } column.IsFrozen = index < FrozenColumnCountWithFiller; if (column.IsFrozen) { frozenColumnWidth += column.ActualWidth; } index++; } if (HorizontalOffset > Math.Max(0, frozenColumnWidth - oldFrozenColumnWidth)) { UpdateHorizontalOffset(HorizontalOffset - frozenColumnWidth + oldFrozenColumnWidth); } else { UpdateHorizontalOffset(0); } } private void CorrectColumnIndexesAfterDeletion(DataGridColumn deletedColumn) { Debug.Assert(deletedColumn != null); for (int columnIndex = deletedColumn.Index; columnIndex < ColumnsItemsInternal.Count; columnIndex++) { ColumnsItemsInternal[columnIndex].Index = ColumnsItemsInternal[columnIndex].Index - 1; Debug.Assert(ColumnsItemsInternal[columnIndex].Index == columnIndex); } } private void CorrectColumnIndexesAfterInsertion(DataGridColumn insertedColumn, int insertionCount) { Debug.Assert(insertedColumn != null); Debug.Assert(insertionCount > 0); for (int columnIndex = insertedColumn.Index + insertionCount; columnIndex < ColumnsItemsInternal.Count; columnIndex++) { ColumnsItemsInternal[columnIndex].Index = columnIndex; } } /// /// Decreases the width of a non-star column by the given amount, if possible. If the total desired /// adjustment amount could not be met, the remaining amount of adjustment is returned. The adjustment /// stops when the column's target width has been met. /// /// Column to adjust. /// The target width of the column (in pixels). /// Amount to decrease (in pixels). /// The remaining amount of adjustment. private static double DecreaseNonStarColumnWidth(DataGridColumn column, double targetWidth, double amount) { Debug.Assert(amount < 0); Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star); if (MathUtilities.GreaterThanOrClose(targetWidth, column.Width.DisplayValue)) { return amount; } double adjustment = Math.Max( column.ActualMinWidth - column.Width.DisplayValue, Math.Max(targetWidth - column.Width.DisplayValue, amount)); column.SetWidthDisplayValue(column.Width.DisplayValue + adjustment); return amount - adjustment; } /// /// Decreases the widths of all non-star columns with DisplayIndex >= displayIndex such that the total /// width is decreased by the given amount, if possible. If the total desired adjustment amount /// could not be met, the remaining amount of adjustment is returned. The adjustment stops when /// the column's target width has been met. /// /// Starting column DisplayIndex. /// The target width of the column (in pixels). /// Amount to decrease (in pixels). /// Whether or not to reverse the order in which the columns are traversed. /// Whether or not to adjust widths of columns that do not yet have their initial desired width. /// The remaining amount of adjustment. private double DecreaseNonStarColumnWidths(int displayIndex, Func targetWidth, double amount, bool reverse, bool affectNewColumns) { if (MathUtilities.GreaterThanOrClose(amount, 0)) { return amount; } foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns(reverse, column => column.IsVisible && column.Width.UnitType != DataGridLengthUnitType.Star && column.DisplayIndex >= displayIndex && column.ActualCanUserResize && (affectNewColumns || column.IsInitialDesiredWidthDetermined))) { amount = DecreaseNonStarColumnWidth(column, Math.Max(column.ActualMinWidth, targetWidth(column)), amount); if (MathUtilities.IsZero(amount)) { break; } } return amount; } private void FlushDisplayIndexChanged(bool raiseEvent) { foreach (DataGridColumn column in ColumnsItemsInternal) { if (column.DisplayIndexHasChanged) { column.DisplayIndexHasChanged = false; if (raiseEvent) { Debug.Assert(column != ColumnsInternal.RowGroupSpacerColumn); OnColumnDisplayIndexChanged(column); } } } } private bool GetColumnEffectiveReadOnlyState(DataGridColumn dataGridColumn) { Debug.Assert(dataGridColumn != null); return IsReadOnly || dataGridColumn.IsReadOnly || dataGridColumn is DataGridFillerColumn; } /// /// Returns the absolute coordinate of the left edge of the given column (including /// the potential gridline - that is the left edge of the gridline is returned). Note that /// the column does not need to be in the display area. /// private double GetColumnXFromIndex(int index) { Debug.Assert(index < ColumnsItemsInternal.Count); Debug.Assert(ColumnsItemsInternal[index].IsVisible); double x = 0; foreach (DataGridColumn column in ColumnsInternal.GetVisibleColumns()) { if (index == column.Index) { break; } x += GetEdgedColumnWidth(column); } return x; } private double GetNegHorizontalOffsetFromHorizontalOffset(double horizontalOffset) { foreach (DataGridColumn column in ColumnsInternal.GetVisibleScrollingColumns()) { if (GetEdgedColumnWidth(column) > horizontalOffset) { break; } horizontalOffset -= GetEdgedColumnWidth(column); } return horizontalOffset; } /// /// Increases the width of a non-star column by the given amount, if possible. If the total desired /// adjustment amount could not be met, the remaining amount of adjustment is returned. The adjustment /// stops when the column's target width has been met. /// /// Column to adjust. /// The target width of the column (in pixels). /// Amount to increase (in pixels). /// The remaining amount of adjustment. private static double IncreaseNonStarColumnWidth(DataGridColumn column, double targetWidth, double amount) { Debug.Assert(amount > 0); Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star); if (targetWidth <= column.Width.DisplayValue) { return amount; } double adjustment = Math.Min( column.ActualMaxWidth - column.Width.DisplayValue, Math.Min(targetWidth - column.Width.DisplayValue, amount)); column.SetWidthDisplayValue(column.Width.DisplayValue + adjustment); return amount - adjustment; } /// /// Increases the widths of all non-star columns with DisplayIndex >= displayIndex such that the total /// width is increased by the given amount, if possible. If the total desired adjustment amount /// could not be met, the remaining amount of adjustment is returned. The adjustment stops when /// the column's target width has been met. /// /// Starting column DisplayIndex. /// The target width of the column (in pixels). /// Amount to increase (in pixels). /// Whether or not to reverse the order in which the columns are traversed. /// Whether or not to adjust widths of columns that do not yet have their initial desired width. /// The remaining amount of adjustment. private double IncreaseNonStarColumnWidths(int displayIndex, Func targetWidth, double amount, bool reverse, bool affectNewColumns) { if (MathUtilities.LessThanOrClose(amount, 0)) { return amount; } foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns(reverse, column => column.IsVisible && column.Width.UnitType != DataGridLengthUnitType.Star && column.DisplayIndex >= displayIndex && column.ActualCanUserResize && (affectNewColumns || column.IsInitialDesiredWidthDetermined))) { amount = IncreaseNonStarColumnWidth(column, Math.Min(column.ActualMaxWidth, targetWidth(column)), amount); if (MathUtilities.IsZero(amount)) { break; } } return amount; } private void InsertDisplayedColumnHeader(DataGridColumn dataGridColumn) { Debug.Assert(dataGridColumn != null); if (_columnHeadersPresenter != null) { dataGridColumn.HeaderCell.IsVisible = dataGridColumn.IsVisible; Debug.Assert(!_columnHeadersPresenter.Children.Contains(dataGridColumn.HeaderCell)); _columnHeadersPresenter.Children.Insert(dataGridColumn.DisplayIndexWithFiller, dataGridColumn.HeaderCell); } } private static void RefreshCellElement(DataGridColumn dataGridColumn, DataGridRow dataGridRow, string propertyName) { Debug.Assert(dataGridColumn != null); Debug.Assert(dataGridRow != null); DataGridCell dataGridCell = dataGridRow.Cells[dataGridColumn.Index]; Debug.Assert(dataGridCell != null); if (dataGridCell.Content is IControl element) { dataGridColumn.RefreshCellContent(element, propertyName); } } private void RemoveAutoGeneratedColumns() { int index = 0; _autoGeneratingColumnOperationCount++; try { while (index < ColumnsInternal.Count) { // Skip over the user columns while (index < ColumnsInternal.Count && !ColumnsInternal[index].IsAutoGenerated) { index++; } // Remove the autogenerated columns while (index < ColumnsInternal.Count && ColumnsInternal[index].IsAutoGenerated) { ColumnsInternal.RemoveAt(index); } } ColumnsInternal.AutogeneratedColumnCount = 0; } finally { _autoGeneratingColumnOperationCount--; } } private bool ScrollColumnIntoView(int columnIndex) { Debug.Assert(columnIndex >= 0 && columnIndex < ColumnsItemsInternal.Count); if (DisplayData.FirstDisplayedScrollingCol != -1 && !ColumnsItemsInternal[columnIndex].IsFrozen && (columnIndex != DisplayData.FirstDisplayedScrollingCol || _negHorizontalOffset > 0)) { int columnsToScroll; if (ColumnsInternal.DisplayInOrder(columnIndex, DisplayData.FirstDisplayedScrollingCol)) { columnsToScroll = ColumnsInternal.GetColumnCount(true /* isVisible */, false /* isFrozen */, columnIndex, DisplayData.FirstDisplayedScrollingCol); if (_negHorizontalOffset > 0) { columnsToScroll++; } ScrollColumns(-columnsToScroll); } else if (columnIndex == DisplayData.FirstDisplayedScrollingCol && _negHorizontalOffset > 0) { ScrollColumns(-1); } else if (DisplayData.LastTotallyDisplayedScrollingCol == -1 || (DisplayData.LastTotallyDisplayedScrollingCol != columnIndex && ColumnsInternal.DisplayInOrder(DisplayData.LastTotallyDisplayedScrollingCol, columnIndex))) { double xColumnLeftEdge = GetColumnXFromIndex(columnIndex); double xColumnRightEdge = xColumnLeftEdge + GetEdgedColumnWidth(ColumnsItemsInternal[columnIndex]); double change = xColumnRightEdge - HorizontalOffset - CellsWidth; double widthRemaining = change; DataGridColumn newFirstDisplayedScrollingCol = ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol]; DataGridColumn nextColumn = ColumnsInternal.GetNextVisibleColumn(newFirstDisplayedScrollingCol); double newColumnWidth = GetEdgedColumnWidth(newFirstDisplayedScrollingCol) - _negHorizontalOffset; while (nextColumn != null && widthRemaining >= newColumnWidth) { widthRemaining -= newColumnWidth; newFirstDisplayedScrollingCol = nextColumn; newColumnWidth = GetEdgedColumnWidth(newFirstDisplayedScrollingCol); nextColumn = ColumnsInternal.GetNextVisibleColumn(newFirstDisplayedScrollingCol); _negHorizontalOffset = 0; } _negHorizontalOffset += widthRemaining; DisplayData.LastTotallyDisplayedScrollingCol = columnIndex; if (newFirstDisplayedScrollingCol.Index == columnIndex) { _negHorizontalOffset = 0; double frozenColumnWidth = ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth(); // If the entire column cannot be displayed, we want to start showing it from its LeftEdge if (newColumnWidth > (CellsWidth - frozenColumnWidth)) { DisplayData.LastTotallyDisplayedScrollingCol = -1; change = xColumnLeftEdge - HorizontalOffset - frozenColumnWidth; } } DisplayData.FirstDisplayedScrollingCol = newFirstDisplayedScrollingCol.Index; // At this point DisplayData.FirstDisplayedScrollingColumn and LastDisplayedScrollingColumn // should be correct if (change != 0) { UpdateHorizontalOffset(HorizontalOffset + change); } } } return true; } private void ScrollColumns(int columns) { DataGridColumn newFirstVisibleScrollingCol = null; DataGridColumn dataGridColumnTmp; int colCount = 0; if (columns > 0) { if (DisplayData.LastTotallyDisplayedScrollingCol >= 0) { dataGridColumnTmp = ColumnsItemsInternal[DisplayData.LastTotallyDisplayedScrollingCol]; while (colCount < columns && dataGridColumnTmp != null) { dataGridColumnTmp = ColumnsInternal.GetNextVisibleColumn(dataGridColumnTmp); colCount++; } if (dataGridColumnTmp == null) { // no more column to display on the right of the last totally seen column return; } } Debug.Assert(DisplayData.FirstDisplayedScrollingCol >= 0); dataGridColumnTmp = ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol]; colCount = 0; while (colCount < columns && dataGridColumnTmp != null) { dataGridColumnTmp = ColumnsInternal.GetNextVisibleColumn(dataGridColumnTmp); colCount++; } newFirstVisibleScrollingCol = dataGridColumnTmp; } if (columns < 0) { Debug.Assert(DisplayData.FirstDisplayedScrollingCol >= 0); dataGridColumnTmp = ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol]; if (_negHorizontalOffset > 0) { colCount++; } while (colCount < -columns && dataGridColumnTmp != null) { dataGridColumnTmp = ColumnsInternal.GetPreviousVisibleScrollingColumn(dataGridColumnTmp); colCount++; } newFirstVisibleScrollingCol = dataGridColumnTmp; if (newFirstVisibleScrollingCol == null) { if (_negHorizontalOffset == 0) { // no more column to display on the left of the first seen column return; } else { newFirstVisibleScrollingCol = ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol]; } } } double newColOffset = 0; foreach (DataGridColumn dataGridColumn in ColumnsInternal.GetVisibleScrollingColumns()) { if (dataGridColumn == newFirstVisibleScrollingCol) { break; } newColOffset += GetEdgedColumnWidth(dataGridColumn); } UpdateHorizontalOffset(newColOffset); } private void UpdateDisplayedColumns() { DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn(); ComputeDisplayedColumns(); } private static DataGridBoundColumn GetDataGridColumnFromType(Type type) { Debug.Assert(type != null); if (type == typeof(bool)) { return new DataGridCheckBoxColumn(); } else if (type == typeof(bool?)) { return new DataGridCheckBoxColumn { IsThreeState = true }; } return new DataGridTextColumn(); } private void AutoGenerateColumnsPrivate() { if (!_measured || (_autoGeneratingColumnOperationCount > 0)) { // Reading the DataType when we generate columns could cause the CollectionView to // raise a Reset if its Enumeration changed. In that case, we don't want to generate again. return; } _autoGeneratingColumnOperationCount++; try { // Always remove existing autogenerated columns before generating new ones RemoveAutoGeneratedColumns(); GenerateColumnsFromProperties(); EnsureRowsPresenterVisibility(); InvalidateRowHeightEstimate(); } finally { _autoGeneratingColumnOperationCount--; } } private void GenerateColumnsFromProperties() { // Autogenerated Columns are added at the end so the user columns appear first if (DataConnection.DataProperties != null && DataConnection.DataProperties.Length > 0) { List> columnOrderPairs = new List>(); // Generate the columns foreach (PropertyInfo propertyInfo in DataConnection.DataProperties) { string columnHeader = propertyInfo.Name; int columnOrder = DATAGRID_defaultColumnDisplayOrder; // Check if DisplayAttribute is defined on the property object[] attributes = propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true); if (attributes != null && attributes.Length > 0) { DisplayAttribute displayAttribute = attributes[0] as DisplayAttribute; Debug.Assert(displayAttribute != null); bool? autoGenerateField = displayAttribute.GetAutoGenerateField(); if (autoGenerateField.HasValue && autoGenerateField.Value == false) { // Abort column generation because we aren't supposed to auto-generate this field continue; } string header = displayAttribute.GetShortName(); if (header != null) { columnHeader = header; } int? order = displayAttribute.GetOrder(); if (order.HasValue) { columnOrder = order.Value; } } // Generate a single column and determine its relative order int insertIndex = 0; if (columnOrder == int.MaxValue) { insertIndex = columnOrderPairs.Count; } else { foreach (KeyValuePair columnOrderPair in columnOrderPairs) { if (columnOrderPair.Key > columnOrder) { break; } insertIndex++; } } DataGridAutoGeneratingColumnEventArgs columnArgs = GenerateColumn(propertyInfo.PropertyType, propertyInfo.Name, columnHeader); columnOrderPairs.Insert(insertIndex, new KeyValuePair(columnOrder, columnArgs)); } // Add the columns to the DataGrid in the correct order foreach (KeyValuePair columnOrderPair in columnOrderPairs) { AddGeneratedColumn(columnOrderPair.Value); } } else if (DataConnection.DataIsPrimitive) { AddGeneratedColumn(GenerateColumn(DataConnection.DataType, string.Empty, DataConnection.DataType.Name)); } } private static DataGridAutoGeneratingColumnEventArgs GenerateColumn(Type propertyType, string propertyName, string header) { // Create a new DataBoundColumn for the Property DataGridBoundColumn newColumn = GetDataGridColumnFromType(propertyType); newColumn.Binding = new Binding(propertyName); newColumn.Header = header; newColumn.IsAutoGenerated = true; return new DataGridAutoGeneratingColumnEventArgs(propertyName, propertyType, newColumn); } private bool AddGeneratedColumn(DataGridAutoGeneratingColumnEventArgs e) { // Raise the AutoGeneratingColumn event in case the user wants to Cancel or Replace the // column being generated OnAutoGeneratingColumn(e); if (e.Cancel) { return false; } else { if (e.Column != null) { // Set the IsAutoGenerated flag here in case the user provides a custom autogenerated column e.Column.IsAutoGenerated = true; } ColumnsInternal.Add(e.Column); ColumnsInternal.AutogeneratedColumnCount++; return true; } } } }