你能在输入时替换文本框中的字符吗?

11

我们正在寻找一种方法,使文本框在输入时替换特定字符。请注意,我们专注于控件本身,而不是绑定、视图模型等内容。对于这个问题,假设有一个窗口,其中央有一个文本框且没有其他任何数据、视图模型或绑定。

我更新了问题,因为似乎下面所有的答案都集中在绑定、依赖属性、强制转换等方面。虽然我很感激这些建议,但就像我上面所说的那样,我们的控件不是绑定控件,因此它们并不适用。

现在,虽然我可以解释其中的原因,但那会让这篇文章变成实际上是一个复杂和高级用例的五倍长,但那与问题本身无关,因此我简化了我们的情况,重点关注我们试图解决的具体问题,即文本框控件或可能是其子类的字符替换。

希望现在更清楚了。


1
你有自定义的文本框还是想将此行为应用于普通的文本框? - sa_ddam213
4个回答

19

实现此功能的最佳方式是使用 TextChanged 事件:

    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);
            }
        }
    }

这样做有几个好处:

  • 每次只需处理替换的部分,而不是整个字符串
  • 它可以很好地与撤销管理器和粘贴文本配合使用
  • 您可以轻松地将其封装到一个附加属性中,该属性可以应用于任何文本框

太棒了!像个冠军一样工作!此外,+1,因为我不知道e.Changes属性。但还有几个问题。你确定需要DeclareChangeBlock吗?我认为在那个事件中已经在其中一个块中了。我通过将其注释掉进行了测试,它仍然似乎正确地尊重撤消。你能给我一个不行的情况吗?另外,如果c.AddedLength是正数,只运行循环体是否有意义?当删除时似乎没有必要运行,因为没有添加任何内容进行检查。(顺便说一下,当绑定到另一个TextBox时,以及粘贴时,我印象深刻,这甚至可以工作。) - Mark A. Donohoe
1
你可能是对的关于 DeclareChangeBlock,我只是为了确保它而添加的。而且你关于检查 AddedLength 是否为正数也是正确的。很高兴能帮到你 :) - Eli Arbel

4
您可以使用IValueConverter。这需要相当多的代码,但如果您想要走MVVM路径或按照WPF方式进行操作,则这是首选方法。
首先,创建一个实现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;
    }
}

导入包含您的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">

在Window.Resources中创建您的转换器的实例。
<Window.Resources>
    <local:IllegalCharactersToUnderscoreConverter x:Key="IllegalCharactersToUnderscore" />
</Window.Resources>

在绑定表达式中使用转换器。UpdateSourceTrigger=PropertyChanged是必需的,以便在您输入时将文本转换。

<TextBox Text="{Binding MyText, 
                Converter={StaticResource IllegalCharactersToUnderscore}, 
                UpdateSourceTrigger=PropertyChanged}"/>

再次强调,我们不是在谈论绑定。我们特别询问的是当用户输入时替换文本框中的文本。换句话说,他们键入一个空格,但我们插入一个下划线。它必须处理粘贴等操作,而且与绑定无关,因为这不是一个绑定控件。 - Mark A. Donohoe
1
首先,在你的问题中并没有明确说明。也许你应该编辑一下。并不是每个人都想阅读其他答案的每一个评论。此外,解释一下为什么不能使用绑定会很有用,也会帮助人们选择最好的答案。 - igelineau

2
你可以创建一个 TextBox 的子类,然后重写 TextBox 的 Text 属性的元数据。
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;
    }
}

你根本不需要使用绑定,这只会在你输入时更新内部的文本框TextProperty

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

我熟悉 coerce 功能,但你已将其应用于你创建的 DP 上。我想在文本框中使用它。此外,这与绑定无关。我们希望在控件本身上实现这一点。不确定这段代码是否适用于该场景。如果我错了,请更新你的答案以展示它,我会将其标记为被接受的答案。 - Mark A. Donohoe
你可以创建一个自定义文本框并处理KeyDown/KeyUp事件,尝试使用“Key”参数查找输入的字符,更新字符串并将文本框值设置为新值。但这需要很多工作,并且在重新应用新值时会遇到破坏文本绑定的问题,从而导致多个PropertyChanged事件。 - sa_ddam213
那么,您使用的文本框没有绑定到任何东西吗?您只是使用 textbox1.Text 来获取值吗?因为您说这与绑定无关,但所有 WPF 值控件都与绑定有关。如果您在文本框中编辑值,那么您绑定的属性如何知道它已更改?唯一的其他解决方案将是使用 IValueConverter - sa_ddam213
“所有的WPF值控件都是关于绑定的”这个说法是错误的。在这种情况下,该控件是复合控件的一部分,我们的视图模型将其绑定。然而,在该控件内部,我们正在进行特殊处理的代码编写。如果我看起来有点不耐烦,那很抱歉,但是询问一个具体问题,然后得到许多试图解决我没有提出的问题的答案,而不了解我们要做什么,这真的很令人沮丧。我们不是在寻求如何设置我们的控件的建议。我们特别想知道如何在文本框中输入时直接或通过子类替换文本。 - Mark A. Donohoe
1
那我不明白问题所在,只需在TextChanged事件或KeyUp/KeyDown事件中更新字符串,就像在winforms中一样。 - sa_ddam213
我已经更新了我的第二个建议,因为它可能适用于你的情况。我知道它仍然使用强制转换,但你不必将任何东西绑定到它上面,它只会在你输入时更新文本框的文本,或者你可以像其他答案建议的那样使用OnTextChange。 - sa_ddam213

0
我尝试通过在我的ViewModel属性中处理来实现这个事情,该属性绑定到TextBox本身,如下所示,并且它可以工作。在这个例子中,我用下划线替换了'!'。在这里,我只是将替换逻辑放在属性setter中,如果有变化,我就替换文本并异步引发属性更改。
        private string text;
        public string Text
        {
            get { return text; }
            set
            {

                text = value;
                if (text.Contains("!"))
                {
                    text = text.Replace('!', '_');
                    Dispatcher.BeginInvoke((Action) (() => RaisePropertyChange("Text")));

                }
                RaisePropertyChange("Text");
            }
        }

文本框绑定是

<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>

我们不是在谈论绑定。我们特别询问的是当用户输入时替换文本框中的文本。换句话说,他们键入一个空格,但我们插入一个下划线。它必须处理粘贴等操作,而且与绑定无关,因为这不是一个绑定控件。 - Mark A. Donohoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接