浏览代码

Merge pull request #5653 from aljosas/fixComboBoxDataValidationSelected

fixing data validation for Combobox control and all SelectingItemsControl-s
Max Katz 4 年之前
父节点
当前提交
f33e0de004

+ 18 - 4
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();
@@ -707,7 +721,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,
@@ -977,7 +991,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 +1010,6 @@ namespace Avalonia.Controls.Primitives
                     _selectedIndex = default;
                 }
             }
-       }
+        }
     }
 }

+ 46 - 18
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
@@ -77,9 +81,9 @@ namespace Avalonia.Controls.UnitTests
         {
             var items = new ObservableCollection<string>
             {
-               "Foo",
-               "Bar",
-               "FooBar"
+                "Foo",
+                "Bar",
+                "FooBar"
             };
 
             var target = new Carousel
@@ -113,9 +117,9 @@ namespace Avalonia.Controls.UnitTests
         {
             var items = new ObservableCollection<string>
             {
-               "Foo",
-               "Bar",
-               "FooBar"
+                "Foo",
+                "Bar",
+                "FooBar"
             };
 
             var target = new Carousel
@@ -172,9 +176,9 @@ namespace Avalonia.Controls.UnitTests
         {
             var items = new ObservableCollection<string>
             {
-               "Foo",
-               "Bar",
-               "FooBar"
+                "Foo",
+                "Bar",
+                "FooBar"
             };
 
             var target = new Carousel
@@ -206,9 +210,9 @@ namespace Avalonia.Controls.UnitTests
         {
             var items = new ObservableCollection<string>
             {
-               "Foo",
-               "Bar",
-               "FooBar"
+                "Foo",
+                "Bar",
+                "FooBar"
             };
 
             var target = new Carousel
@@ -235,9 +239,9 @@ namespace Avalonia.Controls.UnitTests
         {
             var items = new ObservableCollection<string>
             {
-               "Foo",
-               "Bar",
-               "FooBar"
+                "Foo",
+                "Bar",
+                "FooBar"
             };
 
             var target = new Carousel
@@ -269,9 +273,9 @@ namespace Avalonia.Controls.UnitTests
         {
             var items = new ObservableCollection<string>
             {
-               "Foo",
-               "Bar",
-               "FooBar"
+                "Foo",
+                "Bar",
+                "FooBar"
             };
 
             var target = new Carousel
@@ -311,5 +315,29 @@ 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);
+
+                Assert.True(DataValidationErrors.GetHasErrors(target));
+                Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
+            }
+        }
     }
 }

+ 29 - 0
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,31 @@ 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(),
+                    VirtualizationMode =  ItemVirtualizationMode.None
+                };
+
+                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);
+
+                Assert.True(DataValidationErrors.GetHasErrors(target));
+                Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
+                
+            }
+            
+        } 
     }
 }

+ 26 - 0
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@@ -1,11 +1,14 @@
 using System.Linq;
+using System.Reactive.Subjects;
 using Avalonia.Collections;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.LogicalTree;
 using Avalonia.Styling;
+using Avalonia.Threading;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Xunit;
@@ -559,5 +562,28 @@ namespace Avalonia.Controls.UnitTests
 
             public string Value { get; }
         }
+
+
+        [Fact]
+        public void SelectedItem_Validation()
+        {
+            var target = new ListBox
+            {
+                Template = ListBoxTemplate(),
+                Items = new[] { "Foo" },
+                ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
+                SelectionMode = SelectionMode.AlwaysSelected,
+                VirtualizationMode = ItemVirtualizationMode.None
+            };
+
+            Prepare(target);
+            
+            var exception = new System.InvalidCastException("failed validation");
+            var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError));
+            target.Bind(ComboBox.SelectedItemProperty, textObservable);
+                
+            Assert.True(DataValidationErrors.GetHasErrors(target));
+            Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
+        }
     }
 }