csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
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.
1770 lines
79 KiB
1770 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
|
|
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;
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|