I am not sure why the validation state doesn't get reflected in my user control.
I am throwing an exception but for some reason the control doesn't show the validation state...When I use a standard Textbox
(Which is commented out right now in my example) on my MainPage it shows the error state, not sure why its not when its wrapped.
I have slimmed this down so basically its a user control that wraps a TextBox
.
What am I missing??
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 Code Behind:
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>
My MainPage Code Behind:
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;
}
}
My Model:
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");
}
}
}
Ok, I finally figured out how to handle this.
What you need to do here is to copy the validation from the original binding and send it to the Textbox binding.
The first thing you'll need to do to achieve this is to implement the INotifyDataErrorInfo interface in your user control. Then you'll have to validate the user control to get the exact validation text withon the GetErrors function (This can be done with the Validation.GetErrors).
This is a basic implementation and it's in VB but I'm sure you get the point.
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
The next thing to do is to notify you control it becomes invalid. You will have to do this in 2 places.
The first one will be on the BindingValidationError event. The second one will be in the Value PropertyChangedCallback function (It has to be specified when you register the 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
Most of the job is done now but you still need to make some adjustments to the bindings.
When you create the TextBox binding, you need to set the NotifyOnValidationError to False to avoid a notifications loop between the original binding and the Textbox binding. ValidatesOnExceptions, ValidatesOnDataErrors and ValidatesOnNotifyDataErrors need to be set to 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)
Finaly, you need to set the NotifyOnValidationError and ValidatesOnNotifyDataErrors property to True in your XAML.
<uc:MyUserControl x:Name="UC" Value="{Binding Path=Value, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnNotifyDataErrors=True}" Height="20" Width="100" />
This behaviour is caused by an added binding level. Bindings do not support forwarding validation errors.
What happens behind the scenes:
To resolve this issue you could bind the text box directly to the Model like this:
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
});
Since 6 months have passed I wonder if and how did you overcome this problem.
You should be able to echo the dependency property binding directly to the usercontrol textbox. This will pick up validation errors the same way bindings in the parent view would. In your MyUserControl_Loaded function:
var valueBinding = BindingOperations.GetBindingBase(this, ValueProperty);
if (valueBinding != null) TextBox.SetBinding(TextBox.TextProperty, valueBinding);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With