在可重用控件上使用 INotifyDataErrorInfo
Using INotifyDataErrorInfo on reusable control
我想使用可重用控件实现 MVVM Toolkit 的验证方法。我的问题是警告高亮显示在整个控件上,像这样:
如果我不使用可重复使用的控件,它可以正常工作:
可重用控件如下所示:
ValidationTextBox.xaml
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="275" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=HeaderText}" />
<TextBox
Grid.Row="1"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TextBoxContent}" />
</Grid>
</StackPanel>
ValidationTextBox.xaml.cs
public partial class ValidationTextBox : UserControl
{
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(ValidationTextBox), new PropertyMetadata(default(string)));
public string HeaderText
{
get => (string)GetValue(HeaderTextProperty);
set => SetValue(HeaderTextProperty, value);
}
public static readonly DependencyProperty TextBoxContentProperty =
DependencyProperty.Register(nameof(TextBoxContent), typeof(string), typeof(ValidationTextBox), new FrameworkPropertyMetadata(default(string)));
public string TextBoxContent
{
get { return (string)GetValue(TextBoxContentProperty); }
set { SetValue(TextBoxContentProperty, value); }
}
public ValidationTextBox()
{
InitializeComponent();
}
}
以及我使用的视图和视图模型:
RegisterView.xaml
...
<controls:ValidationTextBox
Grid.Row="1"
Grid.Column="2"
MaxWidth="300"
Margin="10,10,0,0"
HeaderText="First name"
TextBoxContent="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
...
RegisterViewModel.cs
public partial class RegisterViewModel : ViewModelBase
{
...
[ObservableProperty]
[Required]
[MinLength(2)]
private string? _firstName;
...
}
如您所见,我为此 属性 使用了 MVVM Toolkit 的源代码生成器及其验证方法。 ViewModelBase
继承自 ObservableValidator
,它实现了 INotifyDataErrorInfo
。
验证工作正常,这意味着每当我输入 2 个字符时,错误突出显示消失并在我输入少于 2 个字符时重新出现。
对于正确显示验证突出显示的第二个示例,我创建了一个 属性 与我为名字所做的相同的方式,我只是将文本框的文本 属性 绑定到 UserName
属性.
是否可以使用可重复使用的控件进行验证,或者在这种情况下需要完全不同的方法?
由于验证 Binding
是从视图模型设置到 UserControl
,绑定引擎会将附加的 属性 Validation.HasError 设置为 true对于 UserControl(绑定目标)。因此,错误模板装饰 UserControl
而不是特定的内部元素。
您必须配置 UserControl
以指示绑定引擎装饰不同的元素。您可以使用附件 Validation.ValidationAdornerSiteFor
属性:
<UserControl>
<StackPanel>
<TextBlock />
<TextBox Validation.ValidationAdornerSiteFor="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
</UserControl>
我只想指出,在完整 UserControl
上应用错误模板在技术上是正确的。
要更改此行为,您必须显式验证内部绑定(参见下面的示例)。
由于 Validation.ValidationAdornerSiteFor
只允许设置一个替代元素,您必须手动委托验证错误,以防 UserControl
有多个验证输入。
以下示例显示如何将外部绑定验证错误路由到相应的内部输入元素:
MyUserControl.xaml.cs
partial class MyUserControl : UserControl
{
// This ValidationRule is only used to explicitly create a ValidationError object.
// It will never be invoked.
private class DummyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo) => throw new NotSupportedException();
}
public string TextData
{
get => (string)GetValue(TextDataProperty);
set => SetValue(TextDataProperty, value);
}
public static readonly DependencyProperty TextDataProperty = DependencyProperty.Register(
"TextData",
typeof(string),
typeof(MyUserControl),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnTextDataChanged));
private static void OnTextDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var userControl = (d as MyUserControl);
BindingExpression? bindingExpression = userControl.GetBindingExpression(TextDataProperty);
if (bindingExpression is null)
{
return;
}
userControl.OnTextDataChanged(bindingExpression.HasError, Validation.GetErrors(userControl).FirstOrDefault());
}
private void OnTextDataChanged(bool hasError, ValidationError validationError)
{
BindingExpression bindingExpression = this.InternalTextBox.GetBindingExpression(TextBox.TextProperty);
if (hasError)
{
validationError = new ValidationError(new DummyValidationRule(), bindingExpression, validationError.ErrorContent, validationError?.Exception);
Validation.MarkInvalid(bindingExpression, validationError);
}
else
{
Validation.ClearInvalid(bindingExpression);
}
}
}
MyUserControl.xaml
<UserControl Validation.ErrorTemplate="{x:Null}">
<StackPanel>
<TextBlock />
<TextBox x:Name="InternalTextBox"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TextData, ValidatesOnNotifyDataErrors=True}" />
</StackPanel>
</UserControl>
<MyUserControl TextData="{Binding TextValue, ValidatesOnNotifyDataErrors=True}" />
我想使用可重用控件实现 MVVM Toolkit 的验证方法。我的问题是警告高亮显示在整个控件上,像这样:
如果我不使用可重复使用的控件,它可以正常工作:
可重用控件如下所示:
ValidationTextBox.xaml
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="275" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=HeaderText}" />
<TextBox
Grid.Row="1"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TextBoxContent}" />
</Grid>
</StackPanel>
ValidationTextBox.xaml.cs
public partial class ValidationTextBox : UserControl
{
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(ValidationTextBox), new PropertyMetadata(default(string)));
public string HeaderText
{
get => (string)GetValue(HeaderTextProperty);
set => SetValue(HeaderTextProperty, value);
}
public static readonly DependencyProperty TextBoxContentProperty =
DependencyProperty.Register(nameof(TextBoxContent), typeof(string), typeof(ValidationTextBox), new FrameworkPropertyMetadata(default(string)));
public string TextBoxContent
{
get { return (string)GetValue(TextBoxContentProperty); }
set { SetValue(TextBoxContentProperty, value); }
}
public ValidationTextBox()
{
InitializeComponent();
}
}
以及我使用的视图和视图模型:
RegisterView.xaml
...
<controls:ValidationTextBox
Grid.Row="1"
Grid.Column="2"
MaxWidth="300"
Margin="10,10,0,0"
HeaderText="First name"
TextBoxContent="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
...
RegisterViewModel.cs
public partial class RegisterViewModel : ViewModelBase
{
...
[ObservableProperty]
[Required]
[MinLength(2)]
private string? _firstName;
...
}
如您所见,我为此 属性 使用了 MVVM Toolkit 的源代码生成器及其验证方法。 ViewModelBase
继承自 ObservableValidator
,它实现了 INotifyDataErrorInfo
。
验证工作正常,这意味着每当我输入 2 个字符时,错误突出显示消失并在我输入少于 2 个字符时重新出现。
对于正确显示验证突出显示的第二个示例,我创建了一个 属性 与我为名字所做的相同的方式,我只是将文本框的文本 属性 绑定到 UserName
属性.
是否可以使用可重复使用的控件进行验证,或者在这种情况下需要完全不同的方法?
由于验证 Binding
是从视图模型设置到 UserControl
,绑定引擎会将附加的 属性 Validation.HasError 设置为 true对于 UserControl(绑定目标)。因此,错误模板装饰 UserControl
而不是特定的内部元素。
您必须配置 UserControl
以指示绑定引擎装饰不同的元素。您可以使用附件 Validation.ValidationAdornerSiteFor
属性:
<UserControl>
<StackPanel>
<TextBlock />
<TextBox Validation.ValidationAdornerSiteFor="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
</UserControl>
我只想指出,在完整 UserControl
上应用错误模板在技术上是正确的。
要更改此行为,您必须显式验证内部绑定(参见下面的示例)。
由于 Validation.ValidationAdornerSiteFor
只允许设置一个替代元素,您必须手动委托验证错误,以防 UserControl
有多个验证输入。
以下示例显示如何将外部绑定验证错误路由到相应的内部输入元素:
MyUserControl.xaml.cs
partial class MyUserControl : UserControl
{
// This ValidationRule is only used to explicitly create a ValidationError object.
// It will never be invoked.
private class DummyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo) => throw new NotSupportedException();
}
public string TextData
{
get => (string)GetValue(TextDataProperty);
set => SetValue(TextDataProperty, value);
}
public static readonly DependencyProperty TextDataProperty = DependencyProperty.Register(
"TextData",
typeof(string),
typeof(MyUserControl),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnTextDataChanged));
private static void OnTextDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var userControl = (d as MyUserControl);
BindingExpression? bindingExpression = userControl.GetBindingExpression(TextDataProperty);
if (bindingExpression is null)
{
return;
}
userControl.OnTextDataChanged(bindingExpression.HasError, Validation.GetErrors(userControl).FirstOrDefault());
}
private void OnTextDataChanged(bool hasError, ValidationError validationError)
{
BindingExpression bindingExpression = this.InternalTextBox.GetBindingExpression(TextBox.TextProperty);
if (hasError)
{
validationError = new ValidationError(new DummyValidationRule(), bindingExpression, validationError.ErrorContent, validationError?.Exception);
Validation.MarkInvalid(bindingExpression, validationError);
}
else
{
Validation.ClearInvalid(bindingExpression);
}
}
}
MyUserControl.xaml
<UserControl Validation.ErrorTemplate="{x:Null}">
<StackPanel>
<TextBlock />
<TextBox x:Name="InternalTextBox"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TextData, ValidatesOnNotifyDataErrors=True}" />
</StackPanel>
</UserControl>
<MyUserControl TextData="{Binding TextValue, ValidatesOnNotifyDataErrors=True}" />