WPF - MVVM文本框限制为特定字符

本文关键字:字符 MVVM 文本 WPF | 更新日期: 2023-09-27 18:12:39

我正在尝试使文本框只接受特定字符。

我的文本框绑定到以下内容:

    private string _CompanyID;
    public string CompanyID
    {
        get { return _CompanyID; }
        set
        {
            _CompanyID = UniversalHelpers.sReturnCorrectColumnName(value);
            OnPropertyChanged("CompanyID");
        }
    }

这是被调用的函数:

    public static string sReturnCorrectColumnName(string sInput)
    {
        if(!string.IsNullOrWhiteSpace(sInput))
            return Regex.Replace(sInput, @"[^a-zA-Z]", string.Empty).ToUpper();
        else
            return sInput;
    }

(我只允许a-z &A-Z,别无其他)。

最后我的文本框看起来是这样的:

<TextBox Text="{Binding ExcelBindings.CompanyID, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

我不明白的是,即使我的模式设置为双向,用户仍然可以写任何他想写的东西。

我做错了什么?

WPF - MVVM文本框限制为特定字符

你应该在那里使用一个自定义UI元素来限制视图端的输入,使用"经典"的解决方案,如更改侦听器。

例如,您可以创建一个简单的TextBox子类型来覆盖OnPreviewTextInput方法。在那里,您可以决定何时应该通过某些输入,或者何时想要阻止它。

例如,这是一个自定义TextBox,它只接受ASCII字母中的字符:

public class AlphabetTextBox : TextBox
{
    private static readonly Regex regex = new Regex("^[a-zA-Z]+$");
    protected override void OnPreviewTextInput(TextCompositionEventArgs e)
    {
        if (!regex.IsMatch(e.Text))
            e.Handled = true;
        base.OnPreviewTextInput(e);
    }
}

当然,您也可以使正则表达式成为文本框的一个属性,并允许人们从XAML设置它。这样,您将得到一个非常可重用的组件,您可以将其用于各种应用程序。

我使用previewtexttinput事件来完成此操作。我有一个用于多个textbox的通用事件,它从配置表中获取正则表达式,但我在本例中硬编码了正则表达式。

private void GenericTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
   e.Handled = !IsTextAllowed(e.Text, @"[^a-zA-Z]");
}
private static bool IsTextAllowed(string Text, string AllowedRegex)
{
    try
    {
        var regex = new Regex(AllowedRegex);
        return !regex.IsMatch(Text);
    }
    catch
    {
        return true;
    }
}

问题是,人类按顺序输入数字,傻瓜。
要输入"0.1",这是一个合法的字符串,您必须输入"0.",这将失败。此外,对于@poke接受的答案(这很棒),e.Text值是将更改为文本框(击键)。
您必须将此更改添加到当前文本框字符串,然后验证连接的候选字符串,并查看该字符串是否有效。人类也很狡猾,所以他们会从剪贴板上粘贴来绕过限制。对于文本框,你将永远无法阻止所有的垃圾,因为在某些时候,用户将不得不通过垃圾,以获得一个有效的字符串。
因此,您可以使用e.Text阻止非法字符输入,或者允许顺序步骤失败。但是您仍然需要检查最后字符串的有效性。
下面是一个文本框的例子,它允许用户输入一个最多8位的十进制值,但他们仍然可以通过从剪贴板粘贴来欺骗。

////////////////////////
// REGEXTEXTBOX CLASS //
////////////////////////

using System.Windows.Controls; // Textbox
using System.Windows.Input;
using System.Text.RegularExpressions; // Regex
namespace MyNamespace
{
    public class RegexTextBox : TextBox
    {
        private Regex _regex = null;
        public Regex Regex
        {
            get { return _regex; }
            set { _regex = value; }
        }

        ///////////////////////////////////////////////////////////////////////
        // MEMBERS
        protected override void OnPreviewTextInput(TextCompositionEventArgs e)
        {
            var prefix = "OnPreviewTextInput() - ";
            logger.Debug(prefix + "Entering");
            string currentText = this.Text;
            string candidateText = currentText + e.Text;
            // If we have a set regex, and the current text fails,
            // mark as handled so the text is not processed.
            if (_regex != null && !_regex.IsMatch(candidateText))
            {
                e.Handled = true;
            }           
            base.OnPreviewTextInput(e);
        }
    } // end of class RegexTextbox
} // end of MyNamespace

/////////////////////
// MAINWINDOW.XAML //
/////////////////////
//(Window class needs to know your namespace so it needs xmlns:myNamespace="clr-namespace:MyNamespace")
<myNamespace:RegexTextBox 
 x:Name="textboxPayToAmount" 
 Text="{Binding PayToAmount}">
</myNamespace:RegexTextBox> 

////////////////////////
// MAINWINDOW.XAML.CS //
////////////////////////
namespace MyNamespace
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            textboxPayToAmount.Regex = 
                new System.Text.RegularExpressions.Regex(@"^'d*('.'d{0,8})?$");
        }
    }
}
Public Shared Function GetWordCount(str As String) As Integer
    Dim collection As MatchCollection = Regex.Matches(str, "'S+")
    Return collection.Count
End Function
Public Shared Function GetInWordLimit(str As String, max_words As Integer) As String
    Dim final As String = ""
    Dim count As Integer = Core.StringOperations.GetWordCount(str)
    Dim avg_max_length As Integer = max_words * 7
    Dim words = str.Split(" ")
    If (words.Length > max_words - 1 And count > max_words - 1) Then
        Dim index As Integer = 0
        For Each word In words
            If index >= max_words Then Exit For
            final &= word & " "
            If Not (Char.IsSeparator(word) Or Char.IsWhiteSpace(word) Or word = "") Then
                index += 1
            End If
        Next
        final = final.TrimEnd
    Else
        final = str
    End If
    If final.Length > avg_max_length - 1 Then final = final.Substring(0, avg_max_length)
    Return final
End Function