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 {ret开发者_高级运维urn _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 OnTestTextChanged
method 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:
static void OnTestTextChanged(object sender, DependencyPropertyChangedEventArgs args) {
MyControl source = (MyControl)sender;
source.TestTextBox.Text = (String)args.NewValue; // *******
}
The asterisked line is the problem here. What happens is as follows:
- you set the
Text
property of the TextBox, - Silverlight then updates the value of your
TestText
dependency property using the binding, - this would cause another call into your PropertyChangedCallback.
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 SetValue
AND 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 TestText
property should therefore look like the following:
public String TestText
{
get { return (String)GetValue(TestTextProperty); }
set { SetValue(TestTextProperty, value); }
}
and instead you should do the validation in your PropertyChangedCallback:
static void OnTestTextChanged(object sender, DependencyPropertyChangedEventArgs args)
{
if ((string)args.NewValue == "!")
{
throw new Exception("No ! allowed!");
}
}
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 !
.
I am not that familiar with dependency properties, but I can see that in your second block of code where you say
if (TestText="!") {
you need to replace that with
if( TestText == "!") {
your code was setting TestText to the value of "!" instead of checking if they are the same string.
精彩评论