views:

831

answers:

2

I'm trying to figure out how to cancel user input in a TextBox when a validation error occurs. If the user attempts to enter an invalid character I would like to prevent it from being added to the TextBox.

How can I add to or modify the code below to prevent the TextBox from accepting invalid characters? Is it possible without listening to the TextBox.TextChanged event?

My TextBox looks like:

<TextBox Validation.Error="OnSomeTextBoxValidationError">
    <TextBox.Text>
        <Binding Path="Value" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                 <local:SomeValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

My custom validation rule looks like:

public class SomeValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        string hex_string = value as string;
        Match invalid_chars = Regex.Match(hex_string, "[^0-9a-fA-F]");
        bool is_valid = (invalid_chars.Success == false);
        string error_context = null;

        if (is_valid == false)
        {
            error_context = "Invalid characters";
        }

        return new ValidationResult(is_valid, error_context);
    }
}

I have an error handler... can I do anything with it?

private void OnSomeTextBoxValidationError(object sender, ValidationErrorEventArgs e)
{
    // Can I do anything here?
}

Please provide an original answer if possible, rather than referring to a URL. I've read a lot of possible solutions involving event handlers, but I haven't come across anyone discussing the possibility of doing all my validation in the ValidationRule.

+1  A: 

You've probably seen this already, but it's the simplest solution and has always worked for me. I catch the PreviewKeyDown event and..

<TextBox PreviewKeyDown="TextBox_PreviewKeyDown" Width="150" Height="30"></TextBox>

private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
    ... validation here, eg. to stop spacebar from being pressed, you'd use:

    if (e.Key == Key.Space) e.Handled = true;

}
GenericMeatUnit
I was avoiding PreviewKeyDown because I was hoping I could do all my validation within the ValidationRule. This may be what I need to do instead. It was good of you to mention the spacebar in your example, as well. Thanks!
emddudley
No problem. I'm generally simple minded so my solutions tend to be simple too. ;)
GenericMeatUnit
+2  A: 

After a lot of research it seems that the only way to have full control over the input to a TextBox is to handle several events directly. According to WPF Recipes in C# 2008 (1st ed., p. 169):

Unfortunately, there's no easy way (at present) to combine the useful, high-level data binding feature with the lower-level keyboard handling that would be necessary to prevent the user from typing invalid characters altogether.

Here's what I came up with to create a hexadecimal numeric TextBox which only accepts characters a-f, A-F and 0-9.

SomeClass.xaml

<TextBox
    x:Name="SomeTextBox"
    LostFocus="TextBoxLostFocus"
    PreviewKeyDown="TextBoxPreviewKeyDown"
    PreviewTextInput="TextBoxPreviewTextInput" />

SomeClass.xaml.cs

private string mInvalidCharPattern = "[^0-9a-fA-F]";

// In my case SomeClass derives from UserControl
public SomeClass()
{
    DataObject.AddPastingHandler(
        this.SomeTextBox,
        new DataObjectPastingEventHandler(TextBoxPasting));
}

private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
    // You may want to refresh the TextBox's Text here. If the user deletes
    // the contents of the TextBox and clicks off of it, then you can restore
    // the original value.
}

// Catch the space character, since it doesn't trigger PreviewTextInput
private void TextBoxPreviewKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Space) { e.Handled = true; }
}

// Do most validation here
private void TextBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
{
    if (ValidateTextInput(e.Text) == false) { e.Handled = true; }
}

// Prevent pasting invalid characters
private void TextBoxPasting(object sender, DataObjectPastingEventArgs e)
{
    string lPastingText = e.DataObject.GetData(DataFormats.Text) as string;
    if (ValidateTextInput(lPastingText) == false) { e.CancelCommand(); }
}

// Do the validation in a separate function which can be reused
private bool ValidateTextInput(string aTextInput)
{
    if (aTextInput == null) { return false; }

    Match lInvalidMatch = Regex.Match(aTextInput, this.mInvalidCharPattern);
    return (lInvalidMatch.Success == false);
}
emddudley