Skip to content

Commit 207dc98

Browse files
committed
Switched ObservableValidator.ValidateProperty to protected
1 parent c021cdf commit 207dc98

File tree

2 files changed

+111
-1
lines changed

2 files changed

+111
-1
lines changed

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,11 +364,12 @@ IEnumerable<ValidationResult> GetAllErrors()
364364

365365
/// <summary>
366366
/// Validates a property with a specified name and a given input value.
367+
/// If any changes are detected, the <see cref="ErrorsChanged"/> event will be raised.
367368
/// </summary>
368369
/// <param name="value">The value to test for the specified property.</param>
369370
/// <param name="propertyName">The name of the property to validate.</param>
370371
/// <exception cref="ArgumentNullException">Thrown when <paramref name="propertyName"/> is <see langword="null"/>.</exception>
371-
private void ValidateProperty(object? value, string? propertyName)
372+
protected void ValidateProperty(object? value, [CallerMemberName] string? propertyName = null)
372373
{
373374
if (propertyName is null)
374375
{

UnitTests/UnitTests.Shared/Mvvm/Test_ObservableValidator.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Collections.Generic;
67
using System.ComponentModel;
78
using System.ComponentModel.DataAnnotations;
@@ -207,6 +208,60 @@ public void Test_ObservableValidator_TrySetProperty()
207208
Assert.AreEqual(model.Name, "This is fine");
208209
}
209210

211+
[TestCategory("Mvvm")]
212+
[TestMethod]
213+
public void Test_ObservableValidator_ValidateProperty()
214+
{
215+
var model = new ComparableModel();
216+
var events = new List<DataErrorsChangedEventArgs>();
217+
218+
model.ErrorsChanged += (s, e) => events.Add(e);
219+
220+
// Set a correct value for both properties, first A then B
221+
model.A = 42;
222+
model.B = 30;
223+
224+
Assert.AreEqual(events.Count, 0);
225+
Assert.IsFalse(model.HasErrors);
226+
227+
// Make B greater than A, hence invalidating A
228+
model.B = 50;
229+
230+
Assert.AreEqual(events.Count, 1);
231+
Assert.AreEqual(events.Last().PropertyName, nameof(ComparableModel.A));
232+
Assert.IsTrue(model.HasErrors);
233+
234+
events.Clear();
235+
236+
// Make A greater than B, hence making it valid again
237+
model.A = 51;
238+
239+
Assert.AreEqual(events.Count, 1);
240+
Assert.AreEqual(events.Last().PropertyName, nameof(ComparableModel.A));
241+
Assert.AreEqual(model.GetErrors(nameof(ComparableModel.A)).Count(), 0);
242+
Assert.IsFalse(model.HasErrors);
243+
244+
events.Clear();
245+
246+
// Make A smaller than B, hence invalidating it
247+
model.A = 49;
248+
249+
Assert.AreEqual(events.Count, 1);
250+
Assert.AreEqual(events.Last().PropertyName, nameof(ComparableModel.A));
251+
Assert.AreEqual(model.GetErrors(nameof(ComparableModel.A)).Count(), 1);
252+
Assert.IsTrue(model.HasErrors);
253+
254+
events.Clear();
255+
256+
// Lower B, hence making A valid again
257+
model.B = 20;
258+
259+
Assert.AreEqual(events.Count, 1);
260+
Assert.AreEqual(events.Last().PropertyName, nameof(ComparableModel.A));
261+
Assert.AreEqual(model.GetErrors(nameof(ComparableModel.A)).Count(), 0);
262+
Assert.IsFalse(model.HasErrors);
263+
}
264+
210265
public class Person : ObservableValidator
211266
{
212267
private string name;
@@ -234,5 +289,59 @@ public int Age
234289
set => SetProperty(ref this.age, value, true);
235290
}
236291
}
292+
293+
/// <summary>
294+
/// Test model for linked properties, to test <see cref="ObservableValidator.ValidateProperty(object?, string?)"/> instance.
295+
/// See https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3665 for the original request for this feature.
296+
/// </summary>
297+
public class ComparableModel : ObservableValidator
298+
{
299+
private int a;
300+
301+
[Range(10, 100)]
302+
[GreaterThan(nameof(B))]
303+
public int A
304+
{
305+
get => this.a;
306+
set => SetProperty(ref this.a, value, true);
307+
}
308+
309+
private int b;
310+
311+
[Range(20, 80)]
312+
public int B
313+
{
314+
get => this.b;
315+
set
316+
{
317+
SetProperty(ref this.b, value, true);
318+
ValidateProperty(A, nameof(A));
319+
}
320+
}
321+
}
322+
323+
public sealed class GreaterThanAttribute : ValidationAttribute
324+
{
325+
public GreaterThanAttribute(string propertyName)
326+
{
327+
PropertyName = propertyName;
328+
}
329+
330+
public string PropertyName { get; }
331+
332+
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
333+
{
334+
object
335+
instance = validationContext.ObjectInstance,
336+
otherValue = instance.GetType().GetProperty(PropertyName).GetValue(instance);
337+
338+
if (((IComparable)value).CompareTo(otherValue) > 0)
339+
{
340+
return ValidationResult.Success;
341+
}
342+
343+
return new ValidationResult("The current value is smaller than the other one");
344+
}
345+
}
237346
}
238347
}

0 commit comments

Comments
 (0)