// (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.Collections;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.VisualTree;
using Avalonia.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Linq;
using Avalonia.Input.Platform;
using System.ComponentModel.DataAnnotations;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
namespace Avalonia.Controls
{
///
/// Displays data in a customizable grid.
///
public partial class DataGrid : TemplatedControl
{
private const string DATAGRID_elementRowsPresenterName = "PART_RowsPresenter";
private const string DATAGRID_elementColumnHeadersPresenterName = "PART_ColumnHeadersPresenter";
private const string DATAGRID_elementFrozenColumnScrollBarSpacerName = "PART_FrozenColumnScrollBarSpacer";
private const string DATAGRID_elementHorizontalScrollbarName = "PART_HorizontalScrollbar";
private const string DATAGRID_elementRowHeadersPresenterName = "PART_RowHeadersPresenter";
private const string DATAGRID_elementTopLeftCornerHeaderName = "PART_TopLeftCornerHeader";
private const string DATAGRID_elementTopRightCornerHeaderName = "PART_TopRightCornerHeader";
private const string DATAGRID_elementValidationSummary = "PART_ValidationSummary";
private const string DATAGRID_elementVerticalScrollbarName = "PART_VerticalScrollbar";
private const bool DATAGRID_defaultAutoGenerateColumns = true;
internal const bool DATAGRID_defaultCanUserReorderColumns = true;
internal const bool DATAGRID_defaultCanUserResizeColumns = true;
internal const bool DATAGRID_defaultCanUserSortColumns = true;
private const DataGridRowDetailsVisibilityMode DATAGRID_defaultRowDetailsVisibility = DataGridRowDetailsVisibilityMode.VisibleWhenSelected;
private const DataGridSelectionMode DATAGRID_defaultSelectionMode = DataGridSelectionMode.Extended;
///
/// The default order to use for columns when there is no
/// value available for the property.
///
///
/// The value of 10,000 comes from the DataAnnotations spec, allowing
/// some properties to be ordered at the beginning and some at the end.
///
private const int DATAGRID_defaultColumnDisplayOrder = 10000;
private const double DATAGRID_horizontalGridLinesThickness = 1;
private const double DATAGRID_minimumRowHeaderWidth = 4;
private const double DATAGRID_minimumColumnHeaderHeight = 4;
internal const double DATAGRID_maximumStarColumnWidth = 10000;
internal const double DATAGRID_minimumStarColumnWidth = 0.001;
private const double DATAGRID_mouseWheelDelta = 48.0;
private const double DATAGRID_maxHeadersThickness = 32768;
private const double DATAGRID_defaultRowHeight = 22;
internal const double DATAGRID_defaultRowGroupSublevelIndent = 20;
private const double DATAGRID_defaultMinColumnWidth = 20;
private const double DATAGRID_defaultMaxColumnWidth = double.PositiveInfinity;
private List _validationErrors;
private List _bindingValidationErrors;
private IDisposable _validationSubscription;
private INotifyCollectionChanged _topLevelGroup;
private ContentControl _clipboardContentControl;
private DataGridColumnHeadersPresenter _columnHeadersPresenter;
private DataGridRowsPresenter _rowsPresenter;
private ScrollBar _vScrollBar;
private ScrollBar _hScrollBar;
private ContentControl _topLeftCornerHeader;
private ContentControl _topRightCornerHeader;
private Control _frozenColumnScrollBarSpacer;
// the sum of the widths in pixels of the scrolling columns preceding
// the first displayed scrolling column
private double _horizontalOffset;
// the number of pixels of the firstDisplayedScrollingCol which are not displayed
private double _negHorizontalOffset;
private byte _autoGeneratingColumnOperationCount;
private bool _areHandlersSuspended;
private bool _autoSizingColumns;
private IndexToValueTable _collapsedSlotsTable;
private DataGridCellCoordinates _currentCellCoordinates;
private Control _clickedElement;
// used to store the current column during a Reset
private int _desiredCurrentColumnIndex;
private int _editingColumnIndex;
// this is a workaround only for the scenarios where we need it, it is not all encompassing nor always updated
private RoutedEventArgs _editingEventArgs;
private bool _executingLostFocusActions;
private bool _flushCurrentCellChanged;
private bool _focusEditingControl;
private IVisual _focusedObject;
private byte _horizontalScrollChangesIgnored;
private DataGridRow _focusedRow;
private bool _ignoreNextScrollBarsLayout;
// Nth row of rows 0..N that make up the RowHeightEstimate
private int _lastEstimatedRow;
private List _loadedRows;
// prevents reentry into the VerticalScroll event handler
private Queue _lostFocusActions;
private int _noSelectionChangeCount;
private int _noCurrentCellChangeCount;
private bool _makeFirstDisplayedCellCurrentCellPending;
private bool _measured;
private int? _mouseOverRowIndex; // -1 is used for the 'new row'
private DataGridColumn _previousCurrentColumn;
private object _previousCurrentItem;
private double[] _rowGroupHeightsByLevel;
private double _rowHeaderDesiredWidth;
private Size? _rowsPresenterAvailableSize;
private bool _scrollingByHeight;
private IndexToValueTable _showDetailsTable;
private bool _successfullyUpdatedSelection;
private DataGridSelectedItemsCollection _selectedItems;
private bool _temporarilyResetCurrentCell;
private object _uneditedValue; // Represents the original current cell value at the time it enters editing mode.
private ICellEditBinding _currentCellEditBinding;
// An approximation of the sum of the heights in pixels of the scrolling rows preceding
// the first displayed scrolling row. Since the scrolled off rows are discarded, the grid
// does not know their actual height. The heights used for the approximation are the ones
// set as the rows were scrolled off.
private double _verticalOffset;
private byte _verticalScrollChangesIgnored;
private IEnumerable _items;
///
/// Identifies the CanUserReorderColumns dependency property.
///
public static readonly StyledProperty CanUserReorderColumnsProperty =
AvaloniaProperty.Register(nameof(CanUserReorderColumns));
///
/// Gets or sets a value that indicates whether the user can change
/// the column display order by dragging column headers with the mouse.
///
public bool CanUserReorderColumns
{
get { return GetValue(CanUserReorderColumnsProperty); }
set { SetValue(CanUserReorderColumnsProperty, value); }
}
///
/// Identifies the CanUserResizeColumns dependency property.
///
public static readonly StyledProperty CanUserResizeColumnsProperty =
AvaloniaProperty.Register(nameof(CanUserResizeColumns));
///
/// Gets or sets a value that indicates whether the user can adjust column widths using the mouse.
///
public bool CanUserResizeColumns
{
get { return GetValue(CanUserResizeColumnsProperty); }
set { SetValue(CanUserResizeColumnsProperty, value); }
}
///
/// Identifies the CanUserSortColumns dependency property.
///
public static readonly StyledProperty CanUserSortColumnsProperty =
AvaloniaProperty.Register(nameof(CanUserSortColumns), true);
///
/// Gets or sets a value that indicates whether the user can sort columns by clicking the column header.
///
public bool CanUserSortColumns
{
get { return GetValue(CanUserSortColumnsProperty); }
set { SetValue(CanUserSortColumnsProperty, value); }
}
///
/// Identifies the ColumnHeaderHeight dependency property.
///
public static readonly StyledProperty ColumnHeaderHeightProperty =
AvaloniaProperty.Register(
nameof(ColumnHeaderHeight),
defaultValue: double.NaN,
validate: IsValidColumnHeaderHeight);
private static bool IsValidColumnHeaderHeight(double value)
{
return double.IsNaN(value) ||
(value >= DATAGRID_minimumColumnHeaderHeight && value <= DATAGRID_maxHeadersThickness);
}
///
/// Gets or sets the height of the column headers row.
///
public double ColumnHeaderHeight
{
get { return GetValue(ColumnHeaderHeightProperty); }
set { SetValue(ColumnHeaderHeightProperty, value); }
}
///
/// Identifies the ColumnWidth dependency property.
///
public static readonly StyledProperty ColumnWidthProperty =
AvaloniaProperty.Register(nameof(ColumnWidth), defaultValue: DataGridLength.Auto);
///
/// Gets or sets the standard width or automatic sizing mode of columns in the control.
///
public DataGridLength ColumnWidth
{
get { return GetValue(ColumnWidthProperty); }
set { SetValue(ColumnWidthProperty, value); }
}
public static readonly StyledProperty AlternatingRowBackgroundProperty =
AvaloniaProperty.Register(nameof(AlternatingRowBackground));
///
/// Gets or sets the that is used to paint the background of odd-numbered rows.
///
///
/// The brush that is used to paint the background of odd-numbered rows. The default is a
/// with a
/// value of white (ARGB value #00FFFFFF).
///
public IBrush AlternatingRowBackground
{
get { return GetValue(AlternatingRowBackgroundProperty); }
set { SetValue(AlternatingRowBackgroundProperty, value); }
}
public static readonly StyledProperty FrozenColumnCountProperty =
AvaloniaProperty.Register(
nameof(FrozenColumnCount),
validate: ValidateFrozenColumnCount);
///
/// Gets or sets the number of columns that the user cannot scroll horizontally.
///
public int FrozenColumnCount
{
get { return GetValue(FrozenColumnCountProperty); }
set { SetValue(FrozenColumnCountProperty, value); }
}
private static bool ValidateFrozenColumnCount(int value) => value >= 0;
public static readonly StyledProperty GridLinesVisibilityProperty =
AvaloniaProperty.Register(nameof(GridLinesVisibility));
///
/// Gets or sets a value that indicates which grid lines separating inner cells are shown.
///
public DataGridGridLinesVisibility GridLinesVisibility
{
get { return GetValue(GridLinesVisibilityProperty); }
set { SetValue(GridLinesVisibilityProperty, value); }
}
public static readonly StyledProperty HeadersVisibilityProperty =
AvaloniaProperty.Register(nameof(HeadersVisibility));
///
/// Gets or sets a value that indicates the visibility of row and column headers.
///
public DataGridHeadersVisibility HeadersVisibility
{
get { return GetValue(HeadersVisibilityProperty); }
set { SetValue(HeadersVisibilityProperty, value); }
}
public static readonly StyledProperty HorizontalGridLinesBrushProperty =
AvaloniaProperty.Register(nameof(HorizontalGridLinesBrush));
///
/// Gets or sets the that is used to paint grid lines separating rows.
///
public IBrush HorizontalGridLinesBrush
{
get { return GetValue(HorizontalGridLinesBrushProperty); }
set { SetValue(HorizontalGridLinesBrushProperty, value); }
}
public static readonly StyledProperty HorizontalScrollBarVisibilityProperty =
AvaloniaProperty.Register(nameof(HorizontalScrollBarVisibility));
///
/// Gets or sets a value that indicates how the horizontal scroll bar is displayed.
///
public ScrollBarVisibility HorizontalScrollBarVisibility
{
get { return GetValue(HorizontalScrollBarVisibilityProperty); }
set { SetValue(HorizontalScrollBarVisibilityProperty, value); }
}
public static readonly StyledProperty IsReadOnlyProperty =
AvaloniaProperty.Register(nameof(IsReadOnly));
///
/// Gets or sets a value that indicates whether the user can edit the values in the control.
///
public bool IsReadOnly
{
get { return GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
public static readonly StyledProperty AreRowGroupHeadersFrozenProperty =
AvaloniaProperty.Register(
nameof(AreRowGroupHeadersFrozen),
defaultValue: true);
///
/// Gets or sets a value that indicates whether the row group header sections
/// remain fixed at the width of the display area or can scroll horizontally.
///
public bool AreRowGroupHeadersFrozen
{
get { return GetValue(AreRowGroupHeadersFrozenProperty); }
set { SetValue(AreRowGroupHeadersFrozenProperty, value); }
}
private void OnAreRowGroupHeadersFrozenChanged(AvaloniaPropertyChangedEventArgs e)
{
var value = (bool)e.NewValue;
ProcessFrozenColumnCount();
// Update elements in the RowGroupHeader that were previously frozen
if (value)
{
if (_rowsPresenter != null)
{
foreach (Control element in _rowsPresenter.Children)
{
if (element is DataGridRowGroupHeader groupHeader)
{
groupHeader.ClearFrozenStates();
}
}
}
}
}
private bool _isValid = true;
public static readonly DirectProperty IsValidProperty =
AvaloniaProperty.RegisterDirect(
nameof(IsValid),
o => o.IsValid);
public bool IsValid
{
get { return _isValid; }
internal set
{
SetAndRaise(IsValidProperty, ref _isValid, value);
PseudoClasses.Set(":invalid", !value);
}
}
public static readonly StyledProperty MaxColumnWidthProperty =
AvaloniaProperty.Register(
nameof(MaxColumnWidth),
defaultValue: DATAGRID_defaultMaxColumnWidth,
validate: IsValidColumnWidth);
private static bool IsValidColumnWidth(double value)
{
return !double.IsNaN(value) && value > 0;
}
///
/// Gets or sets the maximum width of columns in the .
///
public double MaxColumnWidth
{
get { return GetValue(MaxColumnWidthProperty); }
set { SetValue(MaxColumnWidthProperty, value); }
}
public static readonly StyledProperty MinColumnWidthProperty =
AvaloniaProperty.Register(
nameof(MinColumnWidth),
defaultValue: DATAGRID_defaultMinColumnWidth,
validate: IsValidMinColumnWidth);
private static bool IsValidMinColumnWidth(double value)
{
return !double.IsNaN(value) && !double.IsPositiveInfinity(value) && value >= 0;
}
///
/// Gets or sets the minimum width of columns in the .
///
public double MinColumnWidth
{
get { return GetValue(MinColumnWidthProperty); }
set { SetValue(MinColumnWidthProperty, value); }
}
public static readonly StyledProperty RowBackgroundProperty =
AvaloniaProperty.Register(nameof(RowBackground));
///
/// Gets or sets the that is used to paint row backgrounds.
///
public IBrush RowBackground
{
get { return GetValue(RowBackgroundProperty); }
set { SetValue(RowBackgroundProperty, value); }
}
public static readonly StyledProperty RowHeightProperty =
AvaloniaProperty.Register(
nameof(RowHeight),
defaultValue: double.NaN,
validate: IsValidRowHeight);
private static bool IsValidRowHeight(double value)
{
return double.IsNaN(value) ||
(value >= DataGridRow.DATAGRIDROW_minimumHeight &&
value <= DataGridRow.DATAGRIDROW_maximumHeight);
}
///
/// Gets or sets the standard height of rows in the control.
///
public double RowHeight
{
get { return GetValue(RowHeightProperty); }
set { SetValue(RowHeightProperty, value); }
}
public static readonly StyledProperty RowHeaderWidthProperty =
AvaloniaProperty.Register(
nameof(RowHeaderWidth),
defaultValue: double.NaN,
validate: IsValidRowHeaderWidth);
private static bool IsValidRowHeaderWidth(double value)
{
return double.IsNaN(value) ||
(value >= DATAGRID_minimumRowHeaderWidth &&
value <= DATAGRID_maxHeadersThickness);
}
///
/// Gets or sets the width of the row header column.
///
public double RowHeaderWidth
{
get { return GetValue(RowHeaderWidthProperty); }
set { SetValue(RowHeaderWidthProperty, value); }
}
public static readonly StyledProperty SelectionModeProperty =
AvaloniaProperty.Register(nameof(SelectionMode));
///
/// Gets or sets the selection behavior of the data grid.
///
public DataGridSelectionMode SelectionMode
{
get { return GetValue(SelectionModeProperty); }
set { SetValue(SelectionModeProperty, value); }
}
public static readonly StyledProperty VerticalGridLinesBrushProperty =
AvaloniaProperty.Register(nameof(VerticalGridLinesBrush));
///
/// Gets or sets the that is used to paint grid lines separating columns.
///
public IBrush VerticalGridLinesBrush
{
get { return GetValue(VerticalGridLinesBrushProperty); }
set { SetValue(VerticalGridLinesBrushProperty, value); }
}
public static readonly StyledProperty VerticalScrollBarVisibilityProperty =
AvaloniaProperty.Register(nameof(VerticalScrollBarVisibility));
///
/// Gets or sets a value that indicates how the vertical scroll bar is displayed.
///
public ScrollBarVisibility VerticalScrollBarVisibility
{
get { return GetValue(VerticalScrollBarVisibilityProperty); }
set { SetValue(VerticalScrollBarVisibilityProperty, value); }
}
public static readonly StyledProperty> DropLocationIndicatorTemplateProperty =
AvaloniaProperty.Register>(nameof(DropLocationIndicatorTemplate));
///
/// Gets or sets the template that is used when rendering the column headers.
///
public ITemplate DropLocationIndicatorTemplate
{
get { return GetValue(DropLocationIndicatorTemplateProperty); }
set { SetValue(DropLocationIndicatorTemplateProperty, value); }
}
private int _selectedIndex = -1;
private object _selectedItem;
public static readonly DirectProperty SelectedIndexProperty =
AvaloniaProperty.RegisterDirect(
nameof(SelectedIndex),
o => o.SelectedIndex,
(o, v) => o.SelectedIndex = v);
///
/// Gets or sets the index of the current selection.
///
///
/// The index of the current selection, or -1 if the selection is empty.
///
public int SelectedIndex
{
get { return _selectedIndex; }
set { SetAndRaise(SelectedIndexProperty, ref _selectedIndex, value); }
}
public static readonly DirectProperty SelectedItemProperty =
AvaloniaProperty.RegisterDirect(
nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v);
///
/// Gets or sets the data item corresponding to the selected row.
///
public object SelectedItem
{
get { return _selectedItem; }
set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); }
}
public static readonly StyledProperty ClipboardCopyModeProperty =
AvaloniaProperty.Register(
nameof(ClipboardCopyMode),
defaultValue: DataGridClipboardCopyMode.ExcludeHeader);
///
/// The property which determines how DataGrid content is copied to the Clipboard.
///
public DataGridClipboardCopyMode ClipboardCopyMode
{
get { return GetValue(ClipboardCopyModeProperty); }
set { SetValue(ClipboardCopyModeProperty, value); }
}
public static readonly StyledProperty AutoGenerateColumnsProperty =
AvaloniaProperty.Register(nameof(AutoGenerateColumns));
///
/// Gets or sets a value that indicates whether columns are created
/// automatically when the property is set.
///
public bool AutoGenerateColumns
{
get { return GetValue(AutoGenerateColumnsProperty); }
set { SetValue(AutoGenerateColumnsProperty, value); }
}
private void OnAutoGenerateColumnsChanged(AvaloniaPropertyChangedEventArgs e)
{
var value = (bool)e.NewValue;
if (value)
{
InitializeElements(recycleRows: false);
}
else
{
RemoveAutoGeneratedColumns();
}
}
///
/// Identifies the ItemsSource dependency property.
///
public static readonly DirectProperty ItemsProperty =
AvaloniaProperty.RegisterDirect(
nameof(Items),
o => o.Items,
(o, v) => o.Items = v);
///
/// Gets or sets a collection that is used to generate the content of the control.
///
public IEnumerable Items
{
get { return _items; }
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
public static readonly StyledProperty AreRowDetailsFrozenProperty =
AvaloniaProperty.Register(nameof(AreRowDetailsFrozen));
///
/// Gets or sets a value that indicates whether the row details sections remain
/// fixed at the width of the display area or can scroll horizontally.
///
public bool AreRowDetailsFrozen
{
get { return GetValue(AreRowDetailsFrozenProperty); }
set { SetValue(AreRowDetailsFrozenProperty, value); }
}
public static readonly StyledProperty RowDetailsTemplateProperty =
AvaloniaProperty.Register(nameof(RowDetailsTemplate));
///
/// Gets or sets the template that is used to display the content of the details section of rows.
///
public IDataTemplate RowDetailsTemplate
{
get { return GetValue(RowDetailsTemplateProperty); }
set { SetValue(RowDetailsTemplateProperty, value); }
}
public static readonly StyledProperty RowDetailsVisibilityModeProperty =
AvaloniaProperty.Register(nameof(RowDetailsVisibilityMode));
///
/// Gets or sets a value that indicates when the details sections of rows are displayed.
///
public DataGridRowDetailsVisibilityMode RowDetailsVisibilityMode
{
get { return GetValue(RowDetailsVisibilityModeProperty); }
set { SetValue(RowDetailsVisibilityModeProperty, value); }
}
static DataGrid()
{
AffectsMeasure(
ColumnHeaderHeightProperty,
HorizontalScrollBarVisibilityProperty,
VerticalScrollBarVisibilityProperty);
ItemsProperty.Changed.AddClassHandler((x, e) => x.OnItemsPropertyChanged(e));
CanUserResizeColumnsProperty.Changed.AddClassHandler((x, e) => x.OnCanUserResizeColumnsChanged(e));
ColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnColumnWidthChanged(e));
RowBackgroundProperty.Changed.AddClassHandler((x, e) => x.OnRowBackgroundChanged(e));
AlternatingRowBackgroundProperty.Changed.AddClassHandler((x, e) => x.OnRowBackgroundChanged(e));
FrozenColumnCountProperty.Changed.AddClassHandler((x, e) => x.OnFrozenColumnCountChanged(e));
GridLinesVisibilityProperty.Changed.AddClassHandler((x, e) => x.OnGridLinesVisibilityChanged(e));
HeadersVisibilityProperty.Changed.AddClassHandler((x, e) => x.OnHeadersVisibilityChanged(e));
HorizontalGridLinesBrushProperty.Changed.AddClassHandler((x, e) => x.OnHorizontalGridLinesBrushChanged(e));
IsReadOnlyProperty.Changed.AddClassHandler((x, e) => x.OnIsReadOnlyChanged(e));
MaxColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnMaxColumnWidthChanged(e));
MinColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnMinColumnWidthChanged(e));
RowHeightProperty.Changed.AddClassHandler((x, e) => x.OnRowHeightChanged(e));
RowHeaderWidthProperty.Changed.AddClassHandler((x, e) => x.OnRowHeaderWidthChanged(e));
SelectionModeProperty.Changed.AddClassHandler((x, e) => x.OnSelectionModeChanged(e));
VerticalGridLinesBrushProperty.Changed.AddClassHandler((x, e) => x.OnVerticalGridLinesBrushChanged(e));
SelectedIndexProperty.Changed.AddClassHandler((x, e) => x.OnSelectedIndexChanged(e));
SelectedItemProperty.Changed.AddClassHandler((x, e) => x.OnSelectedItemChanged(e));
IsEnabledProperty.Changed.AddClassHandler((x, e) => x.DataGrid_IsEnabledChanged(e));
AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler((x, e) => x.OnAreRowGroupHeadersFrozenChanged(e));
RowDetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsTemplateChanged(e));
RowDetailsVisibilityModeProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsVisibilityModeChanged(e));
AutoGenerateColumnsProperty.Changed.AddClassHandler((x, e) => x.OnAutoGenerateColumnsChanged(e));
}
///
/// Initializes a new instance of the class.
///
public DataGrid()
{
KeyDown += DataGrid_KeyDown;
KeyUp += DataGrid_KeyUp;
//TODO: Check if override works
GotFocus += DataGrid_GotFocus;
LostFocus += DataGrid_LostFocus;
_loadedRows = new List();
_lostFocusActions = new Queue();
_selectedItems = new DataGridSelectedItemsCollection(this);
RowGroupHeadersTable = new IndexToValueTable();
_bindingValidationErrors = new List();
DisplayData = new DataGridDisplayData(this);
ColumnsInternal = CreateColumnsInstance();
RowHeightEstimate = DATAGRID_defaultRowHeight;
RowDetailsHeightEstimate = 0;
_rowHeaderDesiredWidth = 0;
DataConnection = new DataGridDataConnection(this);
_showDetailsTable = new IndexToValueTable();
_collapsedSlotsTable = new IndexToValueTable();
AnchorSlot = -1;
_lastEstimatedRow = -1;
_editingColumnIndex = -1;
_mouseOverRowIndex = null;
CurrentCellCoordinates = new DataGridCellCoordinates(-1, -1);
RowGroupHeaderHeightEstimate = DATAGRID_defaultRowHeight;
}
private void SetValueNoCallback(AvaloniaProperty property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
_areHandlersSuspended = true;
try
{
SetValue(property, value, priority);
}
finally
{
_areHandlersSuspended = false;
}
}
private void OnRowDetailsVisibilityModeChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateRowDetailsVisibilityMode((DataGridRowDetailsVisibilityMode)e.NewValue);
}
private void OnRowDetailsTemplateChanged(AvaloniaPropertyChangedEventArgs e)
{
// Update the RowDetails templates if necessary
if (_rowsPresenter != null)
{
foreach (DataGridRow row in GetAllRows())
{
if (GetRowDetailsVisibility(row.Index))
{
// DetailsPreferredHeight is initialized when the DetailsElement's size changes.
row.ApplyDetailsTemplate(initializeDetailsPreferredHeight: false);
}
}
}
UpdateRowDetailsHeightEstimate();
InvalidateMeasure();
}
///
/// ItemsProperty property changed handler.
///
/// AvaloniaPropertyChangedEventArgs.
private void OnItemsPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
Debug.Assert(DataConnection != null);
var oldValue = (IEnumerable)e.OldValue;
var newItemsSource = (IEnumerable)e.NewValue;
if (LoadingOrUnloadingRow)
{
SetValueNoCallback(ItemsProperty, oldValue);
throw DataGridError.DataGrid.CannotChangeItemsWhenLoadingRows();
}
// Try to commit edit on the old DataSource, but force a cancel if it fails
if (!CommitEdit())
{
CancelEdit(DataGridEditingUnit.Row, false);
}
DataConnection.UnWireEvents(DataConnection.DataSource);
DataConnection.ClearDataProperties();
ClearRowGroupHeadersTable();
// The old selected indexes are no longer relevant. There's a perf benefit from
// updating the selected indexes with a null DataSource, because we know that all
// of the previously selected indexes have been removed from selection
DataConnection.DataSource = null;
_selectedItems.UpdateIndexes();
CoerceSelectedItem();
// Wrap an IEnumerable in an ICollectionView if it's not already one
bool setDefaultSelection = false;
if (newItemsSource != null && !(newItemsSource is IDataGridCollectionView))
{
DataConnection.DataSource = DataGridDataConnection.CreateView(newItemsSource);
}
else
{
DataConnection.DataSource = newItemsSource;
setDefaultSelection = true;
}
if (DataConnection.DataSource != null)
{
// Setup the column headers
if (DataConnection.DataType != null)
{
foreach (var column in ColumnsInternal.GetDisplayedColumns())
{
if (column is DataGridBoundColumn boundColumn)
{
boundColumn.SetHeaderFromBinding();
}
}
}
DataConnection.WireEvents(DataConnection.DataSource);
}
// Wait for the current cell to be set before we raise any SelectionChanged events
_makeFirstDisplayedCellCurrentCellPending = true;
// Clear out the old rows and remove the generated columns
ClearRows(false); //recycle
RemoveAutoGeneratedColumns();
// Set the SlotCount (from the data count and number of row group headers) before we make the default selection
PopulateRowGroupHeadersTable();
SelectedItem = null;
if (DataConnection.CollectionView != null && setDefaultSelection)
{
SelectedItem = DataConnection.CollectionView.CurrentItem;
}
// Treat this like the DataGrid has never been measured because all calculations at
// this point are invalid until the next layout cycle. For instance, the ItemsSource
// can be set when the DataGrid is not part of the visual tree
_measured = false;
InvalidateMeasure();
}
}
private void OnSelectedIndexChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
int index = (int)e.NewValue;
// GetDataItem returns null if index is >= Count, we do not check newValue
// against Count here to avoid enumerating through an Enumerable twice
// Setting SelectedItem coerces the finally value of the SelectedIndex
object newSelectedItem = (index < 0) ? null : DataConnection.GetDataItem(index);
SelectedItem = newSelectedItem;
if (SelectedItem != newSelectedItem)
{
SetValueNoCallback(SelectedIndexProperty, (int)e.OldValue);
}
}
}
private void OnSelectedItemChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
int rowIndex = (e.NewValue == null) ? -1 : DataConnection.IndexOf(e.NewValue);
if (rowIndex == -1)
{
// If the Item is null or it's not found, clear the Selection
if (!CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
{
// Edited value couldn't be committed or aborted
SetValueNoCallback(SelectedItemProperty, e.OldValue);
return;
}
// Clear all row selections
ClearRowSelection(resetAnchorSlot: true);
}
else
{
int slot = SlotFromRowIndex(rowIndex);
if (slot != CurrentSlot)
{
if (!CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
{
// Edited value couldn't be committed or aborted
SetValueNoCallback(SelectedItemProperty, e.OldValue);
return;
}
if (slot >= SlotCount || slot < -1)
{
if (DataConnection.CollectionView != null)
{
DataConnection.CollectionView.MoveCurrentToPosition(rowIndex);
}
}
}
int oldSelectedIndex = SelectedIndex;
SetValueNoCallback(SelectedIndexProperty, rowIndex);
try
{
_noSelectionChangeCount++;
int columnIndex = CurrentColumnIndex;
if (columnIndex == -1)
{
columnIndex = FirstDisplayedNonFillerColumnIndex;
}
if (IsSlotOutOfSelectionBounds(slot))
{
ClearRowSelection(slotException: slot, setAnchorSlot: true);
return;
}
UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false);
}
finally
{
NoSelectionChangeCount--;
}
if (!_successfullyUpdatedSelection)
{
SetValueNoCallback(SelectedIndexProperty, oldSelectedIndex);
SetValueNoCallback(SelectedItemProperty, e.OldValue);
}
}
}
}
private void OnVerticalGridLinesBrushChanged(AvaloniaPropertyChangedEventArgs e)
{
if (_rowsPresenter != null)
{
foreach (DataGridRow row in GetAllRows())
{
row.EnsureGridLines();
}
}
}
private void OnSelectionModeChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
ClearRowSelection(resetAnchorSlot: true);
}
}
private void OnRowHeaderWidthChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
EnsureRowHeaderWidth();
}
}
private void OnRowHeightChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
InvalidateRowHeightEstimate();
// Re-measure all the rows due to the Height change
InvalidateRowsMeasure(invalidateIndividualElements: true);
// DataGrid needs to update the layout information and the ScrollBars
InvalidateMeasure();
}
}
private void OnMinColumnWidthChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
double oldValue = (double)e.OldValue;
foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns())
{
OnColumnMinWidthChanged(column, Math.Max(column.MinWidth, oldValue));
}
}
}
private void OnMaxColumnWidthChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
var oldValue = (double)e.OldValue;
foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns())
{
OnColumnMaxWidthChanged(column, Math.Min(column.MaxWidth, oldValue));
}
}
}
private void OnIsReadOnlyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
var value = (bool)e.NewValue;
if (value && !CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
{
CancelEdit(DataGridEditingUnit.Row, raiseEvents: false);
}
}
}
private void OnHorizontalGridLinesBrushChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended && _rowsPresenter != null)
{
foreach (DataGridRow row in GetAllRows())
{
row.EnsureGridLines();
}
}
}
private void OnHeadersVisibilityChanged(AvaloniaPropertyChangedEventArgs e)
{
var oldValue = (DataGridHeadersVisibility)e.OldValue;
var newValue = (DataGridHeadersVisibility)e.NewValue;
bool hasFlags(DataGridHeadersVisibility value, DataGridHeadersVisibility flags) => ((value & flags) == flags);
bool newValueCols = hasFlags(newValue, DataGridHeadersVisibility.Column);
bool newValueRows = hasFlags(newValue, DataGridHeadersVisibility.Row);
bool oldValueCols = hasFlags(oldValue, DataGridHeadersVisibility.Column);
bool oldValueRows = hasFlags(oldValue, DataGridHeadersVisibility.Row);
// Columns
if (newValueCols != oldValueCols)
{
if (_columnHeadersPresenter != null)
{
EnsureColumnHeadersVisibility();
if (!newValueCols)
{
_columnHeadersPresenter.Measure(Size.Empty);
}
else
{
EnsureVerticalGridLines();
}
InvalidateMeasure();
}
}
// Rows
if (newValueRows != oldValueRows)
{
if (_rowsPresenter != null)
{
foreach (Control element in _rowsPresenter.Children)
{
if (element is DataGridRow row)
{
row.EnsureHeaderStyleAndVisibility(null);
if (newValueRows)
{
row.UpdatePseudoClasses();
row.EnsureHeaderVisibility();
}
}
else if (element is DataGridRowGroupHeader rowGroupHeader)
{
rowGroupHeader.EnsureHeaderVisibility();
}
}
InvalidateRowHeightEstimate();
InvalidateRowsMeasure(invalidateIndividualElements: true);
}
}
if (_topLeftCornerHeader != null)
{
_topLeftCornerHeader.IsVisible = newValueRows && newValueCols;
if (_topLeftCornerHeader.IsVisible)
{
_topLeftCornerHeader.Measure(Size.Empty);
}
}
}
private void OnGridLinesVisibilityChanged(AvaloniaPropertyChangedEventArgs e)
{
foreach (DataGridRow row in GetAllRows())
{
row.EnsureGridLines();
row.InvalidateHorizontalArrange();
}
}
private void OnFrozenColumnCountChanged(AvaloniaPropertyChangedEventArgs e)
{
ProcessFrozenColumnCount();
}
private void ProcessFrozenColumnCount()
{
CorrectColumnFrozenStates();
ComputeScrollBarsLayout();
InvalidateColumnHeadersArrange();
InvalidateCellsArrange();
}
private void OnRowBackgroundChanged(AvaloniaPropertyChangedEventArgs e)
{
foreach (DataGridRow row in GetAllRows())
{
row.EnsureBackground();
}
}
private void OnColumnWidthChanged(AvaloniaPropertyChangedEventArgs e)
{
var value = (DataGridLength)e.NewValue;
foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns())
{
if (column.InheritsWidth)
{
column.SetWidthInternalNoCallback(value);
}
}
EnsureHorizontalLayout();
}
private void OnCanUserResizeColumnsChanged(AvaloniaPropertyChangedEventArgs e)
{
EnsureHorizontalLayout();
}
///
/// Occurs one time for each public, non-static property in the bound data type when the
/// property is changed and the
/// property is true.
///
public event EventHandler AutoGeneratingColumn;
///
/// Occurs before a cell or row enters editing mode.
///
public event EventHandler BeginningEdit;
///
/// Occurs after cell editing has ended.
///
public event EventHandler CellEditEnded;
///
/// Occurs immediately before cell editing has ended.
///
public event EventHandler CellEditEnding;
///
/// Occurs when cell is mouse-pressed.
///
public event EventHandler CellPointerPressed;
///
/// Occurs when the
/// property of a column changes.
///
public event EventHandler ColumnDisplayIndexChanged;
///
/// Raised when column reordering ends, to allow subscribers to clean up.
///
public event EventHandler ColumnReordered;
///
/// Raised when starting a column reordering action. Subscribers to this event can
/// set tooltip and caret UIElements, constrain tooltip position, indicate that
/// a preview should be shown, or cancel reordering.
///
public event EventHandler ColumnReordering;
///
/// Occurs when a different cell becomes the current cell.
///
public event EventHandler CurrentCellChanged;
///
/// Occurs after a
/// is instantiated, so that you can customize it before it is used.
///
public event EventHandler LoadingRow;
///
/// Occurs when a cell in a enters editing mode.
///
///
public event EventHandler PreparingCellForEdit;
///
/// Occurs when the row has been successfully committed or cancelled.
///
public event EventHandler RowEditEnded;
///
/// Occurs immediately before the row has been successfully committed or cancelled.
///
public event EventHandler RowEditEnding;
public static readonly RoutedEvent SelectionChangedEvent =
RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble);
///
/// Occurs when the or
/// property value changes.
///
public event EventHandler SelectionChanged
{
add { AddHandler(SelectionChangedEvent, value); }
remove { AddHandler(SelectionChangedEvent, value); }
}
///
/// Occurs when a
/// object becomes available for reuse.
///
public event EventHandler UnloadingRow;
///
/// Occurs when a new row details template is applied to a row, so that you can customize
/// the details section before it is used.
///
public event EventHandler LoadingRowDetails;
///
/// Occurs when the
/// property value changes.
///
public event EventHandler RowDetailsVisibilityChanged;
///
/// Occurs when a row details element becomes available for reuse.
///
public event EventHandler UnloadingRowDetails;
///
/// Gets a collection that contains all the columns in the control.
///
public ObservableCollection Columns
{
get
{
// we use a backing field here because the field's type
// is a subclass of the property's
return ColumnsInternal;
}
}
///
/// Gets or sets the column that contains the current cell.
///
public DataGridColumn CurrentColumn
{
get
{
if (CurrentColumnIndex == -1)
{
return null;
}
Debug.Assert(CurrentColumnIndex < ColumnsItemsInternal.Count);
return ColumnsItemsInternal[CurrentColumnIndex];
}
set
{
DataGridColumn dataGridColumn = value;
if (dataGridColumn == null)
{
throw DataGridError.DataGrid.ValueCannotBeSetToNull("value", "CurrentColumn");
}
if (CurrentColumn != dataGridColumn)
{
if (dataGridColumn.OwningGrid != this)
{
// Provided column does not belong to this DataGrid
throw DataGridError.DataGrid.ColumnNotInThisDataGrid();
}
if (!dataGridColumn.IsVisible)
{
// CurrentColumn cannot be set to an invisible column
throw DataGridError.DataGrid.ColumnCannotBeCollapsed();
}
if (CurrentSlot == -1)
{
// There is no current row so the current column cannot be set
throw DataGridError.DataGrid.NoCurrentRow();
}
bool beginEdit = _editingColumnIndex != -1;
//exitEditingMode, keepFocus, raiseEvents
if (!EndCellEdit(DataGridEditAction.Commit, true, ContainsFocus, true))
{
// Edited value couldn't be committed or aborted
return;
}
UpdateSelectionAndCurrency(dataGridColumn.Index, CurrentSlot, DataGridSelectionAction.None, false); //scrollIntoView
Debug.Assert(_successfullyUpdatedSelection);
if (beginEdit &&
_editingColumnIndex == -1 &&
CurrentSlot != -1 &&
CurrentColumnIndex != -1 &&
CurrentColumnIndex == dataGridColumn.Index &&
dataGridColumn.OwningGrid == this &&
!GetColumnEffectiveReadOnlyState(dataGridColumn))
{
// Returning to editing mode since the grid was in that mode prior to the EndCellEdit call above.
BeginCellEdit(new RoutedEventArgs());
}
}
}
}
///
/// Gets a list that contains the data items corresponding to the selected rows.
///
public IList SelectedItems
{
get { return _selectedItems as IList; }
}
internal DataGridColumnCollection ColumnsInternal
{
get;
private set;
}
internal int AnchorSlot
{
get;
private set;
}
internal double ActualRowHeaderWidth
{
get
{
if (!AreRowHeadersVisible)
{
return 0;
}
else
{
return !double.IsNaN(RowHeaderWidth) ? RowHeaderWidth : RowHeadersDesiredWidth;
}
}
}
internal double ActualRowsPresenterHeight
{
get
{
if (_rowsPresenter != null)
{
return _rowsPresenter.Bounds.Height;
}
return 0;
}
}
internal bool AreColumnHeadersVisible
{
get
{
return (HeadersVisibility & DataGridHeadersVisibility.Column) == DataGridHeadersVisibility.Column;
}
}
internal bool AreRowHeadersVisible
{
get
{
return (HeadersVisibility & DataGridHeadersVisibility.Row) == DataGridHeadersVisibility.Row;
}
}
///
/// Indicates whether or not at least one auto-sizing column is waiting for all the rows
/// to be measured before its final width is determined.
///
internal bool AutoSizingColumns
{
get
{
return _autoSizingColumns;
}
set
{
if (_autoSizingColumns && !value && ColumnsInternal != null)
{
double adjustment = CellsWidth - ColumnsInternal.VisibleEdgedColumnsWidth;
AdjustColumnWidths(0, adjustment, false);
foreach (DataGridColumn column in ColumnsInternal.GetVisibleColumns())
{
column.IsInitialDesiredWidthDetermined = true;
}
ColumnsInternal.EnsureVisibleEdgedColumnsWidth();
ComputeScrollBarsLayout();
InvalidateColumnHeadersMeasure();
InvalidateRowsMeasure(true);
}
_autoSizingColumns = value;
}
}
internal double AvailableSlotElementRoom
{
get;
set;
}
// Height currently available for cells this value is smaller. This height is reduced by the existence of ColumnHeaders
// or a horizontal scrollbar. Layout is asynchronous so changes to the ColumnHeaders or the horizontal scrollbar are
// not reflected immediately.
internal double CellsHeight
{
get
{
return RowsPresenterEstimatedAvailableHeight ?? 0;
}
}
// Width currently available for cells this value is smaller. This width is reduced by the existence of RowHeaders
// or a vertical scrollbar. Layout is asynchronous so changes to the RowHeaders or the vertical scrollbar are
// not reflected immediately
internal double CellsWidth
{
get
{
double rowsWidth = double.PositiveInfinity;
if (RowsPresenterAvailableSize.HasValue)
{
rowsWidth = Math.Max(0, RowsPresenterAvailableSize.Value.Width - ActualRowHeaderWidth);
}
return double.IsPositiveInfinity(rowsWidth) ? ColumnsInternal.VisibleEdgedColumnsWidth : rowsWidth;
}
}
internal DataGridColumnHeadersPresenter ColumnHeaders => _columnHeadersPresenter;
internal List ColumnsItemsInternal => ColumnsInternal.ItemsInternal;
internal bool ContainsFocus
{
get;
private set;
}
internal int CurrentColumnIndex
{
get
{
return CurrentCellCoordinates.ColumnIndex;
}
private set
{
CurrentCellCoordinates.ColumnIndex = value;
}
}
internal int CurrentSlot
{
get
{
return CurrentCellCoordinates.Slot;
}
private set
{
CurrentCellCoordinates.Slot = value;
}
}
internal DataGridDataConnection DataConnection
{
get;
private set;
}
internal DataGridDisplayData DisplayData
{
get;
private set;
}
internal int EditingColumnIndex
{
get;
private set;
}
internal DataGridRow EditingRow
{
get;
private set;
}
internal double FirstDisplayedScrollingColumnHiddenWidth => _negHorizontalOffset;
// When the RowsPresenter's width increases, the HorizontalOffset will be incorrect until
// the scrollbar's layout is recalculated, which doesn't occur until after the cells are measured.
// This property exists to account for this scenario, and avoid collapsing the incorrect cells.
internal double HorizontalAdjustment
{
get;
private set;
}
internal static double HorizontalGridLinesThickness => DATAGRID_horizontalGridLinesThickness;
// the sum of the widths in pixels of the scrolling columns preceding
// the first displayed scrolling column
internal double HorizontalOffset
{
get
{
return _horizontalOffset;
}
set
{
if (value < 0)
{
value = 0;
}
double widthNotVisible = Math.Max(0, ColumnsInternal.VisibleEdgedColumnsWidth - CellsWidth);
if (value > widthNotVisible)
{
value = widthNotVisible;
}
if (value == _horizontalOffset)
{
return;
}
if (_hScrollBar != null && value != _hScrollBar.Value)
{
_hScrollBar.Value = value;
}
_horizontalOffset = value;
DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn();
// update the lastTotallyDisplayedScrollingCol
ComputeDisplayedColumns();
}
}
internal ScrollBar HorizontalScrollBar => _hScrollBar;
internal IndexToValueTable RowGroupHeadersTable
{
get;
private set;
}
internal bool LoadingOrUnloadingRow
{
get;
private set;
}
internal bool InDisplayIndexAdjustments
{
get;
set;
}
internal int? MouseOverRowIndex
{
get
{
return _mouseOverRowIndex;
}
set
{
if (_mouseOverRowIndex != value)
{
DataGridRow oldMouseOverRow = null;
if (_mouseOverRowIndex.HasValue)
{
int oldSlot = SlotFromRowIndex(_mouseOverRowIndex.Value);
if (IsSlotVisible(oldSlot))
{
oldMouseOverRow = DisplayData.GetDisplayedElement(oldSlot) as DataGridRow;
}
}
_mouseOverRowIndex = value;
// State for the old row needs to be applied after setting the new value
if (oldMouseOverRow != null)
{
oldMouseOverRow.UpdatePseudoClasses();
}
if (_mouseOverRowIndex.HasValue)
{
int newSlot = SlotFromRowIndex(_mouseOverRowIndex.Value);
if (IsSlotVisible(newSlot))
{
DataGridRow newMouseOverRow = DisplayData.GetDisplayedElement(newSlot) as DataGridRow;
Debug.Assert(newMouseOverRow != null);
if (newMouseOverRow != null)
{
newMouseOverRow.UpdatePseudoClasses();
}
}
}
}
}
}
internal double NegVerticalOffset
{
get;
private set;
}
internal int NoCurrentCellChangeCount
{
get
{
return _noCurrentCellChangeCount;
}
set
{
_noCurrentCellChangeCount = value;
if (value == 0)
{
FlushCurrentCellChanged();
}
}
}
internal double RowDetailsHeightEstimate
{
get;
private set;
}
internal double RowHeadersDesiredWidth
{
get
{
return _rowHeaderDesiredWidth;
}
set
{
// We only auto grow
if (_rowHeaderDesiredWidth < value)
{
double oldActualRowHeaderWidth = ActualRowHeaderWidth;
_rowHeaderDesiredWidth = value;
if (oldActualRowHeaderWidth != ActualRowHeaderWidth)
{
EnsureRowHeaderWidth();
}
}
}
}
internal double RowGroupHeaderHeightEstimate
{
get;
private set;
}
internal double RowHeightEstimate
{
get;
private set;
}
internal Size? RowsPresenterAvailableSize
{
get
{
return _rowsPresenterAvailableSize;
}
set
{
if (_rowsPresenterAvailableSize.HasValue && value.HasValue && value.Value.Width > RowsPresenterAvailableSize.Value.Width)
{
// When the available cells width increases, the horizontal offset can be incorrect.
// Store away an adjustment to use during the CellsPresenter's measure, so that the
// ShouldDisplayCell method correctly determines if a cell will be in view.
//
// | h. offset | new available cells width |
// |-------------->|----------------------------------------->|
// __________________________________________________ |
// | | | | | |
// | column0 | column1 | column2 | column3 |<----->|
// | | | | | adj. |
//
double adjustment = (_horizontalOffset + value.Value.Width) - ColumnsInternal.VisibleEdgedColumnsWidth;
HorizontalAdjustment = Math.Min(HorizontalOffset, Math.Max(0, adjustment));
}
else
{
HorizontalAdjustment = 0;
}
_rowsPresenterAvailableSize = value;
}
}
internal double? RowsPresenterEstimatedAvailableHeight
{
get;
set;
}
internal double[] RowGroupSublevelIndents
{
get;
private set;
}
// This flag indicates whether selection has actually changed during a selection operation,
// and exists to ensure that FlushSelectionChanged doesn't unnecessarily raise SelectionChanged.
internal bool SelectionHasChanged
{
get;
set;
}
internal int SlotCount
{
get;
private set;
}
internal bool UpdatedStateOnMouseLeftButtonDown
{
get;
set;
}
///
/// Indicates whether or not to use star-sizing logic. If the DataGrid has infinite available space,
/// then star sizing doesn't make sense. In this case, all star columns grow to a predefined size of
/// 10,000 pixels in order to show the developer that star columns shouldn't be used.
///
internal bool UsesStarSizing
{
get
{
if (ColumnsInternal != null)
{
return ColumnsInternal.VisibleStarColumnCount > 0 &&
(!RowsPresenterAvailableSize.HasValue || !double.IsPositiveInfinity(RowsPresenterAvailableSize.Value.Width));
}
return false;
}
}
internal ScrollBar VerticalScrollBar => _vScrollBar;
internal int VisibleSlotCount
{
get;
set;
}
///
/// Gets the data item bound to the row that contains the current cell.
///
protected object CurrentItem
{
get
{
if (CurrentSlot == -1 || Items == null || RowGroupHeadersTable.Contains(CurrentSlot))
{
return null;
}
return DataConnection.GetDataItem(RowIndexFromSlot(CurrentSlot));
}
}
private DataGridCellCoordinates CurrentCellCoordinates
{
get;
set;
}
private int FirstDisplayedNonFillerColumnIndex
{
get
{
DataGridColumn column = ColumnsInternal.FirstVisibleNonFillerColumn;
if (column != null)
{
if (column.IsFrozen)
{
return column.Index;
}
else
{
if (DisplayData.FirstDisplayedScrollingCol >= column.Index)
{
return DisplayData.FirstDisplayedScrollingCol;
}
else
{
return column.Index;
}
}
}
return -1;
}
}
private int NoSelectionChangeCount
{
get
{
return _noSelectionChangeCount;
}
set
{
_noSelectionChangeCount = value;
if (value == 0)
{
FlushSelectionChanged();
}
}
}
///
/// Enters editing mode for the current cell and current row (if they're not already in editing mode).
///
/// True if operation was successful. False otherwise.
public bool BeginEdit()
{
return BeginEdit(null);
}
///
/// Enters editing mode for the current cell and current row (if they're not already in editing mode).
///
/// Provides information about the user gesture that caused the call to BeginEdit. Can be null.
/// True if operation was successful. False otherwise.
public bool BeginEdit(RoutedEventArgs editingEventArgs)
{
if (CurrentColumnIndex == -1 || !GetRowSelection(CurrentSlot))
{
return false;
}
Debug.Assert(CurrentColumnIndex >= 0);
Debug.Assert(CurrentColumnIndex < ColumnsItemsInternal.Count);
Debug.Assert(CurrentSlot >= -1);
Debug.Assert(CurrentSlot < SlotCount);
Debug.Assert(EditingRow == null || EditingRow.Slot == CurrentSlot);
if (GetColumnEffectiveReadOnlyState(CurrentColumn))
{
// Current column is read-only
return false;
}
return BeginCellEdit(editingEventArgs);
}
///
/// Cancels editing mode and restores the original value.
///
/// True if operation was successful. False otherwise.
public bool CancelEdit()
{
return CancelEdit(DataGridEditingUnit.Row);
}
///
/// Cancels editing mode for the specified DataGridEditingUnit and restores its original value.
///
/// Specifies whether to cancel edit for a Cell or Row.
/// True if operation was successful. False otherwise.
public bool CancelEdit(DataGridEditingUnit editingUnit)
{
return CancelEdit(editingUnit, raiseEvents: true);
}
///
/// Commits editing mode and pushes changes to the backend.
///
/// True if operation was successful. False otherwise.
public bool CommitEdit()
{
return CommitEdit(DataGridEditingUnit.Row, true);
}
///
/// Commits editing mode for the specified DataGridEditingUnit and pushes changes to the backend.
///
/// Specifies whether to commit edit for a Cell or Row.
/// Editing mode is left if True.
/// True if operation was successful. False otherwise.
public bool CommitEdit(DataGridEditingUnit editingUnit, bool exitEditingMode)
{
if (!EndCellEdit(
editAction: DataGridEditAction.Commit,
exitEditingMode: editingUnit == DataGridEditingUnit.Cell ? exitEditingMode : true,
keepFocus: ContainsFocus,
raiseEvents: true))
{
return false;
}
if (editingUnit == DataGridEditingUnit.Row)
{
return EndRowEdit(DataGridEditAction.Commit, exitEditingMode, raiseEvents: true);
}
return true;
}
///
/// Scrolls the specified item or RowGroupHeader and/or column into view.
/// If item is not null: scrolls the row representing the item into view;
/// If column is not null: scrolls the column into view;
/// If both item and column are null, the method returns without scrolling.
///
/// an item from the DataGrid's items source or a CollectionViewGroup from the collection view
/// a column from the DataGrid's columns collection
public void ScrollIntoView(object item, DataGridColumn column)
{
if ((column == null && (item == null || FirstDisplayedNonFillerColumnIndex == -1))
|| (column != null && column.OwningGrid != this))
{
// no-op
return;
}
if (item == null)
{
// scroll column into view
ScrollSlotIntoView(
column.Index,
DisplayData.FirstScrollingSlot,
forCurrentCellChange: false,
forceHorizontalScroll: true);
}
else
{
int slot = -1;
DataGridRowGroupInfo rowGroupInfo = null;
if (item is DataGridCollectionViewGroup collectionViewGroup)
{
rowGroupInfo = RowGroupInfoFromCollectionViewGroup(collectionViewGroup);
if (rowGroupInfo == null)
{
Debug.Assert(false);
return;
}
slot = rowGroupInfo.Slot;
}
else
{
// the row index will be set to -1 if the item is null or not in the list
int rowIndex = DataConnection.IndexOf(item);
if (rowIndex == -1)
{
return;
}
slot = SlotFromRowIndex(rowIndex);
}
int columnIndex = (column == null) ? FirstDisplayedNonFillerColumnIndex : column.Index;
if (_collapsedSlotsTable.Contains(slot))
{
// We need to expand all parent RowGroups so that the slot is visible
if (rowGroupInfo != null)
{
ExpandRowGroupParentChain(rowGroupInfo.Level - 1, rowGroupInfo.Slot);
}
else
{
rowGroupInfo = RowGroupHeadersTable.GetValueAt(RowGroupHeadersTable.GetPreviousIndex(slot));
Debug.Assert(rowGroupInfo != null);
if (rowGroupInfo != null)
{
ExpandRowGroupParentChain(rowGroupInfo.Level, rowGroupInfo.Slot);
}
}
// Update Scrollbar and display information
NegVerticalOffset = 0;
SetVerticalOffset(0);
ResetDisplayedRows();
DisplayData.FirstScrollingSlot = 0;
ComputeScrollBarsLayout();
}
ScrollSlotIntoView(
columnIndex, slot,
forCurrentCellChange: true,
forceHorizontalScroll: true);
}
}
///
/// Arranges the content of the .
///
///
/// The final area within the parent that this element should use to arrange itself and its children.
///
///
/// The actual size used by the .
///
protected override Size ArrangeOverride(Size finalSize)
{
if (_makeFirstDisplayedCellCurrentCellPending)
{
MakeFirstDisplayedCellCurrentCell();
}
if (Bounds.Width != finalSize.Width)
{
// If our final width has changed, we might need to update the filler
InvalidateColumnHeadersArrange();
InvalidateCellsArrange();
}
return base.ArrangeOverride(finalSize);
}
///
/// Measures the children of a to prepare for
/// arranging them during the
/// pass.
///
///
/// The size that the determines it needs during layout, based on its calculations of child object allocated sizes.
///
///
/// The available size that this element can give to child elements. Indicates an upper limit that
/// child elements should not exceed.
///
protected override Size MeasureOverride(Size availableSize)
{
// Delay layout until after the initial measure to avoid invalid calculations when the
// DataGrid is not part of the visual tree
if (!_measured)
{
_measured = true;
// We don't need to clear the rows because it was already done when the ItemsSource changed
RefreshRowsAndColumns(clearRows: false);
//// Update our estimates now that the DataGrid has all of the information necessary
UpdateRowDetailsHeightEstimate();
// Update frozen columns to account for columns added prior to loading or autogenerated columns
if (FrozenColumnCountWithFiller > 0)
{
ProcessFrozenColumnCount();
}
}
Size desiredSize;
// This is a shortcut to skip layout if we don't have any columns
if (ColumnsInternal.VisibleEdgedColumnsWidth == 0)
{
if (_hScrollBar != null && _hScrollBar.IsVisible)
{
_hScrollBar.IsVisible = false;
}
if (_vScrollBar != null && _vScrollBar.IsVisible)
{
_vScrollBar.IsVisible = false;
}
desiredSize = base.MeasureOverride(availableSize);
}
else
{
if (_rowsPresenter != null)
{
_rowsPresenter.InvalidateMeasure();
}
InvalidateColumnHeadersMeasure();
desiredSize = base.MeasureOverride(availableSize);
ComputeScrollBarsLayout();
}
return desiredSize;
}
///
/// Raises the BeginningEdit event.
///
protected virtual void OnBeginningEdit(DataGridBeginningEditEventArgs e)
{
BeginningEdit?.Invoke(this, e);
}
///
/// Raises the CellEditEnded event.
///
protected virtual void OnCellEditEnded(DataGridCellEditEndedEventArgs e)
{
CellEditEnded?.Invoke(this, e);
}
///
/// Raises the CellEditEnding event.
///
protected virtual void OnCellEditEnding(DataGridCellEditEndingEventArgs e)
{
CellEditEnding?.Invoke(this, e);
}
///
/// Raises the CellPointerPressed event.
///
internal virtual void OnCellPointerPressed(DataGridCellPointerPressedEventArgs e)
{
CellPointerPressed?.Invoke(this, e);
}
///
/// Raises the CurrentCellChanged event.
///
protected virtual void OnCurrentCellChanged(EventArgs e)
{
CurrentCellChanged?.Invoke(this, e);
}
///
/// Raises the LoadingRow event for row preparation.
///
protected virtual void OnLoadingRow(DataGridRowEventArgs e)
{
EventHandler handler = LoadingRow;
if (handler != null)
{
Debug.Assert(!_loadedRows.Contains(e.Row));
_loadedRows.Add(e.Row);
LoadingOrUnloadingRow = true;
handler(this, e);
LoadingOrUnloadingRow = false;
Debug.Assert(_loadedRows.Contains(e.Row));
_loadedRows.Remove(e.Row);
}
}
///
/// Scrolls the DataGrid according to the direction of the delta.
///
/// PointerWheelEventArgs
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
if (IsEnabled && !e.Handled && DisplayData.NumDisplayedScrollingElements > 0)
{
double scrollHeight = 0;
if (e.Delta.Y > 0)
{
scrollHeight = Math.Max(-_verticalOffset, -DATAGRID_mouseWheelDelta);
}
else if (e.Delta.Y < 0)
{
if (_vScrollBar != null && VerticalScrollBarVisibility == ScrollBarVisibility.Visible)
{
scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), DATAGRID_mouseWheelDelta);
}
else
{
double maximum = EdgedRowsHeightCalculated - CellsHeight;
scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), DATAGRID_mouseWheelDelta);
}
}
if (scrollHeight != 0)
{
DisplayData.PendingVerticalScrollHeight = scrollHeight;
InvalidateRowsMeasure(invalidateIndividualElements: false);
e.Handled = true;
}
}
}
///
/// Raises the PreparingCellForEdit event.
///
protected virtual void OnPreparingCellForEdit(DataGridPreparingCellForEditEventArgs e)
{
PreparingCellForEdit?.Invoke(this, e);
}
///
/// Raises the RowEditEnded event.
///
protected virtual void OnRowEditEnded(DataGridRowEditEndedEventArgs e)
{
RowEditEnded?.Invoke(this, e);
}
///
/// Raises the RowEditEnding event.
///
protected virtual void OnRowEditEnding(DataGridRowEditEndingEventArgs e)
{
RowEditEnding?.Invoke(this, e);
}
///
/// Raises the SelectionChanged event and clears the _selectionChanged.
/// This event won't get raised again until after _selectionChanged is set back to true.
///
protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)
{
RaiseEvent(e);
}
///
/// Raises the UnloadingRow event for row recycling.
///
protected virtual void OnUnloadingRow(DataGridRowEventArgs e)
{
EventHandler handler = UnloadingRow;
if (handler != null)
{
LoadingOrUnloadingRow = true;
handler(this, e);
LoadingOrUnloadingRow = false;
}
}
///
/// Builds the visual tree for the column header when a new template is applied.
///
//TODO Validation UI
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
// The template has changed, so we need to refresh the visuals
_measured = false;
if (_columnHeadersPresenter != null)
{
// If we're applying a new template, we want to remove the old column headers first
_columnHeadersPresenter.Children.Clear();
}
_columnHeadersPresenter = e.NameScope.Find(DATAGRID_elementColumnHeadersPresenterName);
if (_columnHeadersPresenter != null)
{
if (ColumnsInternal.FillerColumn != null)
{
ColumnsInternal.FillerColumn.IsRepresented = false;
}
_columnHeadersPresenter.OwningGrid = this;
// Columns were added before before our Template was applied, add the ColumnHeaders now
foreach (DataGridColumn column in ColumnsItemsInternal)
{
InsertDisplayedColumnHeader(column);
}
}
if (_rowsPresenter != null)
{
// If we're applying a new template, we want to remove the old rows first
UnloadElements(recycle: false);
}
_rowsPresenter = e.NameScope.Find(DATAGRID_elementRowsPresenterName);
if (_rowsPresenter != null)
{
_rowsPresenter.OwningGrid = this;
InvalidateRowHeightEstimate();
UpdateRowDetailsHeightEstimate();
}
_frozenColumnScrollBarSpacer = e.NameScope.Find(DATAGRID_elementFrozenColumnScrollBarSpacerName);
if (_hScrollBar != null)
{
_hScrollBar.Scroll -= HorizontalScrollBar_Scroll;
}
_hScrollBar = e.NameScope.Find(DATAGRID_elementHorizontalScrollbarName);
if (_hScrollBar != null)
{
//_hScrollBar.IsTabStop = false;
_hScrollBar.Maximum = 0.0;
_hScrollBar.Orientation = Orientation.Horizontal;
_hScrollBar.IsVisible = false;
_hScrollBar.Scroll += HorizontalScrollBar_Scroll;
}
if (_vScrollBar != null)
{
_vScrollBar.Scroll -= VerticalScrollBar_Scroll;
}
_vScrollBar = e.NameScope.Find(DATAGRID_elementVerticalScrollbarName);
if (_vScrollBar != null)
{
//_vScrollBar.IsTabStop = false;
_vScrollBar.Maximum = 0.0;
_vScrollBar.Orientation = Orientation.Vertical;
_vScrollBar.IsVisible = false;
_vScrollBar.Scroll += VerticalScrollBar_Scroll;
}
_topLeftCornerHeader = e.NameScope.Find(DATAGRID_elementTopLeftCornerHeaderName);
EnsureTopLeftCornerHeader(); // EnsureTopLeftCornerHeader checks for a null _topLeftCornerHeader;
_topRightCornerHeader = e.NameScope.Find(DATAGRID_elementTopRightCornerHeaderName);
}
///
/// Cancels editing mode for the specified DataGridEditingUnit and restores its original value.
///
/// Specifies whether to cancel edit for a Cell or Row.
/// Specifies whether or not to raise editing events
/// True if operation was successful. False otherwise.
internal bool CancelEdit(DataGridEditingUnit editingUnit, bool raiseEvents)
{
if (!EndCellEdit(
DataGridEditAction.Cancel,
exitEditingMode: true,
keepFocus: ContainsFocus,
raiseEvents: raiseEvents))
{
return false;
}
if (editingUnit == DataGridEditingUnit.Row)
{
return EndRowEdit(DataGridEditAction.Cancel, true, raiseEvents);
}
return true;
}
///
/// call when: selection changes or SelectedItems object changes
///
internal void CoerceSelectedItem()
{
object selectedItem = null;
if (SelectionMode == DataGridSelectionMode.Extended &&
CurrentSlot != -1 &&
_selectedItems.ContainsSlot(CurrentSlot))
{
selectedItem = CurrentItem;
}
else if (_selectedItems.Count > 0)
{
selectedItem = _selectedItems[0];
}
SetValueNoCallback(SelectedItemProperty, selectedItem);
// Update the SelectedIndex
int newIndex = -1;
if (selectedItem != null)
{
newIndex = DataConnection.IndexOf(selectedItem);
}
SetValueNoCallback(SelectedIndexProperty, newIndex);
}
internal static DataGridCell GetOwningCell(Control element)
{
Debug.Assert(element != null);
DataGridCell cell = element as DataGridCell;
while (element != null && cell == null)
{
element = element.Parent as Control;
cell = element as DataGridCell;
}
return cell;
}
internal IEnumerable