A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1769 lines
79 KiB

// (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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="displayIndex">Starting column DisplayIndex.</param>
/// <param name="amount">Adjustment amount (positive for increase, negative for decrease).</param>
/// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
/// <returns>The remaining amount of adjustment.</returns>
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;
}
/// <summary>
/// Grows an auto-column's width to the desired width.
/// </summary>
/// <param name="column">Auto-column to adjust.</param>
/// <param name="desiredWidth">The new desired width of the column.</param>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="displayIndex">Starting column DisplayIndex.</param>
/// <param name="amount">Amount to decrease (in pixels).</param>
/// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
/// <returns>The remaining amount of adjustment.</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="displayIndex">Starting column DisplayIndex.</param>
/// <param name="amount">Amount of increase (in pixels).</param>
/// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
/// <returns>The remaining amount of adjustment.</returns>
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;
}
}
/// <summary>
/// Invalidates the widths of all columns because the resizing behavior of an individual column has changed.
/// </summary>
/// <param name="column">Column with CanUserResize property that has changed.</param>
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]);
}
}
}
/// <summary>
/// Adjusts the specified column's width according to its new maximum value.
/// </summary>
/// <param name="column">The column to adjust.</param>
/// <param name="oldValue">The old ActualMaxWidth of the column.</param>
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);
}
}
/// <summary>
/// Adjusts the specified column's width according to its new minimum value.
/// </summary>
/// <param name="column">The column to adjust.</param>
/// <param name="oldValue">The old ActualMinWidth of the column.</param>
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
ColumnHeaders?.InvalidateChildIndex();
foreach (var row in GetAllRows())
{
row.Cells[updatedColumn.Index].IsVisible = updatedColumn.IsVisible;
row.InvalidateCellsIndex();
}
}
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;
}
/// <summary>
/// Called when a column property changes, and its cells need to
/// adjust that that column change.
/// </summary>
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();
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="displayIndex">Starting column DisplayIndex.</param>
/// <param name="adjustment">Adjustment amount (positive for increase, negative for decrease).</param>
/// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
/// <returns>The remaining amount of adjustment.</returns>
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<DataGridColumn> starColumns = new List<DataGridColumn>();
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="displayIndex">Starting column DisplayIndex.</param>
/// <param name="remainingAdjustment">Adjustment amount (positive for increase, negative for decrease).</param>
/// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
/// <param name="targetWidth">The target width of the column.</param>
/// <returns>The remaining amount of adjustment.</returns>
private double AdjustStarColumnWidths(int displayIndex, double remainingAdjustment, bool userInitiated, Func<DataGridColumn, double> 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<KeyValuePair<DataGridColumn, double>> starColumnPairs = new List<KeyValuePair<DataGridColumn, double>>();
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<DataGridColumn, double> starColumnPair in starColumnPairs)
{
if (increase ? factor <= starColumnPair.Value : factor >= starColumnPair.Value)
{
break;
}
insertIndex++;
}
starColumnPairs.Insert(insertIndex, new KeyValuePair<DataGridColumn, double>(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<DataGridColumn, double> 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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="column">Column to adjust.</param>
/// <param name="targetWidth">The target width of the column (in pixels).</param>
/// <param name="amount">Amount to decrease (in pixels).</param>
/// <returns>The remaining amount of adjustment.</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="displayIndex">Starting column DisplayIndex.</param>
/// <param name="targetWidth">The target width of the column (in pixels).</param>
/// <param name="amount">Amount to decrease (in pixels).</param>
/// <param name="reverse">Whether or not to reverse the order in which the columns are traversed.</param>
/// <param name="affectNewColumns">Whether or not to adjust widths of columns that do not yet have their initial desired width.</param>
/// <returns>The remaining amount of adjustment.</returns>
private double DecreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> 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;
}
/// <devdoc>
/// 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.
/// </devdoc>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="column">Column to adjust.</param>
/// <param name="targetWidth">The target width of the column (in pixels).</param>
/// <param name="amount">Amount to increase (in pixels).</param>
/// <returns>The remaining amount of adjustment.</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="displayIndex">Starting column DisplayIndex.</param>
/// <param name="targetWidth">The target width of the column (in pixels).</param>
/// <param name="amount">Amount to increase (in pixels).</param>
/// <param name="reverse">Whether or not to reverse the order in which the columns are traversed.</param>
/// <param name="affectNewColumns">Whether or not to adjust widths of columns that do not yet have their initial desired width.</param>
/// <returns>The remaining amount of adjustment.</returns>
private double IncreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> 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<KeyValuePair<int, DataGridAutoGeneratingColumnEventArgs>> columnOrderPairs = new List<KeyValuePair<int, DataGridAutoGeneratingColumnEventArgs>>();
// 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<int, DataGridAutoGeneratingColumnEventArgs> columnOrderPair in columnOrderPairs)
{
if (columnOrderPair.Key > columnOrder)
{
break;
}
insertIndex++;
}
}
DataGridAutoGeneratingColumnEventArgs columnArgs = GenerateColumn(propertyInfo.PropertyType, propertyInfo.Name, columnHeader);
columnOrderPairs.Insert(insertIndex, new KeyValuePair<int, DataGridAutoGeneratingColumnEventArgs>(columnOrder, columnArgs));
}
// Add the columns to the DataGrid in the correct order
foreach (KeyValuePair<int, DataGridAutoGeneratingColumnEventArgs> 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;
}
}
}
}