Browse Source

Merge pull request #5222 from antrv/fixes/datagrid-dve

DataGrid: Extracting error data from DataValidationException
pull/5283/head
Max Katz 6 years ago
committed by GitHub
parent
commit
e1a8bb2daa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 107
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  2. 25
      src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs

107
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<DataGrid, bool>(nameof(CanUserReorderColumns));
/// <summary>
/// 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.
/// </summary>
public bool CanUserReorderColumns
@ -247,8 +247,8 @@ namespace Avalonia.Controls
/// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint the background of odd-numbered rows.
/// </summary>
/// <returns>
/// The brush that is used to paint the background of odd-numbered rows. The default is a
/// <see cref="T:System.Windows.Media.SolidColorBrush" /> with a
/// The brush that is used to paint the background of odd-numbered rows. The default is a
/// <see cref="T:System.Windows.Media.SolidColorBrush" /> with a
/// <see cref="P:System.Windows.Media.SolidColorBrush.Color" /> value of white (ARGB value #00FFFFFF).
/// </returns>
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
}
/// <summary>
/// Gets or sets the maximum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" /> .
/// Gets or sets the maximum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" /> .
/// </summary>
public double MaxColumnWidth
{
@ -418,7 +418,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the minimum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" />.
/// Gets or sets the minimum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" />.
/// </summary>
public double MinColumnWidth
{
@ -496,7 +496,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, IBrush>(nameof(VerticalGridLinesBrush));
/// <summary>
/// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint grid lines separating columns.
/// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint grid lines separating columns.
/// </summary>
public IBrush VerticalGridLinesBrush
{
@ -542,7 +542,7 @@ namespace Avalonia.Controls
/// </summary>
/// <returns>
/// The index of the current selection, or -1 if the selection is empty.
/// </returns>
/// </returns>
public int SelectedIndex
{
get { return _selectedIndex; }
@ -582,7 +582,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(AutoGenerateColumns));
/// <summary>
/// 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 <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is set.
/// </summary>
public bool AutoGenerateColumns
@ -626,7 +626,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(AreRowDetailsFrozen));
/// <summary>
/// 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.
/// </summary>
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
}
/// <summary>
/// Occurs one time for each public, non-static property in the bound data type when the
/// <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is changed and the
/// Occurs one time for each public, non-static property in the bound data type when the
/// <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is changed and the
/// <see cref="P:Avalonia.Controls.DataGrid.AutoGenerateColumns" /> property is true.
/// </summary>
public event EventHandler<DataGridAutoGeneratingColumnEventArgs> AutoGeneratingColumn;
/// <summary>
/// Occurs before a cell or row enters editing mode.
/// Occurs before a cell or row enters editing mode.
/// </summary>
public event EventHandler<DataGridBeginningEditEventArgs> BeginningEdit;
@ -1195,7 +1195,7 @@ namespace Avalonia.Controls
public event EventHandler<DataGridCellPointerPressedEventArgs> CellPointerPressed;
/// <summary>
/// Occurs when the <see cref="P:Avalonia.Controls.DataGridColumn.DisplayIndex" />
/// Occurs when the <see cref="P:Avalonia.Controls.DataGridColumn.DisplayIndex" />
/// property of a column changes.
/// </summary>
public event EventHandler<DataGridColumnEventArgs> ColumnDisplayIndexChanged;
@ -1218,14 +1218,14 @@ namespace Avalonia.Controls
public event EventHandler<EventArgs> CurrentCellChanged;
/// <summary>
/// Occurs after a <see cref="T:Avalonia.Controls.DataGridRow" />
/// Occurs after a <see cref="T:Avalonia.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:Avalonia.Controls.DataGridTemplateColumn" /> enters editing mode.
///
///
/// </summary>
public event EventHandler<DataGridPreparingCellForEditEventArgs> PreparingCellForEdit;
@ -1243,7 +1243,7 @@ namespace Avalonia.Controls
RoutedEvent.Register<DataGrid, SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble);
/// <summary>
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.SelectedItem" /> or
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.SelectedItem" /> or
/// <see cref="P:Avalonia.Controls.DataGrid.SelectedItems" /> property value changes.
/// </summary>
public event EventHandler<SelectionChangedEventArgs> SelectionChanged
@ -1258,19 +1258,19 @@ namespace Avalonia.Controls
public event EventHandler<DataGridColumnEventArgs> Sorting;
/// <summary>
/// Occurs when a <see cref="T:Avalonia.Controls.DataGridRow" />
/// Occurs when a <see cref="T:Avalonia.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
/// 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:Avalonia.Controls.DataGrid.RowDetailsVisibilityMode" />
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.RowDetailsVisibilityMode" />
/// property value changes.
/// </summary>
public event EventHandler<DataGridRowDetailsEventArgs> RowDetailsVisibilityChanged;
@ -1282,7 +1282,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets a collection that contains all the columns in the control.
/// </summary>
/// </summary>
public ObservableCollection<DataGridColumn> 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
}
/// <summary>
/// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to prepare for
/// arranging them during the
/// <see cref="M:Avalonia.Controls.DataGridRow.ArrangeOverride(System.Windows.Size)" /> pass.
/// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to prepare for
/// arranging them during the
/// <see cref="M:Avalonia.Controls.DataGridRow.ArrangeOverride(System.Windows.Size)" /> pass.
/// </summary>
/// <returns>
/// The size that the <see cref="T:Avalonia.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
/// 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
// 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
}
/// <summary>
/// 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.
/// </summary>
//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;

25
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<Exception>)new[] { exception } :
aggregate.InnerExceptions;
var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
var exceptions = exception is AggregateException aggregate ?
aggregate.InnerExceptions :
(IEnumerable<Exception>)new[] { exception };
if (filtered.Count > 0)
{
return filtered;
}
return exceptions.Where(x => !(x is BindingChainException)).ToList();
}
return null;
return Array.Empty<Exception>();
}
public static object UnpackDataValidationException(Exception exception)
{
if (exception is DataValidationException dataValidationException)
{
return dataValidationException.ErrorData;
}
return exception;
}
/// <summary>

Loading…
Cancel
Save