Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you replace characters in a textbox as you type?

Tags:

input

wpf

textbox

We are looking for a way to make a textbox replace certain characters as a person types in it. Note, we are focusing on the control itself, not bindings, viewmodels, etc. For the sake of this question, assume there is a window with a textbox sitting in the middle of it and nothing else. No data, no viewmodel, no bindings, etc.

I've updated the question because it seems all the answers below keep focusing on bindings, dependency properties, coersion, etc. While I appreciate the suggestions, as I stated above, our control is not a bound control so they aren't applicable.

Now while I could explain the reasons for that, that would make this post about five times longer as it's actually a complex and advanced use-case, but that has nothing to do with the question itself, which is why I've simplified our scenario down to focus on the specific question we're trying to resolve, which is about a textbox control, or possibly a subclass of one replacing characters as you type.

Hope that makes more sense now.

like image 247
Mark A. Donohoe Avatar asked Sep 24 '13 00:09

Mark A. Donohoe


3 Answers

The best way to accomplish this is using the TextChanged event:

    private void OnTextChanged(object sender, TextChangedEventArgs e)
    {
        var tb = (TextBox)sender;
        using (tb.DeclareChangeBlock())
        {
            foreach (var c in e.Changes)
            {
                if (c.AddedLength == 0) continue;
                tb.Select(c.Offset, c.AddedLength);
                if (tb.SelectedText.Contains(' '))
                {
                    tb.SelectedText = tb.SelectedText.Replace(' ', '_');
                }
                tb.Select(c.Offset + c.AddedLength, 0);
            }
        }
    }

This has several advantages:

  • You don't go through the entire string every time, just the replaced part
  • It behaves well with the undo manager and with pasting text
  • You can easily encapsulate it into an attached property which can be applied to any text box
like image 52
Eli Arbel Avatar answered Nov 12 '22 15:11

Eli Arbel


You can use an IValueConverter. It involves quite a bit of code, but it's the preferred way if you ever want to go the MVVM path, or just do things the WPF way.

First, create a class that implements IValueConverter

public class IllegalCharactersToUnderscoreConverter
    : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var stringValue = value as String;
        if(stringValue == null)
            return value;

        return RemoveIllegalCharacters(stringValue);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value;
    }
}

Import the namespace containing your ValueConverter

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">

Create an instance of your converter in Window.Resources

<Window.Resources>
    <local:IllegalCharactersToUnderscoreConverter x:Key="IllegalCharactersToUnderscore" />
</Window.Resources>

Use the converter in your binding expression. UpdateSourceTrigger=PropertyChanged is required for the text to be converted as you type.

<TextBox Text="{Binding MyText, 
                Converter={StaticResource IllegalCharactersToUnderscore}, 
                UpdateSourceTrigger=PropertyChanged}"/>
like image 4
igelineau Avatar answered Nov 12 '22 14:11

igelineau


You could subclass TextBox you can just override the Metadata of the TextBox Text property

public class FilteredTextBox : TextBox
{
    public FilteredTextBox()
    {
        TextBox.TextProperty.OverrideMetadata(typeof(FilteredTextBox), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceMyTextValue, true, UpdateSourceTrigger.PropertyChanged));
    }

    private static object CoerceMyTextValue(DependencyObject d, object baseValue)
    {
        if (baseValue != null)
        {
            var userEnteredString = baseValue.ToString();
            return userEnteredString.Replace(' ', '_');
        }
        return baseValue;
    }
}

And you dont need to use bindings at all, this will just update the internal TextBox TextProperty as you type

<Window x:Class="WpfApplication13.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="428" Width="738" Name="UI" 
        xmlns:my="clr-namespace:WpfApplication13" >
    <StackPanel>
        <my:FilteredTextBox />
    </StackPanel>

</Window>
like image 2
sa_ddam213 Avatar answered Nov 12 '22 14:11

sa_ddam213