// (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 System.Collections; using System.Collections.ObjectModel; using System.Diagnostics; using System.Reactive.Disposables; using AtomUI.Controls.Data; using AtomUI.Theme; using AtomUI.Theme.Data; using AtomUI.Theme.Styling; using AtomUI.Theme.Utils; using AtomUI.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Metadata; using Avalonia.Styling; using Avalonia.Utilities; namespace AtomUI.Controls; [TemplatePart(DataGridThemeConstants.BottomRightCornerPart, typeof(Visual))] [TemplatePart(DataGridThemeConstants.ColumnHeadersPresenterPart, typeof(DataGridColumnHeadersPresenter))] [TemplatePart(DataGridThemeConstants.HorizontalScrollbarPart, typeof(ScrollBar))] [TemplatePart(DataGridThemeConstants.RowsPresenterPart, typeof(DataGridRowsPresenter))] [TemplatePart(DataGridThemeConstants.TopLeftCornerPart, typeof(ContentControl))] [TemplatePart(DataGridThemeConstants.TopRightCornerPart, typeof(ContentControl))] [TemplatePart(DataGridThemeConstants.VerticalScrollbarPart, typeof(ScrollBar))] [PseudoClasses(StdPseudoClass.Invalid, DataGridPseudoClass.EmptyRows, DataGridPseudoClass.EmptyColumns)] public partial class DataGrid : TemplatedControl, ISizeTypeAware, IMotionAwareControl, IControlSharedTokenResourcesHost, IResourceBindingManager { #region 公共属性定义 public static readonly StyledProperty SizeTypeProperty = SizeTypeAwareControlProperty.SizeTypeProperty.AddOwner(); public static readonly StyledProperty IsShowFrameBorderProperty = AvaloniaProperty.Register(nameof(IsShowFrameBorder), false); /// /// Identifies the CanUserReorderColumns dependency property. /// public static readonly StyledProperty CanUserReorderColumnsProperty = AvaloniaProperty.Register(nameof(CanUserReorderColumns)); /// /// Identifies the CanUserResizeColumns dependency property. /// public static readonly StyledProperty CanUserResizeColumnsProperty = AvaloniaProperty.Register(nameof(CanUserResizeColumns)); /// /// Identifies the CanUserSortColumns dependency property. /// public static readonly StyledProperty CanUserSortColumnsProperty = AvaloniaProperty.Register(nameof(CanUserSortColumns), false); /// /// Identifies the CanUserFilterColumns dependency property. /// public static readonly StyledProperty CanUserFilterColumnsProperty = AvaloniaProperty.Register(nameof(CanUserFilterColumns), false); /// /// Identifies the CanUserReorderColumns dependency property. /// public static readonly StyledProperty CanUserReorderRowsProperty = AvaloniaProperty.Register(nameof(CanUserReorderRows), false); /// /// If header show next sorter direction tooltip /// public static readonly StyledProperty ShowSorterTooltipProperty = AvaloniaProperty.Register(nameof(ShowSorterTooltip), true); /// /// Identifies the ColumnHeaderHeight dependency property. /// public static readonly StyledProperty ColumnHeaderHeightProperty = AvaloniaProperty.Register( nameof(ColumnHeaderHeight), defaultValue: double.NaN, validate: IsValidColumnHeaderHeight); /// /// Identifies the ColumnWidth dependency property. /// public static readonly StyledProperty ColumnWidthProperty = AvaloniaProperty.Register(nameof(ColumnWidth), defaultValue: DataGridLength.Auto); /// /// Identifies the dependency property. /// public static readonly StyledProperty RowGroupThemeProperty = AvaloniaProperty.Register(nameof(RowGroupTheme)); public static readonly StyledProperty LeftFrozenColumnCountProperty = AvaloniaProperty.Register( nameof(LeftFrozenColumnCount), validate: ValidateFrozenColumnCount); public static readonly StyledProperty RightFrozenColumnCountProperty = AvaloniaProperty.Register( nameof(RightFrozenColumnCount), validate: ValidateFrozenColumnCount); public static readonly StyledProperty GridLinesVisibilityProperty = AvaloniaProperty.Register(nameof(GridLinesVisibility), DataGridGridLinesVisibility.Horizontal); public static readonly StyledProperty HeadersVisibilityProperty = AvaloniaProperty.Register(nameof(HeadersVisibility), DataGridHeadersVisibility.Column); public static readonly StyledProperty HorizontalScrollBarVisibilityProperty = AvaloniaProperty.Register(nameof(HorizontalScrollBarVisibility)); public static readonly StyledProperty IsReadOnlyProperty = AvaloniaProperty.Register(nameof(IsReadOnly)); public static readonly StyledProperty IsRowGroupHeadersFrozenProperty = AvaloniaProperty.Register( nameof(IsRowGroupHeadersFrozen), defaultValue: true); public static readonly DirectProperty IsValidProperty = AvaloniaProperty.RegisterDirect( nameof(IsValid), o => o.IsValid); public static readonly StyledProperty MaxColumnWidthProperty = AvaloniaProperty.Register( nameof(MaxColumnWidth), defaultValue: DefaultMaxColumnWidth, validate: IsValidColumnWidth); public static readonly StyledProperty MinColumnWidthProperty = AvaloniaProperty.Register( nameof(MinColumnWidth), defaultValue: DefaultMinColumnWidth, validate: IsValidMinColumnWidth); public static readonly AttachedProperty IsScrollInertiaEnabledProperty = ScrollViewer.IsScrollInertiaEnabledProperty.AddOwner(); public static readonly StyledProperty RowHeightProperty = AvaloniaProperty.Register( nameof(RowHeight), defaultValue: double.NaN, validate: IsValidRowHeight); public static readonly StyledProperty RowHeaderWidthProperty = AvaloniaProperty.Register( nameof(RowHeaderWidth), defaultValue: double.NaN, validate: IsValidRowHeaderWidth); public static readonly StyledProperty RowHeaderContentTemplateProperty = AvaloniaProperty.Register(nameof(RowHeaderContentTemplate)); public static readonly StyledProperty SelectionModeProperty = AvaloniaProperty.Register(nameof(SelectionMode), DataGridSelectionMode.None); public static readonly StyledProperty VerticalScrollBarVisibilityProperty = AvaloniaProperty.Register(nameof(VerticalScrollBarVisibility)); public static readonly DirectProperty SelectedIndexProperty = AvaloniaProperty.RegisterDirect( nameof(SelectedIndex), o => o.SelectedIndex, (o, v) => o.SelectedIndex = v, defaultBindingMode: BindingMode.TwoWay); public static readonly DirectProperty SelectedItemProperty = AvaloniaProperty.RegisterDirect( nameof(SelectedItem), o => o.SelectedItem, (o, v) => o.SelectedItem = v, defaultBindingMode: BindingMode.TwoWay); public static readonly StyledProperty ClipboardCopyModeProperty = AvaloniaProperty.Register( nameof(ClipboardCopyMode), defaultValue: DataGridClipboardCopyMode.ExcludeHeader); public static readonly StyledProperty AutoGenerateColumnsProperty = AvaloniaProperty.Register(nameof(AutoGenerateColumns)); public static readonly StyledProperty ItemsSourceProperty = AvaloniaProperty.Register(nameof(ItemsSource)); public static readonly StyledProperty IsRowDetailsFrozenProperty = AvaloniaProperty.Register(nameof(IsRowDetailsFrozen)); public static readonly StyledProperty RowDetailsTemplateProperty = AvaloniaProperty.Register(nameof(RowDetailsTemplate)); public static readonly StyledProperty RowDetailsVisibilityModeProperty = AvaloniaProperty.Register(nameof(RowDetailsVisibilityMode), DataGridRowDetailsVisibilityMode.Collapsed); public static readonly DirectProperty CollectionViewProperty = AvaloniaProperty.RegisterDirect(nameof(CollectionView), o => o.CollectionView); public static readonly StyledProperty TitleProperty = AvaloniaProperty.Register(nameof(Title)); public static readonly StyledProperty TitleTemplateProperty = AvaloniaProperty.Register(nameof(TitleTemplate)); public static readonly StyledProperty FooterProperty = AvaloniaProperty.Register(nameof(Footer)); public static readonly StyledProperty FooterTemplateProperty = AvaloniaProperty.Register(nameof(FooterTemplate)); public static readonly StyledProperty EmptyIndicatorProperty = AvaloniaProperty.Register(nameof(EmptyIndicator)); public static readonly StyledProperty EmptyIndicatorTemplateProperty = AvaloniaProperty.Register(nameof(EmptyIndicatorTemplate)); public static readonly StyledProperty IsMotionEnabledProperty = MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner(); public SizeType SizeType { get => GetValue(SizeTypeProperty); set => SetValue(SizeTypeProperty, value); } public bool IsShowFrameBorder { get => GetValue(IsShowFrameBorderProperty); set => SetValue(IsShowFrameBorderProperty, value); } /// /// 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 => GetValue(CanUserReorderColumnsProperty); set => SetValue(CanUserReorderColumnsProperty, value); } /// /// Gets or sets a value that indicates whether the user can adjust column widths using the mouse. /// public bool CanUserResizeColumns { get => GetValue(CanUserResizeColumnsProperty); set => SetValue(CanUserResizeColumnsProperty, value); } /// /// Gets or sets a value that indicates whether the user can sort columns by clicking the column header. /// public bool CanUserSortColumns { get => GetValue(CanUserSortColumnsProperty); set => SetValue(CanUserSortColumnsProperty, value); } /// /// Gets or sets a value that indicates whether the user can filter columns by clicking the column filter indicator. /// public bool CanUserFilterColumns { get => GetValue(CanUserFilterColumnsProperty); set => SetValue(CanUserFilterColumnsProperty, value); } /// /// Gets or sets a value that indicates whether the user can change /// the row order by dragging row reorder handle with the mouse. /// public bool CanUserReorderRows { get => GetValue(CanUserReorderRowsProperty); set => SetValue(CanUserReorderRowsProperty, value); } /// /// If header show next sorter direction tooltip /// public bool ShowSorterTooltip { get => GetValue(ShowSorterTooltipProperty); set => SetValue(ShowSorterTooltipProperty, value); } /// /// Gets or sets the height of the column headers row. /// public double ColumnHeaderHeight { get => GetValue(ColumnHeaderHeightProperty); set => SetValue(ColumnHeaderHeightProperty, value); } /// /// Gets or sets the standard width or automatic sizing mode of columns in the control. /// public DataGridLength ColumnWidth { get => GetValue(ColumnWidthProperty); set => SetValue(ColumnWidthProperty, value); } /// /// Gets or sets the theme applied to all row groups. /// public ControlTheme RowGroupTheme { get => GetValue(RowGroupThemeProperty); set => SetValue(RowGroupThemeProperty, value); } /// /// Gets or sets the number of left edge columns that the user cannot scroll horizontally. /// public int LeftFrozenColumnCount { get => GetValue(LeftFrozenColumnCountProperty); set => SetValue(LeftFrozenColumnCountProperty, value); } /// /// Gets or sets the number of right edge columns that the user cannot scroll horizontally. /// public int RightFrozenColumnCount { get => GetValue(RightFrozenColumnCountProperty); set => SetValue(RightFrozenColumnCountProperty, value); } /// /// Gets or sets a value that indicates which grid lines separating inner cells are shown. /// public DataGridGridLinesVisibility GridLinesVisibility { get => GetValue(GridLinesVisibilityProperty); set => SetValue(GridLinesVisibilityProperty, value); } /// /// Gets or sets a value that indicates the visibility of row and column headers. /// public DataGridHeadersVisibility HeadersVisibility { get => GetValue(HeadersVisibilityProperty); set => SetValue(HeadersVisibilityProperty, value); } /// /// Gets or sets a value that indicates how the horizontal scroll bar is displayed. /// public ScrollBarVisibility HorizontalScrollBarVisibility { get => GetValue(HorizontalScrollBarVisibilityProperty); set => SetValue(HorizontalScrollBarVisibilityProperty, value); } /// /// Gets or sets a value that indicates whether the user can edit the values in the control. /// public bool IsReadOnly { get => GetValue(IsReadOnlyProperty); set => SetValue(IsReadOnlyProperty, value); } /// /// 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 IsRowGroupHeadersFrozen { get => GetValue(IsRowGroupHeadersFrozenProperty); set => SetValue(IsRowGroupHeadersFrozenProperty, value); } /// /// Gets or sets whether scroll gestures should include inertia in their behavior and value. /// public bool IsScrollInertiaEnabled { get => GetValue(IsScrollInertiaEnabledProperty); set => SetValue(IsScrollInertiaEnabledProperty, value); } private bool _isValid = true; public bool IsValid { get => _isValid; internal set { SetAndRaise(IsValidProperty, ref _isValid, value); PseudoClasses.Set(StdPseudoClass.Invalid, !value); } } /// /// Gets or sets the maximum width of columns in the . /// public double MaxColumnWidth { get => GetValue(MaxColumnWidthProperty); set => SetValue(MaxColumnWidthProperty, value); } /// /// Gets or sets the minimum width of columns in the . /// public double MinColumnWidth { get => GetValue(MinColumnWidthProperty); set => SetValue(MinColumnWidthProperty, value); } /// /// Gets or sets the standard height of rows in the control. /// public double RowHeight { get => GetValue(RowHeightProperty); set => SetValue(RowHeightProperty, value); } /// /// Gets or sets the width of the row header column. /// public double RowHeaderWidth { get => GetValue(RowHeaderWidthProperty); set => SetValue(RowHeaderWidthProperty, value); } /// /// Gets or sets the template that is used to display the content of the row header. /// public IDataTemplate? RowHeaderContentTemplate { get => GetValue(RowHeaderContentTemplateProperty); set => SetValue(RowHeaderContentTemplateProperty, value); } /// /// Gets or sets a value that indicates how the vertical scroll bar is displayed. /// public ScrollBarVisibility VerticalScrollBarVisibility { get => GetValue(VerticalScrollBarVisibilityProperty); set => SetValue(VerticalScrollBarVisibilityProperty, value); } /// /// Gets or sets the selection behavior of the data grid. /// public DataGridSelectionMode SelectionMode { get => GetValue(SelectionModeProperty); set => SetValue(SelectionModeProperty, value); } /// /// 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 => _selectedIndex; set => SetAndRaise(SelectedIndexProperty, ref _selectedIndex, value); } private int _selectedIndex = -1; /// /// Gets or sets the data item corresponding to the selected row. /// public object? SelectedItem { get => _selectedItem; set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value); } private object? _selectedItem; /// /// The property which determines how DataGrid content is copied to the Clipboard. /// public DataGridClipboardCopyMode ClipboardCopyMode { get => GetValue(ClipboardCopyModeProperty); set => SetValue(ClipboardCopyModeProperty, value); } /// /// Gets or sets a value that indicates whether columns are created /// automatically when the property is set. /// public bool AutoGenerateColumns { get => GetValue(AutoGenerateColumnsProperty); set => SetValue(AutoGenerateColumnsProperty, value); } /// /// Gets or sets a collection that is used to generate the content of the control. /// public IEnumerable? ItemsSource { get => GetValue(ItemsSourceProperty); set => SetValue(ItemsSourceProperty, value); } /// /// 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 IsRowDetailsFrozen { get => GetValue(IsRowDetailsFrozenProperty); set => SetValue(IsRowDetailsFrozenProperty, value); } /// /// Gets or sets the template that is used to display the content of the details section of rows. /// public IDataTemplate? RowDetailsTemplate { get => GetValue(RowDetailsTemplateProperty); set => SetValue(RowDetailsTemplateProperty, value); } /// /// Gets or sets a value that indicates when the details sections of rows are displayed. /// public DataGridRowDetailsVisibilityMode RowDetailsVisibilityMode { get => GetValue(RowDetailsVisibilityModeProperty); set => SetValue(RowDetailsVisibilityModeProperty, value); } /// /// Gets current . /// public IDataGridCollectionView? CollectionView => DataConnection.CollectionView; [DependsOn(nameof(TitleTemplateProperty))] public object? Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); } public IDataTemplate? TitleTemplate { get => GetValue(TitleTemplateProperty); set => SetValue(TitleTemplateProperty, value); } [DependsOn(nameof(FooterTemplateProperty))] public object? Footer { get => GetValue(FooterProperty); set => SetValue(FooterProperty, value); } public IDataTemplate? FooterTemplate { get => GetValue(FooterTemplateProperty); set => SetValue(FooterTemplateProperty, value); } [DependsOn(nameof(EmptyIndicatorTemplate))] public object? EmptyIndicator { get => GetValue(EmptyIndicatorProperty); set => SetValue(EmptyIndicatorProperty, value); } public IDataTemplate? EmptyIndicatorTemplate { get => GetValue(EmptyIndicatorTemplateProperty); set => SetValue(EmptyIndicatorTemplateProperty, value); } /// /// 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 collection that contains all the columns in the control. /// we use a backing field here because the field's type /// is a subclass of the property's /// public ObservableCollection Columns => ColumnsInternal; public ObservableCollection ColumnGroups => ColumnGroupsInternal; /// /// Gets a list that contains the data items corresponding to the selected rows. /// public IList SelectedItems => _selectedItems; public bool IsMotionEnabled { get => GetValue(IsMotionEnabledProperty); set => SetValue(IsMotionEnabledProperty, value); } #endregion #region 属性验证方法 private static bool IsValidColumnHeaderHeight(double value) { return double.IsNaN(value) || (value >= MinimumColumnHeaderHeight && value <= MaxHeadersThickness); } private static bool ValidateFrozenColumnCount(int value) => value >= 0; private static bool IsValidColumnWidth(double value) { return !double.IsNaN(value) && value > 0; } private static bool IsValidMinColumnWidth(double value) { return !double.IsNaN(value) && !double.IsPositiveInfinity(value) && value >= 0; } private static bool IsValidRowHeight(double value) { return double.IsNaN(value) || (value >= DataGridRow.MinimumHeight && value <= DataGridRow.MaximumHeight); } private static bool IsValidRowHeaderWidth(double value) { return double.IsNaN(value) || (value >= MinimumRowHeaderWidth && value <= MaxHeadersThickness); } #endregion #region 公共事件定义 public static readonly RoutedEvent SelectionChangedEvent = RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble); /// /// 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; /// /// This event is dispatched when the drag indicator is dragged to a specific column. /// public event EventHandler? ColumnDraggingOver; /// /// Raised when row reordering ends, to allow subscribers to clean up. /// public event EventHandler? RowReordered; /// /// Raised when starting a row 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? RowReordering; /// /// 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; /// /// Occurs when the or /// property value changes. /// public event EventHandler? SelectionChanged { add => AddHandler(SelectionChangedEvent, value); remove => RemoveHandler(SelectionChangedEvent, value); } /// /// Occurs when the sorting request is triggered. /// public event EventHandler? Sorting; /// /// Occurs when the filtering request is triggered. /// public event EventHandler? Filtering; /// /// 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; public event EventHandler? HorizontalScroll; public event EventHandler? VerticalScroll; /// /// Occurs before a DataGridRowGroupHeader header is used. /// public event EventHandler? LoadingRowGroup; /// /// Occurs when the DataGridRowGroupHeader is available for reuse. /// public event EventHandler? UnloadingRowGroup; /// /// This event is raised by OnCopyingRowClipboardContent method after the default row content is prepared. /// Event listeners can modify or add to the row clipboard content. /// public event EventHandler? CopyingRowClipboardContent; #endregion /// /// Gets the data item bound to the row that contains the current cell. /// protected object? CurrentItem { get { if (CurrentSlot == -1 || ItemsSource == null || RowGroupHeadersTable.Contains(CurrentSlot)) { return null; } return DataConnection.GetDataItem(RowIndexFromSlot(CurrentSlot)); } } static DataGrid() { AffectsMeasure( ColumnHeaderHeightProperty, HorizontalScrollBarVisibilityProperty, VerticalScrollBarVisibilityProperty); SizeTypeProperty.OverrideDefaultValue(SizeType.Large); ItemsSourceProperty.Changed.AddClassHandler((x, e) => x.HandleItemsSourcePropertyChanged(e)); CanUserResizeColumnsProperty.Changed.AddClassHandler((x, e) => x.HandleCanUserResizeColumnsChanged(e)); ColumnWidthProperty.Changed.AddClassHandler((x, e) => x.HandleColumnWidthChanged(e)); LeftFrozenColumnCountProperty.Changed.AddClassHandler((x, e) => x.HandleFrozenColumnCountChanged(e)); GridLinesVisibilityProperty.Changed.AddClassHandler((x, e) => x.HandleGridLinesVisibilityChanged(e)); HeadersVisibilityProperty.Changed.AddClassHandler((x, e) => x.HandleHeadersVisibilityChanged(e)); IsReadOnlyProperty.Changed.AddClassHandler((x, e) => x.HandleIsReadOnlyChanged(e)); MaxColumnWidthProperty.Changed.AddClassHandler((x, e) => x.HandleMaxColumnWidthChanged(e)); MinColumnWidthProperty.Changed.AddClassHandler((x, e) => x.HandleMinColumnWidthChanged(e)); RowHeightProperty.Changed.AddClassHandler((x, e) => x.HandleRowHeightChanged(e)); RowHeaderWidthProperty.Changed.AddClassHandler((x, e) => x.HandleRowHeaderWidthChanged(e)); SelectionModeProperty.Changed.AddClassHandler((x, e) => x.HandleSelectionModeChanged(e)); SelectedIndexProperty.Changed.AddClassHandler((x, e) => x.HandleSelectedIndexChanged(e)); SelectedItemProperty.Changed.AddClassHandler((x, e) => x.HandleSelectedItemChanged(e)); IsEnabledProperty.Changed.AddClassHandler((x, e) => x.HandleIsEnabledChanged(e)); IsRowGroupHeadersFrozenProperty.Changed.AddClassHandler((x, e) => x.HandleIsRowGroupHeadersFrozenChanged(e)); RowDetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.HandleRowDetailsTemplateChanged(e)); RowHeaderContentTemplateProperty.Changed.AddClassHandler((x, e) => x.HandleRowHeaderContentTemplateChanged(e)); RowDetailsVisibilityModeProperty.Changed.AddClassHandler((x, e) => x.HandleRowDetailsVisibilityModeChanged(e)); AutoGenerateColumnsProperty.Changed.AddClassHandler((x, e) => x.HandleAutoGenerateColumnsChanged(e)); FocusableProperty.OverrideDefaultValue(true); } public DataGrid() { this.RegisterResources(); this.BindMotionProperties(); KeyDown += HandleKeyDown; KeyUp += HandleKeyUp; //TODO: Check if override works GotFocus += HandleGotFocus; LostFocus += HandleLostFocus; CurrentCellCoordinates = new DataGridCellCoordinates(-1, -1); _loadedRows = new List(); _lostFocusActions = new Queue(); _selectedItems = new DataGridSelectedItemsCollection(this); RowGroupHeadersTable = new IndexToValueTable(); _bindingValidationErrors = new List(); DisplayData = new DataGridDisplayData(this); ColumnGroupsInternal = new ObservableCollection(); ColumnsInternal = CreateColumnsInstance(); ColumnsInternal.CollectionChanged += HandleColumnsInternalCollectionChanged; ColumnGroupsInternal.CollectionChanged += HandleGroupColumnsInternalCollectionChanged; RowHeightEstimate = DefaultRowHeight; RowDetailsHeightEstimate = 0; _rowHeaderDesiredWidth = 0; DataConnection = new DataGridDataConnection(this); _showDetailsTable = new IndexToValueTable(); _collapsedSlotsTable = new IndexToValueTable(); AnchorSlot = -1; _lastEstimatedRow = -1; _editingColumnIndex = -1; _mouseOverRowIndex = null; RowGroupHeaderHeightEstimate = DefaultRowHeight; RowGroupSublevelIndents = []; _rowGroupHeightsByLevel = []; _resourceBindingsDisposable = new CompositeDisposable(); UpdatePseudoClasses(); } /// /// 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 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 = DataGridEditingUnit.Row) { 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 && column != 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); 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); } } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); this.AddResourceBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty, SharedTokenKey.BorderThickness, BindingPriority.Template, new RenderScaleAwareThicknessConfigure(this))); if (DataConnection.DataSource != null && !DataConnection.EventsWired) { DataConnection.WireEvents(DataConnection.DataSource); InitializeElements(true /*recycleRows*/); } } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); // When wired to INotifyCollectionChanged, the DataGrid will be cleaned up by GC if (DataConnection.DataSource != null && DataConnection.EventsWired) { DataConnection.UnWireEvents(DataConnection.DataSource); } this.DisposeTokenBindings(); } /// /// 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 (!MathUtils.AreClose(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; } /// protected override void OnDataContextBeginUpdate() { base.OnDataContextBeginUpdate(); NotifyDataContextPropertyForAllRowCells(GetAllRows(), true); } /// protected override void OnDataContextEndUpdate() { base.OnDataContextEndUpdate(); NotifyDataContextPropertyForAllRowCells(GetAllRows(), false); } /// /// Raises the BeginningEdit event. /// protected virtual void NotifyBeginningEdit(DataGridBeginningEditEventArgs e) { BeginningEdit?.Invoke(this, e); } /// /// Raises the CellEditEnded event. /// protected virtual void NotifyCellEditEnded(DataGridCellEditEndedEventArgs e) { CellEditEnded?.Invoke(this, e); } /// /// Raises the CellEditEnding event. /// protected virtual void NotifyCellEditEnding(DataGridCellEditEndingEventArgs e) { CellEditEnding?.Invoke(this, e); } /// /// Raises the CellPointerPressed event. /// internal virtual void NotifyCellPointerPressed(DataGridCellPointerPressedEventArgs e) { CellPointerPressed?.Invoke(this, e); } /// /// Raises the CurrentCellChanged event. /// protected virtual void NotifyCurrentCellChanged(EventArgs e) { CurrentCellChanged?.Invoke(this, e); } /// /// Raises the LoadingRow event for row preparation. /// protected virtual void NotifyLoadingRow(DataGridRowEventArgs e) { if (LoadingRow != null) { Debug.Assert(!_loadedRows.Contains(e.Row)); _loadedRows.Add(e.Row); LoadingOrUnloadingRow = true; LoadingRow(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) { var delta = e.Delta; // KeyModifiers.Shift should scroll in horizontal direction. This does not work on every platform. // If Shift-Key is pressed and X is close to 0 we swap the Vector. if (e.KeyModifiers == KeyModifiers.Shift && MathUtilities.IsZero(delta.X)) { delta = new Vector(delta.Y, delta.X); } if (UpdateScroll(delta * MouseWheelDelta)) { e.Handled = true; } else { e.Handled = e.Handled || !ScrollViewer.GetIsScrollChainingEnabled(this); } } /// /// Raises the PreparingCellForEdit event. /// protected virtual void NotifyPreparingCellForEdit(DataGridPreparingCellForEditEventArgs e) { PreparingCellForEdit?.Invoke(this, e); } /// /// Raises the RowEditEnded event. /// protected virtual void NotifyRowEditEnded(DataGridRowEditEndedEventArgs e) { RowEditEnded?.Invoke(this, e); } /// /// Raises the RowEditEnding event. /// protected virtual void NotifyRowEditEnding(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 NotifySelectionChanged(SelectionChangedEventArgs e) { RaiseEvent(e); } /// /// Raises the UnloadingRow event for row recycling. /// protected virtual void NotifyUnloadingRow(DataGridRowEventArgs e) { EventHandler? handler = UnloadingRow; if (handler != null) { LoadingOrUnloadingRow = true; handler(this, e); LoadingOrUnloadingRow = false; } } /// /// Comparator class so we can sort list by the display index /// public class DisplayIndexComparer : IComparer { int IComparer.Compare(DataGridColumn? x, DataGridColumn? y) { if (x == null && y == null) { return 0; } if (x == null && y != null) { return -1; } if (x != null && y == null) { return 1; } Debug.Assert(x != null && y != null); return (x.DisplayIndexWithFiller < y.DisplayIndexWithFiller) ? -1 : 1; } } /// /// Builds the visual tree for the column header when a new template is applied. /// //TODO Validation UI protected override void OnApplyTemplate(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(DataGridThemeConstants.ColumnHeadersPresenterPart); _groupColumnHeadersPresenter = e.NameScope.Find(DataGridThemeConstants.GroupColumnHeadersPresenterPart); _dataGridDraggingOverIndicator = e.NameScope.Find(DataGridThemeConstants.DraggingOverIndicatorPart); if (_groupColumnHeadersPresenter != null) { _groupColumnHeadersPresenter.OwningGrid = this; } if (ColumnGroups.Count > 0) { if (_groupColumnHeadersPresenter != null) { if (ColumnsInternal.FillerColumn != null) { ColumnsInternal.FillerColumn.IsRepresented = false; } BuildColumnGroupView(); } } else { if (_columnHeadersPresenter != null) { if (ColumnsInternal.FillerColumn != null) { ColumnsInternal.FillerColumn.IsRepresented = false; } _columnHeadersPresenter.OwningGrid = this; // Columns were added before our Template was applied, add the ColumnHeaders now var sortedInternal = new List(ColumnsItemsInternal); sortedInternal.Sort(new DisplayIndexComparer()); foreach (DataGridColumn column in sortedInternal) { 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(DataGridThemeConstants.RowsPresenterPart); if (_rowsPresenter != null) { _rowsPresenter.OwningGrid = this; InvalidateRowHeightEstimate(); UpdateRowDetailsHeightEstimate(); } if (_hScrollBar != null) { _hScrollBar.Scroll -= HandleHorizontalScrollBarScroll; } _hScrollBar = e.NameScope.Find(DataGridThemeConstants.HorizontalScrollbarPart); if (_hScrollBar != null) { _hScrollBar.IsTabStop = false; _hScrollBar.Maximum = 0.0; _hScrollBar.Orientation = Orientation.Horizontal; _hScrollBar.IsVisible = false; _hScrollBar.Scroll += HandleHorizontalScrollBarScroll; } if (_vScrollBar != null) { _vScrollBar.Scroll -= HandleVerticalScrollBarScroll; } _vScrollBar = e.NameScope.Find(DataGridThemeConstants.VerticalScrollbarPart); if (_vScrollBar != null) { _vScrollBar.IsTabStop = false; _vScrollBar.Maximum = 0.0; _vScrollBar.Orientation = Orientation.Vertical; _vScrollBar.IsVisible = false; _vScrollBar.Scroll += HandleVerticalScrollBarScroll; } _topLeftCornerHeader = e.NameScope.Find(DataGridThemeConstants.TopLeftCornerPart); EnsureTopLeftCornerHeader(); // EnsureTopLeftCornerHeader checks for a null _topLeftCornerHeader; _topRightCornerHeader = e.NameScope.Find(DataGridThemeConstants.TopRightCornerPart); _bottomRightCorner = e.NameScope.Find(DataGridThemeConstants.BottomRightCornerPart); if (IsShowFrameBorder) { ConfigureFrameBorderThickness(); } ConfigureHeaderCornerRadius(); SetValue(EmptyIndicatorProperty, new EmptyIndicator() { SizeType = SizeType.Middle, PresetImage = PresetEmptyImage.Simple }, BindingPriority.Template); } /// /// Raises the LoadingRowDetails for row details preparation /// protected virtual void NotifyLoadingRowDetails(DataGridRowDetailsEventArgs e) { EventHandler? handler = LoadingRowDetails; if (handler != null) { LoadingOrUnloadingRow = true; handler(this, e); LoadingOrUnloadingRow = false; } } /// /// Raises the UnloadingRowDetails event /// protected virtual void NotifyUnloadingRowDetails(DataGridRowDetailsEventArgs e) { EventHandler? handler = UnloadingRowDetails; if (handler != null) { LoadingOrUnloadingRow = true; handler(this, e); LoadingOrUnloadingRow = false; } } public void SelectAll() { SetRowsSelection(0, SlotCount - 1); } /// /// Returns the Group at the indicated level or null if the item is not in the ItemsSource /// /// item /// groupLevel /// The group the given item falls under or null if the item is not in the ItemsSource public DataGridCollectionViewGroup? GetGroupFromItem(object item, int groupLevel) { int itemIndex = DataConnection.IndexOf(item); if (itemIndex == -1) { return null; } int groupHeaderSlot = RowGroupHeadersTable.GetPreviousIndex(SlotFromRowIndex(itemIndex)); DataGridRowGroupInfo? rowGroupInfo = RowGroupHeadersTable.GetValueAt(groupHeaderSlot); while (rowGroupInfo != null && rowGroupInfo.Level != groupLevel) { groupHeaderSlot = RowGroupHeadersTable.GetPreviousIndex(rowGroupInfo.Slot); rowGroupInfo = RowGroupHeadersTable.GetValueAt(groupHeaderSlot); } return rowGroupInfo?.CollectionViewGroup; } /// /// Raises the LoadingRowGroup event /// /// EventArgs protected virtual void NotifyLoadingRowGroup(DataGridRowGroupHeaderEventArgs e) { EventHandler? handler = LoadingRowGroup; if (handler != null) { LoadingOrUnloadingRow = true; handler(this, e); LoadingOrUnloadingRow = false; } } /// /// Raises the UnLoadingRowGroup event /// /// EventArgs protected virtual void OnUnloadingRowGroup(DataGridRowGroupHeaderEventArgs e) { EventHandler? handler = UnloadingRowGroup; if (handler != null) { LoadingOrUnloadingRow = true; handler(this, e); LoadingOrUnloadingRow = false; } } /// /// This method raises the CopyingRowClipboardContent event. /// /// Contains the necessary information for generating the row clipboard content. protected virtual void OnCopyingRowClipboardContent(DataGridRowClipboardEventArgs e) { CopyingRowClipboardContent?.Invoke(this, e); } /// /// Raises the AutoGeneratingColumn event. /// protected virtual void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e) { AutoGeneratingColumn?.Invoke(this, e); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == BorderThicknessProperty || change.Property == GridLinesVisibilityProperty || change.Property == IsShowFrameBorderProperty || change.Property == FooterProperty) { ConfigureFrameBorderThickness(); } if (change.Property == TitleProperty || change.Property == HeadersVisibilityProperty) { ConfigureHeaderCornerRadius(); } } }