限制WPF文本框中输入的行数

5
我想限制用户在文本框中输入的行数。
我已经进行了一些研究——最接近我的是这个:限制文本框中每行字符的最大数量
还有一个限制文本框中每行字符的最大数量,但结果是针对winforms的。
这并不完全符合我的要求...值得一提的是,有一个误导性的maxlines属性,我发现它只限制了文本框中显示的内容。
我的要求如下:
  • 不需要使用等宽字体
  • 将文本框限制为最多5行
  • 接受回车
  • 不允许额外的回车
  • 当达到最大长度时停止文本输入
  • 换行(不特别关心是在单词之间换行还是整个单词分开)
  • 处理文本被粘贴到控件中,并只粘贴适合的文本。
  • 没有滚动条
  • 此外,如果可以限制每行的字符数,那就太好了
这些要求是为了创建一个所见即所得的文本框,用于捕获最终将被打印的数据,并且字体需要可变——如果文本被截断或太大而无法适应固定大小的行,则在打印时会呈现出这种情况(即使看起来不正确)。
我已经尝试通过处理事件来实现这一点,但是很难做到正确。以下是我的代码: XAML
 <TextBox TextWrapping="Wrap" AcceptsReturn="True"
        PreviewTextInput="UIElement_OnPreviewTextInput"
        TextChanged="TextBoxBase_OnTextChanged" />

代码后台

 public int TextBoxMaxAllowedLines { get; set; }
    public int TextBoxMaxAllowedCharactersPerLine { get; set; }


    public MainWindow()
    {
        InitializeComponent();

        TextBoxMaxAllowedLines = 5;
        TextBoxMaxAllowedCharactersPerLine = 50;
    }

    private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
    {
        TextBox textBox = (TextBox)sender;

        int textLineCount = textBox.LineCount;

        if (textLineCount > TextBoxMaxAllowedLines)
        {
            StringBuilder text = new StringBuilder();
            for (int i = 0; i < TextBoxMaxAllowedLines; i++)
                text.Append(textBox.GetLineText(i));

            textBox.Text = text.ToString();
        }

    }

    private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        TextBox textBox = (TextBox)sender;

        int textLineCount = textBox.LineCount;


        for (int i = 0; i < textLineCount; i++)
        {
            var line = textBox.GetLineText(i);

            if (i == TextBoxMaxAllowedLines-1)
            {
                int selectStart = textBox.SelectionStart;
                textBox.Text = textBox.Text.TrimEnd('\r', '\n');
                textBox.SelectionStart = selectStart;

                //Last line
                if (line.Length > TextBoxMaxAllowedCharactersPerLine)
                    e.Handled = true;
            }
            else
            {
                if (line.Length > TextBoxMaxAllowedCharactersPerLine-1 && !line.EndsWith("\r\n"))
                    e.Handled = true;    
            }

        }
    }

这个好像不太对——最后一行的行为很奇怪,文本框内选择的位置总是跳来跳去。
另外,也许我走了一条错误的路...... 我还想知道是否可以使用正则表达式来实现这一目标,例如使用这个:https://dev59.com/4XNA5IYBdhLWcg3wEZeT#1103822 我对任何想法都持开放态度,因为我已经苦恼了一段时间。上面列出的要求是不可改变的,我无法更改它们。
6个回答

5
这是我的最终解决方案 - 我仍然希望听到任何人能够提出更好的方法来做到这一点...
这只处理了最大行数 - 我还没有对最大字符数做任何处理 - 但是逻辑上它已经是我已经完成的东西的简单扩展。
由于我正在处理文本框的textChanged事件 - 这也涵盖了对控件的解析 - 我还没有找到一种干净的方式来截断此事件中的文本(除非我单独处理键预览) - 所以我只是通过撤消不允许无效输入。
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True" 
             HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
       <i:Interaction.Behaviors>
            <lineLimitingTextBoxWpfTest:LineLimitingBehavior TextBoxMaxAllowedLines="5" />

        </i:Interaction.Behaviors>
    </TextBox>

代码(用于行为)

/// <summary> limits the number of lines the textbox will accept </summary>
public class LineLimitingBehavior : Behavior<TextBox>
{
    /// <summary> The maximum number of lines the textbox will allow </summary>
    public int? TextBoxMaxAllowedLines { get; set; }

    /// <summary>
    /// Called after the behavior is attached to an AssociatedObject.
    /// </summary>
    /// <remarks>
    /// Override this to hook up functionality to the AssociatedObject.
    /// </remarks>
    protected override void OnAttached()
    {
        if (TextBoxMaxAllowedLines != null && TextBoxMaxAllowedLines > 0)
            AssociatedObject.TextChanged += OnTextBoxTextChanged;
    }

    /// <summary>
    /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
    /// </summary>
    /// <remarks>
    /// Override this to unhook functionality from the AssociatedObject.
    /// </remarks>
    protected override void OnDetaching()
    {
        AssociatedObject.TextChanged -= OnTextBoxTextChanged;
    }

    private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
    {
        TextBox textBox = (TextBox)sender;

        int textLineCount = textBox.LineCount;

        //Use Dispatcher to undo - https://dev59.com/KWgu5IYBdhLWcg3wv5Ya#25453051
        if (textLineCount > TextBoxMaxAllowedLines.Value)
            Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action) (() => textBox.Undo()));
    }
}

这需要将System.Windows.InterActivity添加到项目并在XAML中引用,如下所示:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

1
这是我设置文本框最大行数的简单解决方案,已经正常工作,希望它能满足您的要求。
My_Defined_MaxTextLength是设置最大长度的属性。
My_MaxLines是设置最大行数的属性。
My_TextBox.TextChanged += (sender, e) =>
                 {
                     if(My_TextBox.LineCount > My_MaxLines)
                     {
                         My_TextBox.MaxLength = My_TextBox.Text.Length;
                     }
                     else
                     {
                         My_TextBox.MaxLength = My_Defined_MaxTextLength;
                     }

                 };

最好的问候

Ahmed Nour


0
string prev_text = string.Empty;
    private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
    {
        int MaxLineCount = 5;
        if (textBox1.LineCount > MaxLineCount)
        {
            int index = textBox1.CaretIndex;
            textBox1.Text = prev_text;
            textBox1.CaretIndex = index;
        }
        else
        {
            prev_text = textBox1.Text;
        }
    }

0
这样做可以限制行数并显示最后添加的行,而不是显示第一行。
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
    PreviewTextInput="UIElement_OnPreviewTextInput"
    TextChanged="TextBoxBase_OnTextChanged" />

代码

const int MaxLineCount = 10;

private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
    TextBox textBox = (TextBox)sender;

    int textLineCount = textBox.LineCount;

    if (textLineCount > MaxLineCount)
    {
        StringBuilder text = new StringBuilder();
        for (int i = 0; i < MaxLineCount; i++)
        {
            text.Append(textBox.GetLineText((textLineCount - MaxLineCount) + i - 1));
        }
        textBox.Text = text.ToString();
    }
}

0

我一直在寻找类似问题的答案,但每个答案都涉及到附加事件处理程序和编写大量代码。这对我来说似乎不太合适,并且似乎将GUI与Codebehind联系得过于紧密,不符合我的口味。此外,它似乎没有充分利用WPF的功能。

限制行数实际上是更通用的问题的一部分:如何在编辑时限制文本框中的任何内容?

答案出奇的简单:将您的文本框绑定到自定义DependencyProperty,然后使用CoerceCallback来限制/改变文本框的内容。

确保正确设置数据上下文 - 最简单(但不是最好)的方法是在窗口或用户控件XAML代码的顶部添加以下行:DataContext =“{Binding RelativeSource = {RelativeSource self}}”。

XAML

<TextBox TextWrapping="Wrap"
     Text="{Binding NotesText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
     AcceptsReturn="True"
     HorizontalScrollBarVisibility="Disabled"
     VerticalScrollBarVisibility="Disabled">

代码后端(C#)

    const int MaxLineCount = 10;
    const int MaxLineLength = 200;

    public static readonly DependencyProperty NotesTextProperty =
            DependencyProperty.Register(
                name: "NotesText",
                propertyType: typeof( String ),
                ownerType: typeof( SampleTextBoxEntryWindow ),
                typeMetadata: new PropertyMetadata(
                    defaultValue: string.Empty,
                    propertyChangedCallback: OnNotesTextPropertyChanged,
                    coerceValueCallback: CoerceTextLineLimiter ) );

    public string NotesText
    {
        get { return (String)GetValue( NotesTextProperty ); }
        set { SetValue( NotesTextProperty, value ); }
    }

    private static void OnNotesTextPropertyChanged(DependencyObject source,
        DependencyPropertyChangedEventArgs e)
    {
        // Whatever you want to do when the text changes, like 
        // set flags to allow buttons to light up, etc.
    }

    private static object CoerceTextLineLimiter(DependencyObject d, object value)
    {
        string result = null;

        if (value != null)
        {
            string text = ((string)value);
            string[] lines = text.Split( '\n' );

            if (lines.Length <= MaxLineCount)
                result = text;
            else
            {
                StringBuilder obj = new StringBuilder();
                for (int index = 0; index < MaxLineCount; index++)
                    if (lines[index].Length > 0)
                        obj.AppendLine( lines[index] > MaxLineLength ? lines[index].Substring(0, MaxLineLength) : lines[index] );

                result = obj.ToString();
            }
        }
        return result;
    }

(行限制代码很粗糙 - 但你明白我的意思)。

酷的是,这提供了一个简单的框架来做其他事情,比如限制数字或字母或特殊字符 - 例如,这里是一个简单的(非Regx)电话号码强制方法:

private static object CoercePhoneNumber(DependencyObject d, object value)
    {
        StringBuilder result = new StringBuilder();

        if (value != null)
        {
            string text = ((string)value).ToUpper();

            foreach (char chr in text)
                if ((chr >= '0' && chr <= '9') || (chr == ' ') || (chr == '-') || (chr == '(') || (chr == ')'))
                    result.Append( chr );

        }
        return result.ToString();
    }

这对我来说似乎是一个更清晰、更易于维护的解决方案,可以很容易地进行重构 - 同时尽可能将数据和演示分开。Coerce方法不需要知道数据来自哪里或去哪里 - 它只是数据。


我不确定这在我的情况下是否有用,因为我不知道行有多长,并且字符串中也没有换行符或新行(因为这只是用户在文本框中输入文字)。除非我知道使用的字体(可能仅限于等宽字体),否则我无法在代码后台计算行长度;我找到的唯一方法就是询问控件本身输入了多少行。我发布的解决方案确实做到了这一点,并防止了进一步的输入。但这种方法有些hacky。 - Jay
当你谈到行数时,我误解了你的意思。这很容易解决 - 你可以做类似于你在OnTextBoxTextChanged事件中的代码,并将其适应于CoerceTextLineLimiter方法的使用。将参数d转换为Textbox:((TextBox)d)以获取LineCount值,然后您可以直接操作文本数据((String)value),文本框中的数据将响应。不过,应该没有必要调用。 - Thomas Phaneuf

0
感谢Jay的回答,我找到了最适合我的解决方案。 它将撤消粘贴并阻止输入。
public class LineLimitingBehavior : Behavior<TextBox>
{
    public int? TextBoxMaxAllowedLines { get; set; }

    protected override void OnAttached()
    {
        if (TextBoxMaxAllowedLines == null || !(TextBoxMaxAllowedLines > 0)) return;

        AssociatedObject.PreviewTextInput += OnTextBoxPreviewTextInput;
        AssociatedObject.TextChanged += OnTextBoxTextChanged;
    }

    private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
    {
        var textBox = (TextBox)sender;

        if (textBox.LineCount > TextBoxMaxAllowedLines.Value)
            Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action)(() => textBox.Undo()));
    }

    private void OnTextBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var textBox = (TextBox)sender;
        var currentText = textBox.Text;
        textBox.Text += e.Text;

        if (textBox.LineCount > TextBoxMaxAllowedLines.Value)
            e.Handled = true;

        textBox.Text = currentText;
        textBox.CaretIndex = textBox.Text.Length;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.PreviewTextInput -= OnTextBoxPreviewTextInput;
        AssociatedObject.TextChanged -= OnTextBoxTextChanged;
    }
}

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