【问题标题】:Limit number of lines entered in WPF textbox限制在 WPF 文本框中输入的行数
【发布时间】:2016-01-13 21:04:20
【问题描述】:

我正在尝试限制用户可以在文本框中输入的行数。

我一直在研究 - 我能找到的最接近的是: Limit the max number of chars per line in a textbox.

Limit the max number of chars per line in a textbox 原来是用于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://stackoverflow.com/a/1103822/685341

我对任何想法都持开放态度,因为我已经为此苦苦挣扎了一段时间。上面列出的要求是不可变的 - 我无法更改它们。

【问题讨论】:

    标签: c# wpf textbox limit


    【解决方案1】:
    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;
            }
        }
    

    【讨论】:

      【解决方案2】:

      这应该会限制行数并将显示最后添加的行,而不是显示第一行:

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

      【讨论】:

        【解决方案3】:

        感谢 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;
            }
        }
        

        【讨论】:

          【解决方案4】:

          这是我为 TextBox 设置 MaxLines 的简单方法,它工作正常,我希望它符合您的要求。

          My_Defined_MaxTextLength 是设置 MaxLenght 的属性

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

          最好的问候

          艾哈迈德·努尔

          【讨论】:

            【解决方案5】:

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

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

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

            确保正确设置数据上下文 - 最简单(但不是最好)的方法是将以下行:DataContext="{Binding RelativeSource={RelativeSource self}}" 添加到 Window 或 UserControl XAML 代码的顶部.

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

            (行限制代码很粗略 - 但你明白了)。

            很酷的是,这是否提供了一个简单的框架来做其他事情,比如限制数字或 alpha 或特殊的东西 - 例如,这是一个简单的(非 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。
            • 我误解了您在谈论行数时要查找的内容。这很容易解决 - 您可以执行类似于 OnTextBoxTextChanged 事件中的代码的操作,并将其调整为在 CoerceTextLineLimiter 方法中使用。将参数d转换为Textbox:((TextBox)d)得到LineCount值,然后可以直接操作文本数据((String)value),文本框中的数据会响应。不过,应该不需要调用。
            【解决方案6】:

            这是我的最终解决方案 - 我仍然想听听是否有人可以提出更好的方法......

            这只是处理最大行数——我还没有用最大字符做任何事情——但从逻辑上讲,它是对我已经完成的事情的简单扩展。

            当我正在处理文本框的 textChanged 事件时——这也包括向控件中的传递——我还没有找到一种干净的方法来截断此事件中的文本(除非我单独处理 key_preview)——所以我m 只是不允许通过撤消进行无效输入。

            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 - http://stackoverflow.com/a/25453051/685341
                    if (textLineCount > TextBoxMaxAllowedLines.Value)
                        Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action) (() => textBox.Undo()));
                }
            }
            

            这需要将 System.Windows.InterActivity 添加到项目中并因此在 XAML 中引用:

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

            【讨论】:

              猜你喜欢
              • 2012-09-06
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-01-31
              相关资源
              最近更新 更多