Показать ошибку проверки в UserControl
Я не уверен, почему состояние проверки не отражено в моем пользовательском элементе управления.
Я бросаю исключение, но по какой-то причине элемент управления не показывает состояние проверки... Когда я использую стандартный Textbox
(который сейчас комментируется в моем примере), на моем MainPage отображается состояние ошибки, а не конечно, почему это не когда его обернули.
Я уменьшил это, так что в основном это пользовательский элемент управления, который обертывает Textbox
.
Что мне не хватает?
MyUserControl XAML:
<UserControl x:Class="ValidationWithUserControl.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<TextBox x:Name="TextBox"/>
</Grid>
</UserControl>
Код MyUserControl:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MyUserControl_Loaded);
this.TextBox.Unloaded += new RoutedEventHandler(TextBox_Unloaded);
}
public string Value
{
get { return (string)base.GetValue(ValueProperty); }
set { base.SetValue(ValueProperty, value); }
}
public static DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(string),
typeof(MyUserControl),
new PropertyMetadata(null));
private void MyUserControl_Loaded(object sender, RoutedEventArgs e)
{
this.TextBox.SetBinding(TextBox.TextProperty, new Binding()
{
Source = this,
Path = new PropertyPath("Value"),
Mode = BindingMode.TwoWay,
ValidatesOnExceptions = true,
NotifyOnValidationError= true
});
}
private void TextBox_Unloaded(object sender, RoutedEventArgs e)
{
this.TextBox.ClearValue(TextBox.TextProperty);
}
}
My MainPage XAML:
<Grid x:Name="LayoutRoot" Background="LightBlue">
<StackPanel>
<uc:MyUserControl x:Name="UC" Value="{Binding Path=Value, Mode=TwoWay}" Height="20" Width="100" />
<!--TextBox x:Name="MS" Text="{Binding Path=Value, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}" Height="20" Width="100" /-->
</StackPanel>
</Grid>
Мой код главной страницы:
public partial class MainPage : UserControl
{
private Model model;
//private Model model2;
public MainPage()
{
InitializeComponent();
this.model = new Model("UC");
//this.model2 = new Model("MS");
this.UC.DataContext = this.model;
//this.MS.DataContext = this.model2;
}
}
Моя модель:
public class Model
{
public Model(string answer)
{
this.answer = answer;
}
private string answer;
public string Value
{
get
{
return this.answer;
}
set
{
if (!String.IsNullOrEmpty(value))
this.answer = value;
else
throw new Exception("Error");
}
}
}
Ответы
Ответ 1
Хорошо, я наконец понял, как с этим справиться.
Что вам нужно сделать, это скопировать подтверждение из оригинальной привязки и отправить его в привязку к текстовому полю.
Первое, что вам нужно сделать для достижения этой цели, - реализовать интерфейс INotifyDataErrorInfo в пользовательском элементе управления. Затем вам нужно будет проверить элемент управления пользователя, чтобы получить точный текст проверки с помощью функции GetErrors (это можно сделать с помощью Validation.GetErrors).
Это базовая реализация, и это в VB, но я уверен, что вы поняли.
Public Event ErrorsChanged(ByVal sender As Object, ByVal e As System.ComponentModel.DataErrorsChangedEventArgs) Implements System.ComponentModel.INotifyDataErrorInfo.ErrorsChanged
Public Function GetErrors(ByVal propertyName As String) As System.Collections.IEnumerable Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors
Dim returnValue As System.Collections.IEnumerable = Nothing
Dim errorMessage As String = Nothing
If propertyName = "Value" Then
If Validation.GetErrors(Me).Count = 0 Then
errorMessage = ""
Else
errorMessage = Validation.GetErrors(Me).First.ErrorContent.ToString
End If
If String.IsNullOrEmpty(errorMessage) Then
returnValue = Nothing
Else
returnValue = New List(Of String)() From {errorMessage}
End If
End If
Return returnValue
End Function
Public ReadOnly Property HasErrors As Boolean Implements System.ComponentModel.INotifyDataErrorInfo.HasErrors
Get
Return Validation.GetErrors(Me).Any()
End Get
End Property
Следующее, что нужно сделать, - уведомить вас об управлении, становится недействительным. Вам нужно будет сделать это в двух местах.
Первый будет находиться в событии BindingValidationError. Второй будет находиться в Value PropertyChangedCallback function (он должен быть указан при регистрации DependencyProperty)
Public Shared ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(XDateTimePicker), New PropertyMetadata(Nothing, AddressOf ValuePropertyChangedCallback))
Public Shared Sub ValuePropertyChangedCallback(ByVal dependencyObject As DependencyObject, ByVal dependencyPropertyChangedEventArgs As DependencyPropertyChangedEventArgs)
DirectCast(dependencyObject, MyUserControl).NotifyErrorsChanged("Value")
End Sub
Private Sub MyUserControl_BindingValidationError(ByVal sender As Object, ByVal e As System.Windows.Controls.ValidationErrorEventArgs) Handles Me.BindingValidationError
Me.NotifyErrorsChanged("Value")
End Sub
Public Sub NotifyErrorsChanged(ByVal propertyName As String)
RaiseEvent ErrorsChanged(Me, New System.ComponentModel.DataErrorsChangedEventArgs(propertyName))
End Sub
Большая часть работы выполняется сейчас, но вам все равно нужно внести некоторые изменения в привязки.
При создании привязки TextBox вам необходимо установить NotifyOnValidationError в False, чтобы избежать цикла уведомлений между исходной привязкой и привязкой к текстовому полю. ValidatesOnExceptions, ValidatesOnDataErrors и ValidatesOnNotifyDataErrors необходимо установить значение True.
Dim binding As New System.Windows.Data.Binding
binding.Source = Me
binding.Path = New System.Windows.PropertyPath("Value")
binding.Mode = Data.BindingMode.TwoWay
binding.NotifyOnValidationError = False
binding.ValidatesOnExceptions = True
binding.ValidatesOnDataErrors = True
binding.ValidatesOnNotifyDataErrors = True
Me.TextBox1.SetBinding(TextBox.TextProperty, binding)
Наконец, вам нужно установить свойство NotifyOnValidationError и ValidatesOnNotifyDataErrors в значение True в вашем XAML.
<uc:MyUserControl x:Name="UC" Value="{Binding Path=Value, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnNotifyDataErrors=True}" Height="20" Width="100" />
Ответ 2
Такое поведение вызвано добавленным уровнем привязки.
Привязки не поддерживают ошибки проверки пересылки.
Что происходит за кулисами:
- Пользователь вводит текст в TextBox, а привязка, определенная в MyUserControl_Loaded, передает значение в MyUserControl.ValueProperty.
- Далее, привязка, определенная в MainPage XAML для MyUserControl, передает значение модели.
- Исключение, созданное в Model.Value.set(), связано с привязкой, которая была настроена в MainPage XAML.
- Никакое исключение не передается в привязку, связанную с TextBox.
- Так как UserControl не имеет ValidatesOnExceptions, установленного в true, визуальная индикация не отображается.
Чтобы устранить эту проблему, вы можете привязать текстовое поле непосредственно к модели следующим образом:
this.TextBox.SetBinding(TextBox.TextProperty, new Binding()
{
Source = this.DataContext, // bind to the originating source
Path = new PropertyPath("Value"),
Mode = BindingMode.TwoWay,
ValidatesOnExceptions = true,
NotifyOnValidationError= true
});
С тех пор прошло 6 месяцев, интересно, как и как вы преодолели эту проблему.
Ответ 3
Если кто-то приходит искать хорошего (читай: "не написано в VBA и полностью" ) решение для этой проблемы, я написал базовый класс (хотя я тестировал его только с помощью управления без проблем, не знаю, работает ли он с UserControl
s) на основе ответа @The_Black_Smurf на С#:
namespace MyApplication.Controls
{
using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
public abstract class ControlBaseWithValidation : Control, INotifyDataErrorInfo
{
public ControlBaseWithValidation()
{
// remove the red border that wraps the whole control by default
Validation.SetErrorTemplate(this, null);
}
public delegate void ErrorsChangedEventHandler(object sender, DataErrorsChangedEventArgs e);
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors
{
get
{
var validationErrors = Validation.GetErrors(this);
return validationErrors.Any();
}
}
public IEnumerable GetErrors(string propertyName)
{
var validationErrors = Validation.GetErrors(this);
var specificValidationErrors =
validationErrors.Where(
error => ((BindingExpression)error.BindingInError).TargetProperty.Name == propertyName).ToList();
var specificValidationErrorMessages = specificValidationErrors.Select(valError => valError.ErrorContent);
return specificValidationErrorMessages;
}
public void NotifyErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
protected static void ValidatePropertyWhenChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
((ControlBaseWithValidation)dependencyObject).NotifyErrorsChanged(dependencyPropertyChangedEventArgs.Property.Name);
}
}
}
используйте ControlBaseWithValidation
класс как базовый класс для ваших беззаботных элементов управления вместо класса Control
и добавьте обратный вызов ValidatePropertyWhenChangedCallback
как PropertyChangedCallback
для любых свойств зависимостей, которые вы хотите проверить.
Ответ 4
Вы должны иметь возможность отображать привязку свойства зависимостей непосредственно к текстовому полю usercontrol. Это приведет к ошибкам проверки так же, как привязки в родительском представлении. В вашей функции MyUserControl_Loaded:
var valueBinding = BindingOperations.GetBindingBase(this, ValueProperty);
if (valueBinding != null) TextBox.SetBinding(TextBox.TextProperty, valueBinding);