views:

61

answers:

3

I'm creating a FilteredTextBox in WPF that subclasses the included TextBox control. The FilteredTextBox must only allow characters in the range [a-zA-Z0-9_] to be entered, and I've got that part pretty much working. I've hooked into OnPreviewTextInput to handle typed characters, OnPreviewDrop to filter characters that are dragged and dropped, and I've added a PreviewExecutedHandler that runs whenever a command is executed on the control to handle pasting.

So far, so good.

The tricky part is that the control should also replace spaces with underscores when they're typed in.

I've come up with a solution, but it feels pretty hacked together and I don't know what it's missing. I feel like there's got to be a better technique that I'm just not aware of. What I've done:

internal class FilteredTextBox : TextBox
{
    public FilteredTextBox()
    {
        CommandManager.AddPreviewExecutedHandler(this, this.HandlePreviewExecuteHandler);
    }

    private void HandlePreviewExecuteHandler(object sender, ExecutedRoutedEventArgs e)
    {
        var uiCmd = e.Command as RoutedUICommand;
        if (uiCmd != null && (uiCmd.Text == "Space" || uiCmd.Text == "ShiftSpace"))
        {
            // We're manually handling spaces, so we need to make appropriate checks.
            if (this.Text.Length == this.MaxLength) return;

            if (this.SelectionLength == 0)
            {
                // If the user is just typing a space normally
                // We need to cache CaretIndex b/c it's reset to 0 when we set Text.
                var tmpIndex = this.CaretIndex;
                this.Text = this.Text.Insert(tmpIndex, "_");
                this.CaretIndex = tmpIndex + 1;
            }
            else
            {
                // Otherwise, replace the selected text with the underscore and fixup the caret.
                this.SelectedText = "_";
                this.CaretIndex += this.SelectedText.Length;
                this.SelectionLength = 0;
            }

            e.Handled = true; // If someone hits the spacebar, say we handled it.
            return;
        }
    }
}

Is there a smarter way?

+1  A: 

Would it be better to just replace space with _ when the control loses focus or do you absolutely need it to be replaced as the user keys it in?

Vivek
Per the spec, it must be as the user keys it in.
Greg D
+3  A: 

I would bind the TextBox to a ValueConverter that eliminates whitespace on demand and replaces them with underscores.

The ValueConverter would look something like this:

public class SpaceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return System.Convert.ToString(value); 
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string text = System.Convert.ToString(value);

            //the meat and potatoes is this line
            text = text.Replace(" ", "_");    

            return text;
        }
    }

And your TextBox would bind to it this way:

<TextBox Text="{Binding Path=UserString, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource SpaceConverter}}" />

Note that UserString would have to be in the current DataContext.

Don't forget to define SpaceConverter in the XAML as well. One way to do this, assuming you're working on a UserControl, would be:

<UserControl.Resources>
   <local:SpaceConverter x:Key="SpaceConverter" />
</UserControl.Resources>

Where local is defined as the namespace containing SpaceConverter.cs.

bufferz
A: 

I think your logic is good, but I'd put it in an override of OnPreviewKeyDown.

Robert Rossney