My problem: I would like to validate the user input of a TextBox using the control’s ValidatesOnExceptions property.
XAML code:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
...
<TextBox x:Name="TestTextBox" Text="{Binding TestText, Mode=TwoWay, ValidatesOnExceptions=True}" TextChanged="TestTextBox_TextChanged"/>
1 : The validation using a normal property works fine:
ViewModel code:
private string _testText,
public string TestText {
get {return _testText;}
set {
if (value=="!")
throw new Exception("Error: No ! allowed!");
_testText = value;
}
}
2: The validation using a Dependency Property causes “A first chance exception of type ‘System.Exception’…” and the application stops working.
ViewModel code:
public partial class MyControl : UserControl {
public MyControl() {
InitializeComponent();
}
public static readonly DependencyProperty TestTextProperty = DependencyProperty.Register("TestText", typeof(String), typeof(MyControl), new PropertyMetadata("DefaultText", new PropertyChangedCallback(OnTestTextChanged)));
public event TextChangedEventHandler TestTextChanged;
public String TestText {
get {
return (String)GetValue(TestTextProperty);
}
set {
SetValue(TestTextProperty, value);
if (TestTextChanged != null) {
TestTextChanged(TestTextBox, null);
}
if (TestText=="!") {
throw new Exception("No ! allowed!");
}
}
}
static void OnTestTextChanged(object sender, DependencyPropertyChangedEventArgs args) {
MyControl source = (MyControl)sender;
source.TestTextBox.Text = (String)args.NewValue;
}
private void TestTextBox_TextChanged(object sender, TextChangedEventArgs e) {
TextBox source = (TextBox)sender;
TestText = source.Text;
}
}
What am I doing wrong?
If you run your second example and take a look at the call stack when the exception gets thrown, you’ll see that it’s not going through the dependency system at all. The textbox’s text changed, the event handler runs and that encountered an exception. That’s why the application dies.
I suspect you put that TextChanged event handler in because your
OnTestTextChangedmethod wasn’t being called. As it happens, there’s a reason for this, but it’s a bit subtle and I can’t find any documentation to back it up. I refer to this behaviour as ‘blacklisting’. In short, if you have a PropertyChangedCallback on a dependency property, and the PropertyChangedCallback causes the same dependency property to be set, either directly or indirectly, the PropertyChangedCallback gets ‘blacklisted’ and never gets called again.Your PropertyChangedCallback is as follows:
The asterisked line is the problem here. What happens is as follows:
Textproperty of the TextBox,TestTextdependency property using the binding,However, at this point Silverlight realises that it is making a recursive call to your PropertyChangedCallback, and instead of calling it again, decides to ‘blacklist’ it. As a result of this ‘blacklisting’, your PropertyChangedCallback never gets called again. Annoyingly, there’s no error or warning when it does this.
I’m not sure why it doesn’t give any warnings or errors. However, if it didn’t ‘blacklist’ your PropertyChangedCallback and continued to call it, you’d end up with a stack overflow.
So, how do you fix your code? Well, to start with, I’d like to introduce a golden rule of working with Silverlight (and WPF) dependency properties, which your code violates:
THE SETTER IN THE PROPERTY BACKED BY THE DEPENDENCY PROPERTY SHOULD CALL
SetValueAND DO NOTHING MORE. Anything you want to do whenever the value of the dependency property changes must go in a PropertyChangedCallback and NOT in the property setter.If you don’t stick to this rule, you are entering a world of pain.
Your
TestTextproperty should therefore look like the following:and instead you should do the validation in your PropertyChangedCallback:
After making these changes to your code, I was able to run it and get a validation tooltip to show on the TextBox when I entered the text
!.