Skip to content

Commit 8203709

Browse files
committed
Fix validation error icon
1 parent 09b627f commit 8203709

File tree

3 files changed

+135
-16
lines changed

3 files changed

+135
-16
lines changed

samples/MvvmSampleUwp/Controls/ValidationTextBox.cs

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,52 @@
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.ComponentModel;
6+
using System.ComponentModel.DataAnnotations;
7+
using System.Linq;
58
using Windows.UI.Xaml;
69
using Windows.UI.Xaml.Controls;
7-
using Windows.UI.Xaml.Input;
810

911
namespace MvvmSampleUwp.Controls;
1012

1113
/// <summary>
1214
/// A simple control that acts as a container for a documentation block.
1315
/// </summary>
16+
[TemplatePart(Name = "PART_TextBox", Type = typeof(TextBox))]
17+
[TemplatePart(Name = "PART_WarningIcon", Type = typeof(FontIcon))]
1418
public sealed class ValidationTextBox : ContentControl
1519
{
20+
/// <summary>
21+
/// The <see cref="TextBox"/> instance in use.
22+
/// </summary>
23+
private TextBox textBox;
24+
25+
/// <summary>
26+
/// The <see cref="MarkdownTextBlock"/> instance in use.
27+
/// </summary>
28+
private FontIcon warningIcon;
29+
30+
/// <summary>
31+
/// The previous data context in use.
32+
/// </summary>
33+
private INotifyDataErrorInfo oldDataContext;
34+
35+
public ValidationTextBox()
36+
{
37+
DataContextChanged += ValidationTextBox_DataContextChanged;
38+
}
39+
40+
/// <inheritdoc/>
41+
protected override void OnApplyTemplate()
42+
{
43+
base.OnApplyTemplate();
44+
45+
textBox = (TextBox)GetTemplateChild("PART_TextBox");
46+
warningIcon = (FontIcon)GetTemplateChild("PART_WarningIcon");
47+
48+
textBox.TextChanged += TextBox_TextChanged;
49+
}
50+
1651
/// <summary>
1752
/// Gets or sets the <see cref="string"/> representing the text to display.
1853
/// </summary>
@@ -68,20 +103,91 @@ public string PlaceholderText
68103
new PropertyMetadata(default(string)));
69104

70105
/// <summary>
71-
/// Gets or sets the <see cref="string"/> representing the input scope to use.
106+
/// Gets or sets the <see cref="string"/> representing the text to display.
72107
/// </summary>
73-
public InputScope InputScope
108+
public string PropertyName
74109
{
75-
get => (InputScope)GetValue(InputScopeProperty);
76-
set => SetValue(InputScopeProperty, value);
110+
get => (string)GetValue(PropertyNameProperty);
111+
set => SetValue(PropertyNameProperty, value);
77112
}
78113

79114
/// <summary>
80-
/// The <see cref="DependencyProperty"/> backing <see cref="InputScope"/>.
115+
/// The <see cref="DependencyProperty"/> backing <see cref="PropertyName"/>.
81116
/// </summary>
82-
public static readonly DependencyProperty InputScopeProperty = DependencyProperty.Register(
83-
nameof(InputScope),
84-
typeof(InputScope),
117+
public static readonly DependencyProperty PropertyNameProperty = DependencyProperty.Register(
118+
nameof(PropertyName),
119+
typeof(string),
85120
typeof(ValidationTextBox),
86-
new PropertyMetadata(default(InputScope)));
121+
new PropertyMetadata(PropertyNameProperty, OnPropertyNamePropertyChanged));
122+
123+
/// <summary>
124+
/// Invokes <see cref="RefreshErrors"/> whenever <see cref="PropertyName"/> changes.
125+
/// </summary>
126+
private static void OnPropertyNamePropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
127+
{
128+
if (args.NewValue is not string { Length: > 0 } propertyName)
129+
{
130+
return;
131+
}
132+
133+
((ValidationTextBox)sender).RefreshErrors();
134+
}
135+
136+
/// <summary>
137+
/// Updates the bindings whenever the data context changes.
138+
/// </summary>
139+
private void ValidationTextBox_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
140+
{
141+
if (oldDataContext is not null)
142+
{
143+
oldDataContext.ErrorsChanged -= DataContext_ErrorsChanged;
144+
}
145+
146+
if (args.NewValue is INotifyDataErrorInfo dataContext)
147+
{
148+
oldDataContext = dataContext;
149+
150+
oldDataContext.ErrorsChanged += DataContext_ErrorsChanged;
151+
}
152+
153+
RefreshErrors();
154+
}
155+
156+
/// <summary>
157+
/// Invokes <see cref="RefreshErrors"/> whenever the data context requires it.
158+
/// </summary>
159+
private void DataContext_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
160+
{
161+
RefreshErrors();
162+
}
163+
164+
/// <summary>
165+
/// Updates <see cref="Text"/> when needed.
166+
/// </summary>
167+
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
168+
{
169+
Text = ((TextBox)sender).Text;
170+
}
171+
172+
/// <summary>
173+
/// Refreshes the errors currently displayed.
174+
/// </summary>
175+
private void RefreshErrors()
176+
{
177+
if (this.warningIcon is not FontIcon warningIcon ||
178+
PropertyName is not string propertyName ||
179+
DataContext is not INotifyDataErrorInfo dataContext)
180+
{
181+
return;
182+
}
183+
184+
ValidationResult result = dataContext.GetErrors(propertyName).OfType<ValidationResult>().FirstOrDefault();
185+
186+
warningIcon.Visibility = result is not null ? Visibility.Visible : Visibility.Collapsed;
187+
188+
if (result is not null)
189+
{
190+
ToolTipService.SetToolTip(warningIcon, result.ErrorMessage);
191+
}
192+
}
87193
}

samples/MvvmSampleUwp/Controls/ValidationTextBox.xaml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,27 @@
99
<Setter Property="Template">
1010
<Setter.Value>
1111
<ControlTemplate TargetType="local:ValidationTextBox">
12-
<Grid>
12+
<Grid ColumnSpacing="12">
1313
<Grid.ColumnDefinitions>
1414
<ColumnDefinition Width="560" />
1515
<ColumnDefinition Width="Auto" />
1616
</Grid.ColumnDefinitions>
1717
<TextBox
18+
Name="PART_TextBox"
1819
HorizontalAlignment="Stretch"
1920
Header="{TemplateBinding HeaderText}"
20-
InputScope="{TemplateBinding InputScope}"
2121
IsSpellCheckEnabled="False"
2222
PlaceholderText="{TemplateBinding PlaceholderText}"
2323
Text="{TemplateBinding Text}" />
24+
<FontIcon
25+
Name="PART_WarningIcon"
26+
Grid.Column="1"
27+
Margin="0,32,0,0"
28+
VerticalAlignment="Center"
29+
FontSize="18"
30+
Foreground="Orange"
31+
Glyph="&#xE814;"
32+
Visibility="Collapsed" />
2433
</Grid>
2534
</ControlTemplate>
2635
</Setter.Value>

samples/MvvmSampleUwp/Views/ObservableValidatorPage.xaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,28 @@
1414
<ScrollViewer Padding="{StaticResource DocumentationPageContentPadding}" CanContentRenderOutsideBounds="True">
1515
<StackPanel Spacing="16">
1616
<controls:ValidationTextBox
17+
DataContext="{x:Bind ViewModel.Form}"
1718
HeaderText="Enter your first:"
18-
InputScope="NameOrPhoneNumber"
1919
PlaceholderText="First name"
20+
PropertyName="FirstName"
2021
Text="{x:Bind ViewModel.Form.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
2122
<controls:ValidationTextBox
23+
DataContext="{x:Bind ViewModel.Form}"
2224
HeaderText="Enter your last name:"
23-
InputScope="NameOrPhoneNumber"
2425
PlaceholderText="Last name"
26+
PropertyName="LastName"
2527
Text="{x:Bind ViewModel.Form.LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
2628
<controls:ValidationTextBox
29+
DataContext="{x:Bind ViewModel.Form}"
2730
HeaderText="Enter your email address:"
28-
InputScope="EmailNameOrAddress"
2931
PlaceholderText="Email"
32+
PropertyName="Email"
3033
Text="{x:Bind ViewModel.Form.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
3134
<controls:ValidationTextBox
35+
DataContext="{x:Bind ViewModel.Form}"
3236
HeaderText="Enter your phone number:"
33-
InputScope="NameOrPhoneNumber"
3437
PlaceholderText="Phone number"
38+
PropertyName="PhoneNumber"
3539
Text="{x:Bind ViewModel.Form.PhoneNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
3640

3741
<Button Command="{x:Bind ViewModel.Form.SubmitCommand}" Content="Submit" />

0 commit comments

Comments
 (0)