From 2384b07ab88b7edb927a6969d1298af66294bf13 Mon Sep 17 00:00:00 2001 From: Anton Ryzhov Date: Thu, 24 Dec 2020 22:00:52 +0200 Subject: [PATCH] DataGrid: Extracting error data from DataValidationException #5222 --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 107 +++++++++--------- .../Utils/ValidationUtil.cs | 25 ++-- 2 files changed, 69 insertions(+), 63 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 5bb2763566..6687c59aa4 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -1,6 +1,6 @@ // 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. +// All other rights reserved. using Avalonia.Collections; using Avalonia.Controls.Primitives; @@ -92,7 +92,7 @@ namespace Avalonia.Controls private ContentControl _topRightCornerHeader; private Control _frozenColumnScrollBarSpacer; - // the sum of the widths in pixels of the scrolling columns preceding + // the sum of the widths in pixels of the scrolling columns preceding // the first displayed scrolling column private double _horizontalOffset; @@ -143,7 +143,7 @@ namespace Avalonia.Controls private object _uneditedValue; // Represents the original current cell value at the time it enters editing mode. private ICellEditBinding _currentCellEditBinding; - // An approximation of the sum of the heights in pixels of the scrolling rows preceding + // An approximation of the sum of the heights in pixels of the scrolling rows preceding // the first displayed scrolling row. Since the scrolled off rows are discarded, the grid // does not know their actual height. The heights used for the approximation are the ones // set as the rows were scrolled off. @@ -162,7 +162,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(CanUserReorderColumns)); /// - /// Gets or sets a value that indicates whether the user can change + /// 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 @@ -247,8 +247,8 @@ namespace Avalonia.Controls /// Gets or sets the that is used to paint the background of odd-numbered rows. /// /// - /// The brush that is used to paint the background of odd-numbered rows. The default is a - /// with a + /// The brush that is used to paint the background of odd-numbered rows. The default is a + /// with a /// value of white (ARGB value #00FFFFFF). /// public IBrush AlternatingRowBackground @@ -379,8 +379,8 @@ namespace Avalonia.Controls public bool IsValid { get { return _isValid; } - internal set - { + internal set + { SetAndRaise(IsValidProperty, ref _isValid, value); PseudoClasses.Set(":invalid", !value); } @@ -398,7 +398,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the maximum width of columns in the . + /// Gets or sets the maximum width of columns in the . /// public double MaxColumnWidth { @@ -418,7 +418,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the minimum width of columns in the . + /// Gets or sets the minimum width of columns in the . /// public double MinColumnWidth { @@ -496,7 +496,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(VerticalGridLinesBrush)); /// - /// Gets or sets the that is used to paint grid lines separating columns. + /// Gets or sets the that is used to paint grid lines separating columns. /// public IBrush VerticalGridLinesBrush { @@ -542,7 +542,7 @@ namespace Avalonia.Controls /// /// /// The index of the current selection, or -1 if the selection is empty. - /// + /// public int SelectedIndex { get { return _selectedIndex; } @@ -582,7 +582,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(AutoGenerateColumns)); /// - /// Gets or sets a value that indicates whether columns are created + /// Gets or sets a value that indicates whether columns are created /// automatically when the property is set. /// public bool AutoGenerateColumns @@ -626,7 +626,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(AreRowDetailsFrozen)); /// - /// Gets or sets a value that indicates whether the row details sections remain + /// Gets or sets a value that indicates whether the row details sections remain /// fixed at the width of the display area or can scroll horizontally. /// public bool AreRowDetailsFrozen @@ -881,7 +881,7 @@ namespace Avalonia.Controls { int index = (int)e.NewValue; - // GetDataItem returns null if index is >= Count, we do not check newValue + // GetDataItem returns null if index is >= Count, we do not check newValue // against Count here to avoid enumerating through an Enumerable twice // Setting SelectedItem coerces the finally value of the SelectedIndex object newSelectedItem = (index < 0) ? null : DataConnection.GetDataItem(index); @@ -1168,14 +1168,14 @@ namespace Avalonia.Controls } /// - /// Occurs one time for each public, non-static property in the bound data type when the - /// property is changed and the + /// 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. + /// Occurs before a cell or row enters editing mode. /// public event EventHandler BeginningEdit; @@ -1195,7 +1195,7 @@ namespace Avalonia.Controls public event EventHandler CellPointerPressed; /// - /// Occurs when the + /// Occurs when the /// property of a column changes. /// public event EventHandler ColumnDisplayIndexChanged; @@ -1218,14 +1218,14 @@ namespace Avalonia.Controls public event EventHandler CurrentCellChanged; /// - /// Occurs after a + /// 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; @@ -1243,7 +1243,7 @@ namespace Avalonia.Controls RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble); /// - /// Occurs when the or + /// Occurs when the or /// property value changes. /// public event EventHandler SelectionChanged @@ -1258,19 +1258,19 @@ namespace Avalonia.Controls public event EventHandler Sorting; /// - /// Occurs when a + /// 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 + /// 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 + /// Occurs when the /// property value changes. /// public event EventHandler RowDetailsVisibilityChanged; @@ -1282,7 +1282,7 @@ namespace Avalonia.Controls /// /// Gets a collection that contains all the columns in the control. - /// + /// public ObservableCollection Columns { get @@ -1456,7 +1456,7 @@ namespace Avalonia.Controls } // Height currently available for cells this value is smaller. This height is reduced by the existence of ColumnHeaders - // or a horizontal scrollbar. Layout is asynchronous so changes to the ColumnHeaders or the horizontal scrollbar are + // or a horizontal scrollbar. Layout is asynchronous so changes to the ColumnHeaders or the horizontal scrollbar are // not reflected immediately. internal double CellsHeight { @@ -1555,7 +1555,7 @@ namespace Avalonia.Controls internal static double HorizontalGridLinesThickness => DATAGRID_horizontalGridLinesThickness; - // the sum of the widths in pixels of the scrolling columns preceding + // the sum of the widths in pixels of the scrolling columns preceding // the first displayed scrolling column internal double HorizontalOffset { @@ -2083,20 +2083,20 @@ namespace Avalonia.Controls } /// - /// Measures the children of a to prepare for - /// arranging them during the - /// pass. + /// 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 + /// 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 + // Delay layout until after the initial measure to avoid invalid calculations when the // DataGrid is not part of the visual tree if (!_measured) { @@ -3006,7 +3006,7 @@ namespace Avalonia.Controls /// If the editing element has focus, this method will set focus to the DataGrid itself /// in order to force the element to lose focus. It will then wait for the editing element's /// LostFocus event, at which point it will perform the specified action. - /// + /// /// NOTE: It is important to understand that the specified action will be performed when the editing /// element loses focus only if this method returns true. If it returns false, then the action /// will not be performed later on, and should instead be performed by the caller, if necessary. @@ -3065,7 +3065,7 @@ namespace Avalonia.Controls { if (!_scrollingByHeight) { - // Update layout when RowDetails are expanded or collapsed, just updating the vertical scroll bar is not enough + // Update layout when RowDetails are expanded or collapsed, just updating the vertical scroll bar is not enough // since rows could be added or removed InvalidateMeasure(); } @@ -3278,7 +3278,7 @@ namespace Avalonia.Controls { // Current cell was reset because the commit deleted row(s). // Since the user wants to change the current cell, we don't - // want to end up with no current cell. We pick the last row + // want to end up with no current cell. We pick the last row // in the grid which may be the 'new row'. int lastSlot = LastVisibleSlot; if (forCurrentCellChange && @@ -3336,7 +3336,7 @@ namespace Avalonia.Controls if (_ignoreNextScrollBarsLayout) { _ignoreNextScrollBarsLayout = false; - // + // } @@ -3393,7 +3393,7 @@ namespace Avalonia.Controls } // Now cellsWidth is the width potentially available for displaying data cells. - // Now cellsHeight is the height potentially available for displaying data cells. + // Now cellsHeight is the height potentially available for displaying data cells. bool needHorizScrollbar = false; bool needVertScrollbar = false; @@ -3418,7 +3418,7 @@ namespace Avalonia.Controls Debug.Assert(cellsHeight >= 0); needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true; - if (vertScrollBarWidth > 0 && + if (vertScrollBarWidth > 0 && allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) || MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth))) { @@ -3458,7 +3458,7 @@ namespace Avalonia.Controls // we compute the number of visible columns only after we set up the vertical scroll bar. ComputeDisplayedColumns(); - if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) && + if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) && allowHorizScrollbar && needVertScrollbar && !needHorizScrollbar && MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) && @@ -3963,7 +3963,8 @@ namespace Avalonia.Controls { var errorList = binding.ValidationErrors - .SelectMany(ex => ValidationUtil.UnpackException(ex)) + .SelectMany(ValidationUtil.UnpackException) + .Select(ValidationUtil.UnpackDataValidationException) .ToList(); DataValidationErrors.SetErrors(editingElement, errorList); @@ -4124,7 +4125,7 @@ namespace Avalonia.Controls } /// - /// Exits editing mode without trying to commit or revert the editing, and + /// Exits editing mode without trying to commit or revert the editing, and /// without repopulating the edited row's cell. /// //TODO TabStop @@ -5103,9 +5104,9 @@ namespace Avalonia.Controls { if (ctrl || _editingColumnIndex == -1 || IsReadOnly) { - //Go to the next/previous control on the page when + //Go to the next/previous control on the page when // - Ctrl key is used - // - Potential current cell is not edited, or the datagrid is read-only. + // - Potential current cell is not edited, or the datagrid is read-only. return false; } @@ -5516,11 +5517,11 @@ namespace Avalonia.Controls // v---v //|<|_____|###|>| // ^ ^ - // min max + // min max // we want to make the relative size of the thumb reflect the relative size of the viewing area // viewportSize / (max + viewportSize) = cellsWidth / max - // -> viewportSize = max * cellsWidth / (max - cellsWidth) + // -> viewportSize = max * cellsWidth / (max - cellsWidth) // always zero _hScrollBar.Minimum = 0; @@ -5572,7 +5573,7 @@ namespace Avalonia.Controls _hScrollBar.Maximum = 0; if (_hScrollBar.IsVisible) { - // This will trigger a call to this method via Cells_SizeChanged for + // This will trigger a call to this method via Cells_SizeChanged for // which no processing is needed. _hScrollBar.IsVisible = false; _ignoreNextScrollBarsLayout = true; @@ -5591,14 +5592,14 @@ namespace Avalonia.Controls // v---v //|<|_____|###|>| // ^ ^ - // min max + // min max // we want to make the relative size of the thumb reflect the relative size of the viewing area // viewportSize / (max + viewportSize) = cellsWidth / max // -> viewportSize = max * cellsHeight / (totalVisibleHeight - cellsHeight) // -> = max * cellsHeight / (totalVisibleHeight - cellsHeight) // -> = max * cellsHeight / max - // -> = cellsHeight + // -> = cellsHeight // always zero _vScrollBar.Minimum = 0; @@ -5621,7 +5622,7 @@ namespace Avalonia.Controls if (!_vScrollBar.IsVisible) { - // This will trigger a call to this method via Cells_SizeChanged for + // This will trigger a call to this method via Cells_SizeChanged for // which no processing is needed. _vScrollBar.IsVisible = true; if (_vScrollBar.DesiredSize.Width == 0) @@ -5637,7 +5638,7 @@ namespace Avalonia.Controls _vScrollBar.Maximum = 0; if (_vScrollBar.IsVisible) { - // This will trigger a call to this method via Cells_SizeChanged for + // This will trigger a call to this method via Cells_SizeChanged for // which no processing is needed. _vScrollBar.IsVisible = false; _ignoreNextScrollBarsLayout = true; @@ -5660,8 +5661,8 @@ namespace Avalonia.Controls Debug.Assert(slot >= 0); // Before changing selection, check if the current cell needs to be committed, and - // check if the current row needs to be committed. If any of those two operations are required and fail, - // do not change selection, and do not change current cell. + // check if the current row needs to be committed. If any of those two operations are required and fail, + // do not change selection, and do not change current cell. bool wasInEdit = EditingColumnIndex != -1; diff --git a/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs b/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs index e1ecb2c562..c36d9708c1 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs @@ -80,19 +80,24 @@ namespace Avalonia.Controls.Utils { if (exception != null) { - var aggregate = exception as AggregateException; - var exceptions = aggregate == null ? - (IEnumerable)new[] { exception } : - aggregate.InnerExceptions; - var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList(); + var exceptions = exception is AggregateException aggregate ? + aggregate.InnerExceptions : + (IEnumerable)new[] { exception }; - if (filtered.Count > 0) - { - return filtered; - } + return exceptions.Where(x => !(x is BindingChainException)).ToList(); } - return null; + return Array.Empty(); + } + + public static object UnpackDataValidationException(Exception exception) + { + if (exception is DataValidationException dataValidationException) + { + return dataValidationException.ErrorData; + } + + return exception; } ///