mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-22 09:23:53 +08:00
1484 lines
55 KiB
C#
1484 lines
55 KiB
C#
// (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.Styling;
|
|
using Avalonia.Utilities;
|
|
|
|
namespace AtomUI.Controls;
|
|
|
|
[TemplatePart(DataGridTheme.BottomRightCornerPart, typeof(Visual))]
|
|
[TemplatePart(DataGridTheme.ColumnHeadersPresenterPart, typeof(DataGridColumnHeadersPresenter))]
|
|
[TemplatePart(DataGridTheme.FrozenColumnScrollBarSpacerPart, typeof(Control))]
|
|
[TemplatePart(DataGridTheme.HorizontalScrollbarPart, typeof(ScrollBar))]
|
|
[TemplatePart(DataGridTheme.RowsPresenterPart, typeof(DataGridRowsPresenter))]
|
|
[TemplatePart(DataGridTheme.TopLeftCornerPart, typeof(ContentControl))]
|
|
[TemplatePart(DataGridTheme.TopRightCornerPart, typeof(ContentControl))]
|
|
[TemplatePart(DataGridTheme.VerticalScrollbarPart, typeof(ScrollBar))]
|
|
[PseudoClasses(StdPseudoClass.Invalid, DataGridPseudoClass.EmptyRows, DataGridPseudoClass.EmptyColumns)]
|
|
public partial class DataGrid : TemplatedControl,
|
|
ISizeTypeAware,
|
|
IMotionAwareControl,
|
|
IControlSharedTokenResourcesHost,
|
|
IResourceBindingManager
|
|
{
|
|
#region 公共属性定义
|
|
|
|
public static readonly StyledProperty<SizeType> SizeTypeProperty =
|
|
SizeTypeAwareControlProperty.SizeTypeProperty.AddOwner<DataGrid>();
|
|
|
|
/// <summary>
|
|
/// Identifies the CanUserReorderColumns dependency property.
|
|
/// </summary>
|
|
public static readonly StyledProperty<bool> CanUserReorderColumnsProperty =
|
|
AvaloniaProperty.Register<DataGrid, bool>(nameof(CanUserReorderColumns));
|
|
|
|
/// <summary>
|
|
/// Identifies the CanUserResizeColumns dependency property.
|
|
/// </summary>
|
|
public static readonly StyledProperty<bool> CanUserResizeColumnsProperty =
|
|
AvaloniaProperty.Register<DataGrid, bool>(nameof(CanUserResizeColumns));
|
|
|
|
/// <summary>
|
|
/// Identifies the CanUserSortColumns dependency property.
|
|
/// </summary>
|
|
public static readonly StyledProperty<bool> CanUserSortColumnsProperty =
|
|
AvaloniaProperty.Register<DataGrid, bool>(nameof(CanUserSortColumns), false);
|
|
|
|
/// <summary>
|
|
/// Identifies the ColumnHeaderHeight dependency property.
|
|
/// </summary>
|
|
public static readonly StyledProperty<double> ColumnHeaderHeightProperty =
|
|
AvaloniaProperty.Register<DataGrid, double>(
|
|
nameof(ColumnHeaderHeight),
|
|
defaultValue: double.NaN,
|
|
validate: IsValidColumnHeaderHeight);
|
|
|
|
/// <summary>
|
|
/// Identifies the ColumnWidth dependency property.
|
|
/// </summary>
|
|
public static readonly StyledProperty<DataGridLength> ColumnWidthProperty =
|
|
AvaloniaProperty.Register<DataGrid, DataGridLength>(nameof(ColumnWidth), defaultValue: DataGridLength.Auto);
|
|
|
|
/// <summary>
|
|
/// Identifies the <see cref="CellTheme"/> dependency property.
|
|
/// </summary>
|
|
public static readonly StyledProperty<ControlTheme> CellThemeProperty =
|
|
AvaloniaProperty.Register<DataGrid, ControlTheme>(nameof(CellTheme));
|
|
|
|
/// <summary>
|
|
/// Identifies the <see cref="RowGroupTheme"/> dependency property.
|
|
/// </summary>
|
|
public static readonly StyledProperty<ControlTheme> RowGroupThemeProperty =
|
|
AvaloniaProperty.Register<DataGrid, ControlTheme>(nameof(RowGroupTheme));
|
|
|
|
public static readonly StyledProperty<int> FrozenColumnCountProperty =
|
|
AvaloniaProperty.Register<DataGrid, int>(
|
|
nameof(FrozenColumnCount),
|
|
validate: ValidateFrozenColumnCount);
|
|
|
|
public static readonly StyledProperty<DataGridGridLinesVisibility> GridLinesVisibilityProperty =
|
|
AvaloniaProperty.Register<DataGrid, DataGridGridLinesVisibility>(nameof(GridLinesVisibility), DataGridGridLinesVisibility.Horizontal);
|
|
|
|
public static readonly StyledProperty<DataGridHeadersVisibility> HeadersVisibilityProperty =
|
|
AvaloniaProperty.Register<DataGrid, DataGridHeadersVisibility>(nameof(HeadersVisibility));
|
|
|
|
public static readonly StyledProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
|
|
AvaloniaProperty.Register<DataGrid, ScrollBarVisibility>(nameof(HorizontalScrollBarVisibility));
|
|
|
|
public static readonly StyledProperty<bool> IsReadOnlyProperty =
|
|
AvaloniaProperty.Register<DataGrid, bool>(nameof(IsReadOnly));
|
|
|
|
public static readonly StyledProperty<bool> AreRowGroupHeadersFrozenProperty =
|
|
AvaloniaProperty.Register<DataGrid, bool>(
|
|
nameof(AreRowGroupHeadersFrozen),
|
|
defaultValue: true);
|
|
|
|
public static readonly DirectProperty<DataGrid, bool> IsValidProperty =
|
|
AvaloniaProperty.RegisterDirect<DataGrid, bool>(
|
|
nameof(IsValid),
|
|
o => o.IsValid);
|
|
|
|
public static readonly StyledProperty<double> MaxColumnWidthProperty =
|
|
AvaloniaProperty.Register<DataGrid, double>(
|
|
nameof(MaxColumnWidth),
|
|
defaultValue: DefaultMaxColumnWidth,
|
|
validate: IsValidColumnWidth);
|
|
|
|
public static readonly StyledProperty<double> MinColumnWidthProperty =
|
|
AvaloniaProperty.Register<DataGrid, double>(
|
|
nameof(MinColumnWidth),
|
|
defaultValue: DefaultMinColumnWidth,
|
|
validate: IsValidMinColumnWidth);
|
|
|
|
public static readonly AttachedProperty<bool> IsScrollInertiaEnabledProperty =
|
|
ScrollViewer.IsScrollInertiaEnabledProperty.AddOwner<DataGrid>();
|
|
|
|
public static readonly StyledProperty<double> RowHeightProperty =
|
|
AvaloniaProperty.Register<DataGrid, double>(
|
|
nameof(RowHeight),
|
|
defaultValue: double.NaN,
|
|
validate: IsValidRowHeight);
|
|
|
|
public static readonly StyledProperty<double> RowHeaderWidthProperty =
|
|
AvaloniaProperty.Register<DataGrid, double>(
|
|
nameof(RowHeaderWidth),
|
|
defaultValue: double.NaN,
|
|
validate: IsValidRowHeaderWidth);
|
|
|
|
public static readonly StyledProperty<DataGridSelectionMode> SelectionModeProperty =
|
|
AvaloniaProperty.Register<DataGrid, DataGridSelectionMode>(nameof(SelectionMode));
|
|
|
|
public static readonly StyledProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty =
|
|
AvaloniaProperty.Register<DataGrid, ScrollBarVisibility>(nameof(VerticalScrollBarVisibility));
|
|
|
|
public static readonly StyledProperty<ITemplate<Control>> DropLocationIndicatorTemplateProperty =
|
|
AvaloniaProperty.Register<DataGrid, ITemplate<Control>>(nameof(DropLocationIndicatorTemplate));
|
|
|
|
public static readonly DirectProperty<DataGrid, int> SelectedIndexProperty =
|
|
AvaloniaProperty.RegisterDirect<DataGrid, int>(
|
|
nameof(SelectedIndex),
|
|
o => o.SelectedIndex,
|
|
(o, v) => o.SelectedIndex = v,
|
|
defaultBindingMode: BindingMode.TwoWay);
|
|
|
|
public static readonly DirectProperty<DataGrid, object?> SelectedItemProperty =
|
|
AvaloniaProperty.RegisterDirect<DataGrid, object?>(
|
|
nameof(SelectedItem),
|
|
o => o.SelectedItem,
|
|
(o, v) => o.SelectedItem = v,
|
|
defaultBindingMode: BindingMode.TwoWay);
|
|
|
|
public static readonly StyledProperty<DataGridClipboardCopyMode> ClipboardCopyModeProperty =
|
|
AvaloniaProperty.Register<DataGrid, DataGridClipboardCopyMode>(
|
|
nameof(ClipboardCopyMode),
|
|
defaultValue: DataGridClipboardCopyMode.ExcludeHeader);
|
|
|
|
public static readonly StyledProperty<bool> AutoGenerateColumnsProperty =
|
|
AvaloniaProperty.Register<DataGrid, bool>(nameof(AutoGenerateColumns));
|
|
|
|
public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
|
|
AvaloniaProperty.Register<DataGrid, IEnumerable?>(nameof(ItemsSource));
|
|
|
|
public static readonly StyledProperty<bool> AreRowDetailsFrozenProperty =
|
|
AvaloniaProperty.Register<DataGrid, bool>(nameof(AreRowDetailsFrozen));
|
|
|
|
public static readonly StyledProperty<IDataTemplate?> RowDetailsTemplateProperty =
|
|
AvaloniaProperty.Register<DataGrid, IDataTemplate?>(nameof(RowDetailsTemplate));
|
|
|
|
public static readonly StyledProperty<DataGridRowDetailsVisibilityMode> RowDetailsVisibilityModeProperty =
|
|
AvaloniaProperty.Register<DataGrid, DataGridRowDetailsVisibilityMode>(nameof(RowDetailsVisibilityMode));
|
|
|
|
public static readonly DirectProperty<DataGrid, IDataGridCollectionView?> CollectionViewProperty =
|
|
AvaloniaProperty.RegisterDirect<DataGrid, IDataGridCollectionView?>(nameof(CollectionView),
|
|
o => o.CollectionView);
|
|
|
|
public static readonly StyledProperty<bool> IsMotionEnabledProperty
|
|
= WaveSpiritAwareControlProperty.IsMotionEnabledProperty.AddOwner<DataGrid>();
|
|
|
|
public SizeType SizeType
|
|
{
|
|
get => GetValue(SizeTypeProperty);
|
|
set => SetValue(SizeTypeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates whether the user can change
|
|
/// the column display order by dragging column headers with the mouse.
|
|
/// </summary>
|
|
public bool CanUserReorderColumns
|
|
{
|
|
get => GetValue(CanUserReorderColumnsProperty);
|
|
set => SetValue(CanUserReorderColumnsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates whether the user can adjust column widths using the mouse.
|
|
/// </summary>
|
|
public bool CanUserResizeColumns
|
|
{
|
|
get => GetValue(CanUserResizeColumnsProperty);
|
|
set => SetValue(CanUserResizeColumnsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates whether the user can sort columns by clicking the column header.
|
|
/// </summary>
|
|
public bool CanUserSortColumns
|
|
{
|
|
get => GetValue(CanUserSortColumnsProperty);
|
|
set => SetValue(CanUserSortColumnsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the height of the column headers row.
|
|
/// </summary>
|
|
public double ColumnHeaderHeight
|
|
{
|
|
get => GetValue(ColumnHeaderHeightProperty);
|
|
set => SetValue(ColumnHeaderHeightProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the standard width or automatic sizing mode of columns in the control.
|
|
/// </summary>
|
|
public DataGridLength ColumnWidth
|
|
{
|
|
get => GetValue(ColumnWidthProperty);
|
|
set => SetValue(ColumnWidthProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the theme applied to all cells.
|
|
/// </summary>
|
|
public ControlTheme CellTheme
|
|
{
|
|
get => GetValue(CellThemeProperty);
|
|
set => SetValue(CellThemeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the theme applied to all row groups.
|
|
/// </summary>
|
|
public ControlTheme RowGroupTheme
|
|
{
|
|
get => GetValue(RowGroupThemeProperty);
|
|
set => SetValue(RowGroupThemeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of columns that the user cannot scroll horizontally.
|
|
/// </summary>
|
|
public int FrozenColumnCount
|
|
{
|
|
get => GetValue(FrozenColumnCountProperty);
|
|
set => SetValue(FrozenColumnCountProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates which grid lines separating inner cells are shown.
|
|
/// </summary>
|
|
public DataGridGridLinesVisibility GridLinesVisibility
|
|
{
|
|
get => GetValue(GridLinesVisibilityProperty);
|
|
set => SetValue(GridLinesVisibilityProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates the visibility of row and column headers.
|
|
/// </summary>
|
|
public DataGridHeadersVisibility HeadersVisibility
|
|
{
|
|
get => GetValue(HeadersVisibilityProperty);
|
|
set => SetValue(HeadersVisibilityProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates how the horizontal scroll bar is displayed.
|
|
/// </summary>
|
|
public ScrollBarVisibility HorizontalScrollBarVisibility
|
|
{
|
|
get => GetValue(HorizontalScrollBarVisibilityProperty);
|
|
set => SetValue(HorizontalScrollBarVisibilityProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates whether the user can edit the values in the control.
|
|
/// </summary>
|
|
public bool IsReadOnly
|
|
{
|
|
get => GetValue(IsReadOnlyProperty);
|
|
set => SetValue(IsReadOnlyProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public bool AreRowGroupHeadersFrozen
|
|
{
|
|
get => GetValue(AreRowGroupHeadersFrozenProperty);
|
|
set => SetValue(AreRowGroupHeadersFrozenProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether scroll gestures should include inertia in their behavior and value.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the maximum width of columns in the <see cref="T:AtomUI.Controls.DataGrid" /> .
|
|
/// </summary>
|
|
public double MaxColumnWidth
|
|
{
|
|
get => GetValue(MaxColumnWidthProperty);
|
|
set => SetValue(MaxColumnWidthProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the minimum width of columns in the <see cref="T:AtomUI.Controls.DataGrid" />.
|
|
/// </summary>
|
|
public double MinColumnWidth
|
|
{
|
|
get => GetValue(MinColumnWidthProperty);
|
|
set => SetValue(MinColumnWidthProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the standard height of rows in the control.
|
|
/// </summary>
|
|
public double RowHeight
|
|
{
|
|
get => GetValue(RowHeightProperty);
|
|
set => SetValue(RowHeightProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the width of the row header column.
|
|
/// </summary>
|
|
public double RowHeaderWidth
|
|
{
|
|
get => GetValue(RowHeaderWidthProperty);
|
|
set => SetValue(RowHeaderWidthProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates how the vertical scroll bar is displayed.
|
|
/// </summary>
|
|
public ScrollBarVisibility VerticalScrollBarVisibility
|
|
{
|
|
get => GetValue(VerticalScrollBarVisibilityProperty);
|
|
set => SetValue(VerticalScrollBarVisibilityProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the selection behavior of the data grid.
|
|
/// </summary>
|
|
public DataGridSelectionMode SelectionMode
|
|
{
|
|
get => GetValue(SelectionModeProperty);
|
|
set => SetValue(SelectionModeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the template that is used when rendering the column headers.
|
|
/// </summary>
|
|
public ITemplate<Control> DropLocationIndicatorTemplate
|
|
{
|
|
get => GetValue(DropLocationIndicatorTemplateProperty);
|
|
set => SetValue(DropLocationIndicatorTemplateProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the index of the current selection.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The index of the current selection, or -1 if the selection is empty.
|
|
/// </returns>
|
|
public int SelectedIndex
|
|
{
|
|
get => _selectedIndex;
|
|
set => SetAndRaise(SelectedIndexProperty, ref _selectedIndex, value);
|
|
}
|
|
private int _selectedIndex = -1;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the data item corresponding to the selected row.
|
|
/// </summary>
|
|
public object? SelectedItem
|
|
{
|
|
get => _selectedItem;
|
|
set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
|
|
}
|
|
|
|
private object? _selectedItem;
|
|
|
|
/// <summary>
|
|
/// The property which determines how DataGrid content is copied to the Clipboard.
|
|
/// </summary>
|
|
public DataGridClipboardCopyMode ClipboardCopyMode
|
|
{
|
|
get => GetValue(ClipboardCopyModeProperty);
|
|
set => SetValue(ClipboardCopyModeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates whether columns are created
|
|
/// automatically when the <see cref="P:AtomUI.Controls.DataGrid.ItemsSource" /> property is set.
|
|
/// </summary>
|
|
public bool AutoGenerateColumns
|
|
{
|
|
get => GetValue(AutoGenerateColumnsProperty);
|
|
set => SetValue(AutoGenerateColumnsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a collection that is used to generate the content of the control.
|
|
/// </summary>
|
|
public IEnumerable? ItemsSource
|
|
{
|
|
get => GetValue(ItemsSourceProperty);
|
|
set => SetValue(ItemsSourceProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public bool AreRowDetailsFrozen
|
|
{
|
|
get => GetValue(AreRowDetailsFrozenProperty);
|
|
set => SetValue(AreRowDetailsFrozenProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the template that is used to display the content of the details section of rows.
|
|
/// </summary>
|
|
public IDataTemplate? RowDetailsTemplate
|
|
{
|
|
get => GetValue(RowDetailsTemplateProperty);
|
|
set => SetValue(RowDetailsTemplateProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value that indicates when the details sections of rows are displayed.
|
|
/// </summary>
|
|
public DataGridRowDetailsVisibilityMode RowDetailsVisibilityMode
|
|
{
|
|
get => GetValue(RowDetailsVisibilityModeProperty);
|
|
set => SetValue(RowDetailsVisibilityModeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets current <see cref="IDataGridCollectionView"/>.
|
|
/// </summary>
|
|
public IDataGridCollectionView? CollectionView =>
|
|
DataConnection.CollectionView;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the column that contains the current cell.
|
|
/// </summary>
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
public ObservableCollection<DataGridColumn> Columns => ColumnsInternal;
|
|
|
|
public ObservableCollection<IDataGridColumnGroupItem> ColumnGroups => ColumnGroupsInternal;
|
|
|
|
/// <summary>
|
|
/// Gets a list that contains the data items corresponding to the selected rows.
|
|
/// </summary>
|
|
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<SelectionChangedEventArgs> SelectionChangedEvent =
|
|
RoutedEvent.Register<DataGrid, SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble);
|
|
|
|
/// <summary>
|
|
/// Occurs one time for each public, non-static property in the bound data type when the
|
|
/// <see cref="P:AtomUI.Controls.DataGrid.ItemsSource" /> property is changed and the
|
|
/// <see cref="P:AtomUI.Controls.DataGrid.AutoGenerateColumns" /> property is true.
|
|
/// </summary>
|
|
public event EventHandler<DataGridAutoGeneratingColumnEventArgs>? AutoGeneratingColumn;
|
|
|
|
/// <summary>
|
|
/// Occurs before a cell or row enters editing mode.
|
|
/// </summary>
|
|
public event EventHandler<DataGridBeginningEditEventArgs>? BeginningEdit;
|
|
|
|
/// <summary>
|
|
/// Occurs after cell editing has ended.
|
|
/// </summary>
|
|
public event EventHandler<DataGridCellEditEndedEventArgs>? CellEditEnded;
|
|
|
|
/// <summary>
|
|
/// Occurs immediately before cell editing has ended.
|
|
/// </summary>
|
|
public event EventHandler<DataGridCellEditEndingEventArgs>? CellEditEnding;
|
|
|
|
/// <summary>
|
|
/// Occurs when cell is mouse-pressed.
|
|
/// </summary>
|
|
public event EventHandler<DataGridCellPointerPressedEventArgs>? CellPointerPressed;
|
|
|
|
/// <summary>
|
|
/// Occurs when the <see cref="P:AtomUI.Controls.DataGridColumn.DisplayIndex" />
|
|
/// property of a column changes.
|
|
/// </summary>
|
|
public event EventHandler<DataGridColumnEventArgs>? ColumnDisplayIndexChanged;
|
|
|
|
/// <summary>
|
|
/// Raised when column reordering ends, to allow subscribers to clean up.
|
|
/// </summary>
|
|
public event EventHandler<DataGridColumnEventArgs>? ColumnReordered;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public event EventHandler<DataGridColumnReorderingEventArgs>? ColumnReordering;
|
|
|
|
/// <summary>
|
|
/// Occurs when a different cell becomes the current cell.
|
|
/// </summary>
|
|
public event EventHandler<EventArgs>? CurrentCellChanged;
|
|
|
|
/// <summary>
|
|
/// Occurs after a <see cref="T:AtomUI.Controls.DataGridRow" />
|
|
/// is instantiated, so that you can customize it before it is used.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowEventArgs>? LoadingRow;
|
|
|
|
/// <summary>
|
|
/// Occurs when a cell in a <see cref="T:AtomUI.Controls.DataGridTemplateColumn" /> enters editing mode.
|
|
///
|
|
/// </summary>
|
|
public event EventHandler<DataGridPreparingCellForEditEventArgs>? PreparingCellForEdit;
|
|
|
|
/// <summary>
|
|
/// Occurs when the row has been successfully committed or cancelled.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowEditEndedEventArgs>? RowEditEnded;
|
|
|
|
/// <summary>
|
|
/// Occurs immediately before the row has been successfully committed or cancelled.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowEditEndingEventArgs>? RowEditEnding;
|
|
|
|
/// <summary>
|
|
/// Occurs when the <see cref="P:AtomUI.Controls.DataGrid.SelectedItem" /> or
|
|
/// <see cref="P:AtomUI.Controls.DataGrid.SelectedItems" /> property value changes.
|
|
/// </summary>
|
|
public event EventHandler<SelectionChangedEventArgs>? SelectionChanged
|
|
{
|
|
add => AddHandler(SelectionChangedEvent, value);
|
|
remove => RemoveHandler(SelectionChangedEvent, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when the <see cref="DataGridColumn"/> sorting request is triggered.
|
|
/// </summary>
|
|
public event EventHandler<DataGridColumnEventArgs>? Sorting;
|
|
|
|
/// <summary>
|
|
/// Occurs when a <see cref="T:AtomUI.Controls.DataGridRow" />
|
|
/// object becomes available for reuse.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowEventArgs>? UnloadingRow;
|
|
|
|
/// <summary>
|
|
/// Occurs when a new row details template is applied to a row, so that you can customize
|
|
/// the details section before it is used.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowDetailsEventArgs>? LoadingRowDetails;
|
|
|
|
/// <summary>
|
|
/// Occurs when the <see cref="P:AtomUI.Controls.DataGrid.RowDetailsVisibilityMode" />
|
|
/// property value changes.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowDetailsEventArgs>? RowDetailsVisibilityChanged;
|
|
|
|
/// <summary>
|
|
/// Occurs when a row details element becomes available for reuse.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowDetailsEventArgs>? UnloadingRowDetails;
|
|
|
|
public event EventHandler<ScrollEventArgs>? HorizontalScroll;
|
|
public event EventHandler<ScrollEventArgs>? VerticalScroll;
|
|
|
|
/// <summary>
|
|
/// Occurs before a DataGridRowGroupHeader header is used.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowGroupHeaderEventArgs>? LoadingRowGroup;
|
|
|
|
/// <summary>
|
|
/// Occurs when the DataGridRowGroupHeader is available for reuse.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowGroupHeaderEventArgs>? UnloadingRowGroup;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public event EventHandler<DataGridRowClipboardEventArgs>? CopyingRowClipboardContent;
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Gets the data item bound to the row that contains the current cell.
|
|
/// </summary>
|
|
protected object? CurrentItem
|
|
{
|
|
get
|
|
{
|
|
if (CurrentSlot == -1 || ItemsSource == null || RowGroupHeadersTable.Contains(CurrentSlot))
|
|
{
|
|
return null;
|
|
}
|
|
return DataConnection.GetDataItem(RowIndexFromSlot(CurrentSlot));
|
|
}
|
|
}
|
|
|
|
static DataGrid()
|
|
{
|
|
AffectsMeasure<DataGrid>(
|
|
ColumnHeaderHeightProperty,
|
|
HorizontalScrollBarVisibilityProperty,
|
|
VerticalScrollBarVisibilityProperty);
|
|
|
|
SizeTypeProperty.OverrideDefaultValue<DataGrid>(SizeType.Large);
|
|
|
|
ItemsSourceProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleItemsSourcePropertyChanged(e));
|
|
CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleCanUserResizeColumnsChanged(e));
|
|
ColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleColumnWidthChanged(e));
|
|
FrozenColumnCountProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleFrozenColumnCountChanged(e));
|
|
GridLinesVisibilityProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleGridLinesVisibilityChanged(e));
|
|
HeadersVisibilityProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleHeadersVisibilityChanged(e));
|
|
IsReadOnlyProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleIsReadOnlyChanged(e));
|
|
MaxColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleMaxColumnWidthChanged(e));
|
|
MinColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleMinColumnWidthChanged(e));
|
|
RowHeightProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleRowHeightChanged(e));
|
|
RowHeaderWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleRowHeaderWidthChanged(e));
|
|
SelectionModeProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleSelectionModeChanged(e));
|
|
SelectedIndexProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleSelectedIndexChanged(e));
|
|
SelectedItemProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleSelectedItemChanged(e));
|
|
IsEnabledProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleIsEnabledChanged(e));
|
|
AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleAreRowGroupHeadersFrozenChanged(e));
|
|
RowDetailsTemplateProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleRowDetailsTemplateChanged(e));
|
|
RowDetailsVisibilityModeProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleRowDetailsVisibilityModeChanged(e));
|
|
AutoGenerateColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.HandleAutoGenerateColumnsChanged(e));
|
|
|
|
FocusableProperty.OverrideDefaultValue<DataGrid>(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<DataGridRow>();
|
|
_lostFocusActions = new Queue<Action>();
|
|
_selectedItems = new DataGridSelectedItemsCollection(this);
|
|
RowGroupHeadersTable = new IndexToValueTable<DataGridRowGroupInfo>();
|
|
_bindingValidationErrors = new List<Exception>();
|
|
|
|
DisplayData = new DataGridDisplayData(this);
|
|
ColumnGroupsInternal = new ObservableCollection<IDataGridColumnGroupItem>();
|
|
ColumnsInternal = CreateColumnsInstance();
|
|
ColumnsInternal.CollectionChanged += HandleColumnsInternalCollectionChanged;
|
|
ColumnGroupsInternal.CollectionChanged += HandleGroupColumnsInternalCollectionChanged;
|
|
RowHeightEstimate = DefaultRowHeight;
|
|
RowDetailsHeightEstimate = 0;
|
|
_rowHeaderDesiredWidth = 0;
|
|
|
|
DataConnection = new DataGridDataConnection(this);
|
|
_showDetailsTable = new IndexToValueTable<bool>();
|
|
_collapsedSlotsTable = new IndexToValueTable<bool>();
|
|
|
|
AnchorSlot = -1;
|
|
_lastEstimatedRow = -1;
|
|
_editingColumnIndex = -1;
|
|
_mouseOverRowIndex = null;
|
|
|
|
RowGroupHeaderHeightEstimate = DefaultRowHeight;
|
|
RowGroupSublevelIndents = [];
|
|
_rowGroupHeightsByLevel = [];
|
|
_resourceBindingsDisposable = new CompositeDisposable();
|
|
UpdatePseudoClasses();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enters editing mode for the current cell and current row (if they're not already in editing mode).
|
|
/// </summary>
|
|
/// <param name="editingEventArgs">Provides information about the user gesture that caused the call to BeginEdit. Can be null.</param>
|
|
/// <returns>True if operation was successful. False otherwise.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancels editing mode for the specified DataGridEditingUnit and restores its original value.
|
|
/// </summary>
|
|
/// <param name="editingUnit">Specifies whether to cancel edit for a Cell or Row.</param>
|
|
/// <returns>True if operation was successful. False otherwise.</returns>
|
|
public bool CancelEdit(DataGridEditingUnit editingUnit = DataGridEditingUnit.Row)
|
|
{
|
|
return CancelEdit(editingUnit, raiseEvents: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Commits editing mode and pushes changes to the backend.
|
|
/// </summary>
|
|
/// <returns>True if operation was successful. False otherwise.</returns>
|
|
public bool CommitEdit()
|
|
{
|
|
return CommitEdit(DataGridEditingUnit.Row, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Commits editing mode for the specified DataGridEditingUnit and pushes changes to the backend.
|
|
/// </summary>
|
|
/// <param name="editingUnit">Specifies whether to commit edit for a Cell or Row.</param>
|
|
/// <param name="exitEditingMode">Editing mode is left if True.</param>
|
|
/// <returns>True if operation was successful. False otherwise.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="item">an item from the DataGrid's items source or a CollectionViewGroup from the collection view</param>
|
|
/// <param name="column">a column from the DataGrid's columns collection</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Arranges the content of the <see cref="T:AtomUI.Controls.DataGridRow" />.
|
|
/// </summary>
|
|
/// <param name="finalSize">
|
|
/// The final area within the parent that this element should use to arrange itself and its children.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The actual size used by the <see cref="T:AtomUI.Controls.DataGridRow" />.
|
|
/// </returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Measures the children of a <see cref="T:AtomUI.Controls.DataGridRow" /> to prepare for
|
|
/// arranging them during the
|
|
/// <see cref="M:AtomUI.Controls.DataGridRow.ArrangeOverride(System.Windows.Size)" /> pass.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The size that the <see cref="T:AtomUI.Controls.DataGridRow" /> determines it needs during layout, based on its calculations of child object allocated sizes.
|
|
/// </returns>
|
|
/// <param name="availableSize">
|
|
/// The available size that this element can give to child elements. Indicates an upper limit that
|
|
/// child elements should not exceed.
|
|
/// </param>
|
|
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;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnDataContextBeginUpdate()
|
|
{
|
|
base.OnDataContextBeginUpdate();
|
|
|
|
NotifyDataContextPropertyForAllRowCells(GetAllRows(), true);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnDataContextEndUpdate()
|
|
{
|
|
base.OnDataContextEndUpdate();
|
|
|
|
NotifyDataContextPropertyForAllRowCells(GetAllRows(), false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the BeginningEdit event.
|
|
/// </summary>
|
|
protected virtual void NotifyBeginningEdit(DataGridBeginningEditEventArgs e)
|
|
{
|
|
BeginningEdit?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the CellEditEnded event.
|
|
/// </summary>
|
|
protected virtual void NotifyCellEditEnded(DataGridCellEditEndedEventArgs e)
|
|
{
|
|
CellEditEnded?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the CellEditEnding event.
|
|
/// </summary>
|
|
protected virtual void NotifyCellEditEnding(DataGridCellEditEndingEventArgs e)
|
|
{
|
|
CellEditEnding?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the CellPointerPressed event.
|
|
/// </summary>
|
|
internal virtual void NotifyCellPointerPressed(DataGridCellPointerPressedEventArgs e)
|
|
{
|
|
CellPointerPressed?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the CurrentCellChanged event.
|
|
/// </summary>
|
|
protected virtual void NotifyCurrentCellChanged(EventArgs e)
|
|
{
|
|
CurrentCellChanged?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the LoadingRow event for row preparation.
|
|
/// </summary>
|
|
protected virtual void NotifyLoadingRow(DataGridRowEventArgs e)
|
|
{
|
|
EventHandler<DataGridRowEventArgs>? 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls the DataGrid according to the direction of the delta.
|
|
/// </summary>
|
|
/// <param name="e">PointerWheelEventArgs</param>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the PreparingCellForEdit event.
|
|
/// </summary>
|
|
protected virtual void NotifyPreparingCellForEdit(DataGridPreparingCellForEditEventArgs e)
|
|
{
|
|
PreparingCellForEdit?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the RowEditEnded event.
|
|
/// </summary>
|
|
protected virtual void NotifyRowEditEnded(DataGridRowEditEndedEventArgs e)
|
|
{
|
|
RowEditEnded?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the RowEditEnding event.
|
|
/// </summary>
|
|
protected virtual void NotifyRowEditEnding(DataGridRowEditEndingEventArgs e)
|
|
{
|
|
RowEditEnding?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the SelectionChanged event and clears the _selectionChanged.
|
|
/// This event won't get raised again until after _selectionChanged is set back to true.
|
|
/// </summary>
|
|
protected virtual void NotifySelectionChanged(SelectionChangedEventArgs e)
|
|
{
|
|
RaiseEvent(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the UnloadingRow event for row recycling.
|
|
/// </summary>
|
|
protected virtual void NotifyUnloadingRow(DataGridRowEventArgs e)
|
|
{
|
|
EventHandler<DataGridRowEventArgs>? handler = UnloadingRow;
|
|
if (handler != null)
|
|
{
|
|
LoadingOrUnloadingRow = true;
|
|
handler(this, e);
|
|
LoadingOrUnloadingRow = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Comparator class so we can sort list by the display index
|
|
/// </summary>
|
|
public class DisplayIndexComparer : IComparer<DataGridColumn>
|
|
{
|
|
int IComparer<DataGridColumn>.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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds the visual tree for the column header when a new template is applied.
|
|
/// </summary>
|
|
//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<DataGridColumnHeadersPresenter>(DataGridTheme.ColumnHeadersPresenterPart);
|
|
_groupHeaderView = e.NameScope.Find<DataGridHeaderView>(DataGridTheme.ColumnHeaderViewPart);
|
|
|
|
if (ColumnGroups.Count > 0)
|
|
{
|
|
if (_groupHeaderView != 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<DataGridColumn>(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<DataGridRowsPresenter>(DataGridTheme.RowsPresenterPart);
|
|
|
|
if (_rowsPresenter != null)
|
|
{
|
|
_rowsPresenter.OwningGrid = this;
|
|
InvalidateRowHeightEstimate();
|
|
UpdateRowDetailsHeightEstimate();
|
|
}
|
|
|
|
_frozenColumnScrollBarSpacer = e.NameScope.Find<Control>(DataGridTheme.FrozenColumnScrollBarSpacerPart);
|
|
|
|
if (_hScrollBar != null)
|
|
{
|
|
_hScrollBar.Scroll -= HandleHorizontalScrollBarScroll;
|
|
}
|
|
|
|
_hScrollBar = e.NameScope.Find<ScrollBar>(DataGridTheme.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<ScrollBar>(DataGridTheme.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<ContentControl>(DataGridTheme.TopLeftCornerPart);
|
|
EnsureTopLeftCornerHeader(); // EnsureTopLeftCornerHeader checks for a null _topLeftCornerHeader;
|
|
_topRightCornerHeader = e.NameScope.Find<ContentControl>(DataGridTheme.TopRightCornerPart);
|
|
_bottomRightCorner = e.NameScope.Find<Visual>(DataGridTheme.BottomRightCornerPart);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the LoadingRowDetails for row details preparation
|
|
/// </summary>
|
|
protected virtual void NotifyLoadingRowDetails(DataGridRowDetailsEventArgs e)
|
|
{
|
|
EventHandler<DataGridRowDetailsEventArgs>? handler = LoadingRowDetails;
|
|
if (handler != null)
|
|
{
|
|
LoadingOrUnloadingRow = true;
|
|
handler(this, e);
|
|
LoadingOrUnloadingRow = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the UnloadingRowDetails event
|
|
/// </summary>
|
|
protected virtual void NotifyUnloadingRowDetails(DataGridRowDetailsEventArgs e)
|
|
{
|
|
EventHandler<DataGridRowDetailsEventArgs>? handler = UnloadingRowDetails;
|
|
if (handler != null)
|
|
{
|
|
LoadingOrUnloadingRow = true;
|
|
handler(this, e);
|
|
LoadingOrUnloadingRow = false;
|
|
}
|
|
}
|
|
|
|
public void SelectAll()
|
|
{
|
|
SetRowsSelection(0, SlotCount - 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the Group at the indicated level or null if the item is not in the ItemsSource
|
|
/// </summary>
|
|
/// <param name="item">item</param>
|
|
/// <param name="groupLevel">groupLevel</param>
|
|
/// <returns>The group the given item falls under or null if the item is not in the ItemsSource</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the LoadingRowGroup event
|
|
/// </summary>
|
|
/// <param name="e">EventArgs</param>
|
|
protected virtual void OnLoadingRowGroup(DataGridRowGroupHeaderEventArgs e)
|
|
{
|
|
EventHandler<DataGridRowGroupHeaderEventArgs>? handler = LoadingRowGroup;
|
|
if (handler != null)
|
|
{
|
|
LoadingOrUnloadingRow = true;
|
|
handler(this, e);
|
|
LoadingOrUnloadingRow = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the UnLoadingRowGroup event
|
|
/// </summary>
|
|
/// <param name="e">EventArgs</param>
|
|
protected virtual void OnUnloadingRowGroup(DataGridRowGroupHeaderEventArgs e)
|
|
{
|
|
EventHandler<DataGridRowGroupHeaderEventArgs>? handler = UnloadingRowGroup;
|
|
if (handler != null)
|
|
{
|
|
LoadingOrUnloadingRow = true;
|
|
handler(this, e);
|
|
LoadingOrUnloadingRow = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method raises the CopyingRowClipboardContent event.
|
|
/// </summary>
|
|
/// <param name="e">Contains the necessary information for generating the row clipboard content.</param>
|
|
protected virtual void OnCopyingRowClipboardContent(DataGridRowClipboardEventArgs e)
|
|
{
|
|
CopyingRowClipboardContent?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the AutoGeneratingColumn event.
|
|
/// </summary>
|
|
protected virtual void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
|
|
{
|
|
AutoGeneratingColumn?.Invoke(this, e);
|
|
}
|
|
} |