Skip to content

Commit 85df605

Browse files
Move InputValidationAdorner from sample to Custom Adorner in NuGet Package
Makes more type-safe and easier to use based on community feedback by placing context on Adorner itself (NotifyDataErrorInfo property) - akin to EditableObject sample change as well. Allows for string collection of validation messages as well as existing ValidationResult behavior. Separate out InputValidationAdorner into its own doc TODO: Issue with form clearing newly corrected fields...
1 parent 5e37f1b commit 85df605

File tree

8 files changed

+235
-176
lines changed

8 files changed

+235
-176
lines changed

components/Adorners/samples/Adorners.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,7 @@ The following example uses `IEditableObject` to control the editing lifecycle co
6060
6161
Adorners are template-based controls, but you can use a class-backed resource dictionary to better enable usage of x:Bind for easier creation and binding to the `AdornedElement`, as seen here.
6262

63-
## Input Validation Example
64-
65-
The custom adorner example above can be further extended to provide input validation feedback to the user using the standard `INotifyDataErrorInfo` interface.
66-
We use the `ObservableValidator` class from the `CommunityToolkit.Mvvm` package to provide validation rules for our view model properties.
67-
When the user submits invalid input, the adorner displays a red border around the text box and shows a tooltip with the validation error message:
68-
69-
> [!SAMPLE InputValidationAdornerSample]
63+
You can see other example of custom adorners with the other Adorner help topics for the built-in adorners provided in this package, such as the `InputValidationAdorner`.
7064

7165
## TODO: Resize Example
7266

components/Adorners/samples/InputValidation/InputValidationAdornerSample.xaml

Lines changed: 13 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,53 +6,22 @@
66
xmlns:local="using:AdornersExperiment.Samples.InputValidation"
77
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
88
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
9+
xmlns:adorners="using:CommunityToolkit.WinUI.Adorners"
910
xmlns:ui="using:CommunityToolkit.WinUI"
1011
mc:Ignorable="d">
1112

12-
<Page.Resources>
13-
<local:InputValidationAdornerResources />
14-
<!--
15-
Inner style copied here from above for convenience of the sample
16-
<Grid Visibility="{x:Bind HasValidationFailed, Mode=OneWay}" >
17-
<Rectangle Margin="-4"
18-
HorizontalAlignment="Stretch"
19-
VerticalAlignment="Stretch"
20-
RadiusX="4"
21-
RadiusY="4"
22-
Stroke="Red"
23-
StrokeThickness="1"/>
24-
25-
<muxc:InfoBadge Width="20"
26-
MinHeight="20"
27-
Margin="0,0,-32,0"
28-
HorizontalAlignment="Right"
29-
VerticalAlignment="Center"
30-
Background="{ThemeResource SystemFillColorCriticalBrush}">
31-
<muxc:InfoBadge.IconSource>
32-
<muxc:FontIconSource FontFamily="{ThemeResource SymbolThemeFontFamily}"
33-
Glyph="&#xF13C;" />
34-
</muxc:InfoBadge.IconSource>
35-
<ToolTipService.ToolTip>
36-
<ToolTip Content="{x:Bind ValidationMessage, Mode=OneWay}" />
37-
</ToolTipService.ToolTip>
38-
</muxc:InfoBadge>
39-
</Grid>
40-
-->
41-
</Page.Resources>
42-
4313
<StackPanel HorizontalAlignment="Left"
4414
Spacing="16">
4515
<TextBlock Text="Please fill out the form below and click Submit:" />
4616

47-
<!-- We set the DataContext here for our Adorner to retrieve the IEditableObject reference -->
17+
<!-- We set the DataContext here for our Adorner to retrieve the INotifyDataErrorInfo reference -->
4818
<!-- We use TwoWay binding to ensure the updates from the Adorner are reflected in the ViewModel -->
49-
<TextBox DataContext="{x:Bind ViewModel}"
50-
Header="Enter your first name:"
19+
<TextBox Header="Enter your first name:"
5120
PlaceholderText="First name"
5221
Text="{x:Bind ViewModel.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
5322
<ui:AdornerLayer.Xaml>
54-
<local:InputValidationAdorner PropertyName="FirstName"
55-
Style="{StaticResource DefaultInputValidationAdornerStyle}" />
23+
<adorners:InputValidationAdorner PropertyName="FirstName"
24+
NotifyDataErrorInfo="{x:Bind ViewModel}" />
5625
</ui:AdornerLayer.Xaml>
5726
</TextBox>
5827

@@ -61,8 +30,8 @@
6130
PlaceholderText="Last name"
6231
Text="{x:Bind ViewModel.LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
6332
<ui:AdornerLayer.Xaml>
64-
<local:InputValidationAdorner PropertyName="LastName"
65-
Style="{StaticResource DefaultInputValidationAdornerStyle}" />
33+
<adorners:InputValidationAdorner PropertyName="LastName"
34+
NotifyDataErrorInfo="{x:Bind ViewModel}" />
6635
</ui:AdornerLayer.Xaml>
6736
</TextBox>
6837

@@ -71,8 +40,8 @@
7140
PlaceholderText="Email"
7241
Text="{x:Bind ViewModel.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
7342
<ui:AdornerLayer.Xaml>
74-
<local:InputValidationAdorner PropertyName="Email"
75-
Style="{StaticResource DefaultInputValidationAdornerStyle}" />
43+
<adorners:InputValidationAdorner PropertyName="Email"
44+
NotifyDataErrorInfo="{x:Bind ViewModel}" />
7645
</ui:AdornerLayer.Xaml>
7746
</TextBox>
7847

@@ -81,8 +50,8 @@
8150
PlaceholderText="Phone number"
8251
Text="{x:Bind ViewModel.PhoneNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
8352
<ui:AdornerLayer.Xaml>
84-
<local:InputValidationAdorner PropertyName="PhoneNumber"
85-
Style="{StaticResource DefaultInputValidationAdornerStyle}" />
53+
<adorners:InputValidationAdorner PropertyName="PhoneNumber"
54+
NotifyDataErrorInfo="{x:Bind ViewModel}" />
8655
</ui:AdornerLayer.Xaml>
8756
</TextBox>
8857

@@ -94,8 +63,8 @@
9463
SpinButtonPlacementMode="Inline"
9564
Value="{x:Bind ViewModel.Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
9665
<ui:AdornerLayer.Xaml>
97-
<local:InputValidationAdorner PropertyName="Age"
98-
Style="{StaticResource DefaultInputValidationAdornerStyle}" />
66+
<adorners:InputValidationAdorner PropertyName="Age"
67+
NotifyDataErrorInfo="{x:Bind ViewModel}" />
9968
</ui:AdornerLayer.Xaml>
10069
</muxc:NumberBox>
10170

components/Adorners/samples/InputValidation/InputValidationAdornerSample.xaml.cs

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -76,119 +76,3 @@ private void Submit()
7676
}
7777
}
7878
}
79-
80-
/// <summary>
81-
/// An Adorner that shows an error message if Data Validation fails.
82-
/// The adorned control's <see cref="FrameworkElement.DataContext"/> must implement <see cref="INotifyDataErrorInfo"/>. It assumes that the return of <see cref="INotifyDataErrorInfo.GetErrors(string?)"/> is a <see cref="ValidationResult"/> collection.
83-
/// </summary>
84-
public sealed partial class InputValidationAdorner : Adorner<FrameworkElement>
85-
{
86-
/// <summary>
87-
/// Gets or sets the name of the property this adorner should look for errors on.
88-
/// </summary>
89-
public string PropertyName
90-
{
91-
get { return (string)GetValue(PropertyNameProperty); }
92-
set { SetValue(PropertyNameProperty, value); }
93-
}
94-
95-
/// <summary>
96-
/// Identifies the <see cref="PropertyName"/> dependency property.
97-
/// </summary>
98-
public static readonly DependencyProperty PropertyNameProperty =
99-
DependencyProperty.Register(nameof(PropertyName), typeof(string), typeof(InputValidationAdorner), new PropertyMetadata(null));
100-
101-
/// <summary>
102-
/// Gets or sets whether the popup is open.
103-
/// </summary>
104-
public bool HasValidationFailed
105-
{
106-
get { return (bool)GetValue(HasValidationFailedProperty); }
107-
set { SetValue(HasValidationFailedProperty, value); }
108-
}
109-
110-
/// <summary>
111-
/// Identifies the <see cref="HasValidationFailed"/> dependency property.
112-
/// </summary>
113-
public static readonly DependencyProperty HasValidationFailedProperty =
114-
DependencyProperty.Register(nameof(HasValidationFailed), typeof(bool), typeof(InputValidationAdorner), new PropertyMetadata(false));
115-
116-
/// <summary>
117-
/// Gets or sets the validation message for this failed property.
118-
/// </summary>
119-
public string ValidationMessage
120-
{
121-
get { return (string)GetValue(ValidationMessageProperty); }
122-
set { SetValue(ValidationMessageProperty, value); }
123-
}
124-
125-
/// <summary>
126-
/// Identifies the <see cref="ValidationMessage"/> dependency property.
127-
/// </summary>
128-
public static readonly DependencyProperty ValidationMessageProperty =
129-
DependencyProperty.Register(nameof(ValidationMessage), typeof(string), typeof(InputValidationAdorner), new PropertyMetadata(null));
130-
131-
private INotifyDataErrorInfo? _dataErrorInfo;
132-
133-
public InputValidationAdorner()
134-
{
135-
this.DefaultStyleKey = typeof(InputValidationAdorner);
136-
137-
// Uno workaround
138-
DataContext = this;
139-
}
140-
141-
protected override void OnApplyTemplate()
142-
{
143-
base.OnApplyTemplate();
144-
}
145-
146-
protected override void OnAttached()
147-
{
148-
base.OnAttached();
149-
150-
if (AdornedElement?.DataContext is INotifyDataErrorInfo iError)
151-
{
152-
_dataErrorInfo = iError;
153-
_dataErrorInfo.ErrorsChanged += this.INotifyDataErrorInfo_ErrorsChanged;
154-
}
155-
}
156-
157-
private void INotifyDataErrorInfo_ErrorsChanged(object? sender, DataErrorsChangedEventArgs e)
158-
{
159-
if (_dataErrorInfo is not null)
160-
{
161-
// Reset state
162-
if (!_dataErrorInfo.HasErrors)
163-
{
164-
HasValidationFailed = false;
165-
ValidationMessage = string.Empty;
166-
return;
167-
}
168-
169-
if (e.PropertyName == PropertyName)
170-
{
171-
HasValidationFailed = true;
172-
173-
StringBuilder message = new();
174-
foreach (ValidationResult result in _dataErrorInfo.GetErrors(e.PropertyName))
175-
{
176-
message.AppendLine(result.ErrorMessage);
177-
}
178-
179-
ValidationMessage = message.ToString().Trim();
180-
}
181-
}
182-
}
183-
184-
protected override void OnDetaching()
185-
{
186-
if (_dataErrorInfo is not null)
187-
{
188-
_dataErrorInfo.ErrorsChanged -= this.INotifyDataErrorInfo_ErrorsChanged;
189-
_dataErrorInfo = null;
190-
}
191-
192-
base.OnDetaching();
193-
}
194-
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: InputValidationAdorner
3+
author: michael-hawker
4+
description: An InputValidationAdorner provides input validation to any element implementing INotifyDataErrorInfo to provide feedback to a user.
5+
keywords: Adorners, Input Validation, INotifyDataErrorInfo, MVVM, CommunityToolkit.Mvvm
6+
dev_langs:
7+
- csharp
8+
category: Controls
9+
subcategory: Layout
10+
discussion-id: 278
11+
issue-id: 0
12+
icon: assets/icon.png
13+
---
14+
15+
# InputValidationAdorner
16+
17+
The `InputValidationAdorner` provides input validation to any element implementing `INotifyDataErrorInfo` to provide feedback to a user.
18+
19+
## Background
20+
21+
Input Validation existed in WPF and was available in a couple of ways. See the Migrating from WPF section below for more details on the differences between WPF and WinUI Input Validation.
22+
23+
## Input Validation Example
24+
25+
The `InputValidationAdorner` can be attached to any element and triggered to be shown automatically based on validation provided by the `INotifyDataErrorInfo` interface set on the `NotifyDataErrorInfo` property of the adorner.
26+
27+
The custom adorner will automatically display the validation message for the specified `PropertyName` is marked as invalid by the `INotifyDataErrorInfo` implementation.
28+
29+
For the example below, we use the `ObservableValidator` class from the `CommunityToolkit.Mvvm` package to provide automatic validation of the rules within our view model properties.
30+
When the user submits invalid input, the adorner displays a red border around the text box and shows a tooltip with the validation error message:
31+
32+
> [!SAMPLE InputValidationAdornerSample]
33+
34+
## Migrating from WPF
35+
36+
Input Validation within WinUI is handled as a mix of both of WPF's [Binding Validation](https://learn.microsoft.com/dotnet/desktop/wpf/data/how-to-implement-binding-validation) and [Custom Object Validation](https://learn.microsoft.com/dotnet/desktop/wpf/data/how-to-implement-validation-logic-on-custom-objects).
37+
38+
> [!WARNING]
39+
> That the WinUI Adorner uses the `INotifyDataErrorInfo` interface for validation feedback, whereas WPF's Custom Object Validation uses the `IDataErrorInfo` interface. You will need to adapt your validation logic accordingly when migrating from WPF to WinUI.
40+
41+
> [!NOTE]
42+
> The `ValidationRule` Binding concept from WPF is not supported in WinUI. You will need to implement validation logic within your view model or data model using the `INotifyDataErrorInfo` interface instead.
43+
> You can still specify a custom error template by styling the `InputValidationAdorner` control.
44+
45+
When paired with the validation provided by the `CommunityToolkit.Mvvm` package, you can achieve similar functionality to WPF's Input Validation with less boilerplate code.

0 commit comments

Comments
 (0)