Browse Source

fixing data validation for Combobox control and all SelectingItems control #5652

pull/5653/head
aljosas 5 years ago
parent
commit
899ba64913
  1. 28
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  2. 110
      tests/Avalonia.Controls.UnitTests/CarouselTests.cs
  3. 65
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  4. 7
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

28
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives
nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay);
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
/// <summary>
/// Defines the <see cref="SelectedItems"/> property.
@ -466,6 +466,20 @@ namespace Avalonia.Controls.Primitives
EndUpdating();
}
/// <summary>
/// Called to update the validation state for properties for which data validation is
/// enabled.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The new binding value for the property.</param>
protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
{
if (property == SelectedItemProperty)
{
DataValidationErrors.SetError(this, value.Error);
}
}
protected override void OnInitialized()
{
base.OnInitialized();
@ -503,6 +517,7 @@ namespace Avalonia.Controls.Primitives
{
AutoScrollToSelectedItemIfNecessary();
}
if (change.Property == ItemsProperty && _updateState is null && _selection is object)
{
var newValue = change.NewValue.GetValueOrDefault<IEnumerable>();
@ -707,7 +722,7 @@ namespace Avalonia.Controls.Primitives
_oldSelectedItem = SelectedItem;
}
else if (e.PropertyName == nameof(InternalSelectionModel.WritableSelectedItems) &&
_oldSelectedItems != (Selection as InternalSelectionModel)?.SelectedItems)
_oldSelectedItems != (Selection as InternalSelectionModel)?.SelectedItems)
{
RaisePropertyChanged(
SelectedItemsProperty,
@ -853,10 +868,7 @@ namespace Avalonia.Controls.Primitives
private ISelectionModel CreateDefaultSelectionModel()
{
return new InternalSelectionModel
{
SingleSelect = !SelectionMode.HasFlagCustom(SelectionMode.Multiple),
};
return new InternalSelectionModel { SingleSelect = !SelectionMode.HasFlagCustom(SelectionMode.Multiple), };
}
private void InitializeSelectionModel(ISelectionModel model)
@ -977,7 +989,7 @@ namespace Avalonia.Controls.Primitives
public Optional<ISelectionModel> Selection { get; set; }
public Optional<IList?> SelectedItems { get; set; }
public Optional<int> SelectedIndex
public Optional<int> SelectedIndex
{
get => _selectedIndex;
set
@ -996,6 +1008,6 @@ namespace Avalonia.Controls.Primitives
_selectedIndex = default;
}
}
}
}
}
}

110
tests/Avalonia.Controls.UnitTests/CarouselTests.cs

@ -1,10 +1,14 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -16,12 +20,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = new[]
{
"Foo",
"Bar"
}
Template = new FuncControlTemplate<Carousel>(CreateTemplate), Items = new[] { "Foo", "Bar" }
};
target.ApplyTemplate();
@ -35,12 +34,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = new[]
{
"Foo",
"Bar"
}
Template = new FuncControlTemplate<Carousel>(CreateTemplate), Items = new[] { "Foo", "Bar" }
};
target.ApplyTemplate();
@ -75,18 +69,11 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var items = new ObservableCollection<string> { "Foo", "Bar", "FooBar" };
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
Template = new FuncControlTemplate<Carousel>(CreateTemplate), Items = items, IsVirtualized = false
};
target.ApplyTemplate();
@ -111,18 +98,11 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes_And_Virtualized()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var items = new ObservableCollection<string> { "Foo", "Bar", "FooBar" };
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = true,
Template = new FuncControlTemplate<Carousel>(CreateTemplate), Items = items, IsVirtualized = true,
};
target.ApplyTemplate();
@ -150,9 +130,7 @@ namespace Avalonia.Controls.UnitTests
var items = new ObservableCollection<string>();
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
Template = new FuncControlTemplate<Carousel>(CreateTemplate), Items = items, IsVirtualized = false
};
target.ApplyTemplate();
@ -170,18 +148,11 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Selected_Index_Changes_To_None_When_Items_Assigned_Null()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var items = new ObservableCollection<string> { "Foo", "Bar", "FooBar" };
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
Template = new FuncControlTemplate<Carousel>(CreateTemplate), Items = items, IsVirtualized = false
};
target.ApplyTemplate();
@ -204,12 +175,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Selected_Index_Is_Maintained_Carousel_Created_With_Non_Zero_SelectedIndex()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var items = new ObservableCollection<string> { "Foo", "Bar", "FooBar" };
var target = new Carousel
{
@ -233,18 +199,11 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Selected_Item_Changes_To_Next_First_Item_When_Item_Removed_From_Beggining_Of_List()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var items = new ObservableCollection<string> { "Foo", "Bar", "FooBar" };
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
Template = new FuncControlTemplate<Carousel>(CreateTemplate), Items = items, IsVirtualized = false
};
target.ApplyTemplate();
@ -267,18 +226,11 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Selected_Item_Changes_To_First_Item_If_SelectedItem_Is_Removed_From_Middle()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var items = new ObservableCollection<string> { "Foo", "Bar", "FooBar" };
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
Template = new FuncControlTemplate<Carousel>(CreateTemplate), Items = items, IsVirtualized = false
};
target.ApplyTemplate();
@ -311,5 +263,31 @@ namespace Avalonia.Controls.UnitTests
contentPresenter.UpdateChild();
return Assert.IsType<TextBlock>(contentPresenter.Child);
}
[Fact]
public void SelectedItem_Validation()
{
using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
{
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate), IsVirtualized = false
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var exception = new System.InvalidCastException("failed validation");
var textObservable =
new BehaviorSubject<BindingNotification>(new BindingNotification(exception,
BindingErrorType.DataValidationError));
target.Bind(ComboBox.SelectedItemProperty, textObservable);
Dispatcher.UIThread.RunJobs();
Assert.True(DataValidationErrors.GetHasErrors(target));
Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
}
}
}
}

65
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -1,11 +1,14 @@
using System.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Xunit;
@ -173,5 +176,67 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(expectedSelectedIndex, target.SelectedIndex);
}
}
[Fact]
public void SelectedItem_Validation()
{
using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
{
var target = new ComboBox
{
Template = GetTemplate()
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var exception = new System.InvalidCastException("failed validation");
var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError));
target.Bind(ComboBox.SelectedItemProperty, textObservable);
Dispatcher.UIThread.RunJobs();
Assert.True(DataValidationErrors.GetHasErrors(target));
Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
}
}
private ComboBox CreateControl()
{
var control = new ComboBox()
{
Template = GetTemplate()
};
control.ApplyTemplate();
return control;
}
private TextBox GetTextBox(ComboBox control)
{
return control.GetTemplateChildren()
// .OfType<ButtonSpinner>()
// .Select(b => b.Content)
.OfType<TextBox>()
.First();
}
// private IControlTemplate CreateTemplate()
// {
// return new FuncControlTemplate<ComboBox>((control, scope) =>
// {
// var textBox =
// new TextBox
// {
// Name = "PART_TextBox"
// }.RegisterInNameScope(scope);
// return new ButtonSpinner
// {
// Name = "PART_Spinner",
// Content = textBox,
// }.RegisterInNameScope(scope);
// });
// }
}
}

7
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -559,5 +559,12 @@ namespace Avalonia.Controls.UnitTests
public string Value { get; }
}
[Fact]
public void SelectedItem_Validation()
{
}
}
}

Loading…
Cancel
Save