Skip to content

Commit b7637ca

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 b7637ca

File tree

8 files changed

+237
-178
lines changed

8 files changed

+237
-178
lines changed

components/Adorners/samples/Adorners.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Adorners
33
author: michael-hawker
44
description: Adorners let you overlay content on top of your XAML components in a separate layer on top of everything else.
5-
keywords: Adorners, Control, Layout
5+
keywords: Adorners, Control, Layout, InfoBadge, AdornerLayer, AdornerDecorator, Adorner, Input Validation, Highlighting
66
dev_langs:
77
- csharp
88
category: Controls
@@ -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: 14 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,27 @@
1-
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
1+
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
22
<Page x:Class="AdornersExperiment.Samples.InputValidation.InputValidationAdornerSample"
33
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
44
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:adorners="using:CommunityToolkit.WinUI.Adorners"
56
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
67
xmlns:local="using:AdornersExperiment.Samples.InputValidation"
78
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
89
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
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 NotifyDataErrorInfo="{x:Bind ViewModel}"
24+
PropertyName="FirstName" />
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 NotifyDataErrorInfo="{x:Bind ViewModel}"
34+
PropertyName="LastName" />
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 NotifyDataErrorInfo="{x:Bind ViewModel}"
44+
PropertyName="Email" />
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 NotifyDataErrorInfo="{x:Bind ViewModel}"
54+
PropertyName="PhoneNumber" />
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 NotifyDataErrorInfo="{x:Bind ViewModel}"
67+
PropertyName="Age" />
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)