2015-10-15 4 views
4

Я пытаюсь ограничить количество строк, которые пользователь может ввести в текстовое поле.Ограничить количество строк, введенных в текстовое поле WPF

Я занимаюсь исследованием - ближайший я могу найти это: Limit the max number of chars per line in a textbox.

И Limit the max number of chars per line in a textbox, который оказывается для winforms.

Это не совсем то, что мне нужно ... также стоит упомянуть, что есть вводящее в заблуждение свойство maxlines, которое я обнаружил, только ограничивает то, что показано в текстовом поле.

Мои требования:

  • Не требуют использования моно разнесены шрифта
  • Limit текстовое поле, чтобы имея максимум 5 линий
  • принимает возврат каретки
  • Не разрешайте возврат каретки
  • Остановка ввода текста, когда максимальная длина достигнута
  • Обтекает текст (не особо заботясь о том, s между словами или разрывает целые слова)
  • Обрабатывает текст, вставляемый в элемент управления, и будет вставлять только то, что подойдет. не
  • Нет полосы прокрутки
  • Кроме того - и это было бы неплохо - имея возможность ограничения количества символов в строке

Эти требования для создания WYSIWYG текстового поля, которое будет использоваться для сбора данных, которые будут в конечном итоге печататься, а шрифты должны быть изменены - если текст обрезается или слишком велик для строки фиксированного размера, тогда он будет распечатан (даже если он выглядит не так).

У меня был удар, когда я делал это сам, занимаясь событиями, но у меня есть большие проблемы с этим. Вот мой код до сих пор.

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

Я открыт для любых идей, как Я боролся с этим некоторое время. Требования, перечисленные выше, являются неизменными - я не могу их изменить.

ответ

4

Вот мое окончательное решение - мне все равно хотелось бы услышать, может ли кто-нибудь придумать лучший способ сделать это ...

Это просто обрабатывает максимальное количество строк - я еще ничего не сделал с максимальными символами, но это логически простое расширение того, что я уже сделал.

Поскольку я обрабатываю событие textChanged в текстовом поле - это также охватывает также pasing в элементе управления - я не нашел чистого способа обрезать текст в этом случае (если только я не обрабатываю 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" 
0

Я искал ответы на проблемы, подобные этому в течение некоторого времени, и каждый ответ, который я нашел, включает в себя прикрепление обработчиков событий и много написания кода. Мне это показалось неправдоподобным и, похоже, слишком сильно связывает GUI с Codebehind для моих вкусов. Кроме того, он, похоже, не использует возможности WPF.

Ограничение количества строк на самом деле является частью более общего вопроса: как вы ограничиваете что-либо в текстовом поле, когда оно редактируется?

Ответ на удивление прост: привяжите текстовое поле к пользовательскому DependencyProperty, затем используйте CoerceCallback для ограничения/изменения/изменения содержимого текстового поля.

Убедитесь, что вы правильно настроили контекст данных - самым простым (но не лучшим) способом является добавление строки: DataContext = "{Binding RelativeSource = {RelativeSource self}}" в начало вашего окна или UserControl XAML код.

XAML

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

Codebehind (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 не нужно ничего знать о том, откуда поступали данные или идет - это просто данные.

+0

Я не уверен, что это полезно в моей ситуации, когда я не знаю, сколько времени занимает строка, и я не ожидаю возврата каретки или новых строк в строке (так как это только пользователь, вводящий слово- обертывание текстового поля. Не зная, какой шрифт я использую (и, возможно, только в том случае, если я использую шрифт с одним интервалом), я не могу рассчитать длины строк в коде позади; единственный способ получить это, что я нашел, - попросить сам контроль, сколько строк было введено. Решение, которое я разместил, делает именно это и предотвращает дальнейший ввод. это хаки. – Jay

+0

Я неправильно понял то, что вы искали, когда вы говорили о количестве строк. Это легко решить - вы можете сделать что-то похожее на код в своем событии OnTextBoxTextChanged и адаптировать его для использования в методе CoerceTextLineLimiter. Передайте параметр d в текстовое поле: ((TextBox) d), чтобы получить значение LineCount, затем вы можете напрямую использовать значение текстовых данных ((String)), и данные в текстовом поле будут отвечать. Однако не нужно ссылаться. –

0

Это моя простая душа, чтобы установить MaxLines для TextBox, и она работает нормально, я надеюсь, что она соответствует вашим требованиям.

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

       }; 

С наилучшими пожеланиями

Ахмед Нура

0

Благодаря ответ Джея я был в состоянии найти лучшее решение для меня. Он отменит вставку и печать блоков.

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