Validating input which is databound to a DependencyProperty - Silverlight

谁说我不能喝 提交于 2020-01-06 14:00:43

问题


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?


回答1:


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 !.




回答2:


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.



来源:https://stackoverflow.com/questions/6651728/validating-input-which-is-databound-to-a-dependencyproperty-silverlight

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!