I am trying to limit the number of lines a user can enter in a textbox.
I have been researching - the closest I can find is this: Limit the max number of chars per line in a textbox .
And Limit the max number of chars per line in a textbox which turns out to be for winforms.
This isn't quite what I'm after... also worth mentioning that there is a misleading maxlines property which I have discovered only limits what is shown in the text box.
My requirements:
These requirements are for creating a WYSIWYG textbox which will be used for capturing data that will eventually be printed, and the fonts need to be changeable - if the text gets cut off or is too big for a fixed size line - then it will come out that way in print (even if it does not look right).
I've had a stab at doing this myself by handling events - but am having a great deal of trouble getting this right. Here is my code so far.
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
PreviewTextInput="UIElement_OnPreviewTextInput"
TextChanged="TextBoxBase_OnTextChanged" />
Code Behind
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;
}
}
}
This doesn't quite work right - I am getting strange behaviour on the last line and the selected position within the textbox keeps jumping about.
As an aside, maybe I am going down the wrong track... I was also wondering if this could be achieved by using a regular expression using something like this: https://stackoverflow.com/a/1103822/685341
I am open to any ideas as I have been struggling with this for a while. The requirements listed above are immutable - I am unable to change them.
Here is my final solution - I'd still like to hear if anyone can come up with a better way of doing this...
This just handles max number of lines - I haven't done anything with max characters yet - but it's logically a simple extension to what I have already done.
As I'm handling the textChanged event of the textbox - this also covers pasing into the control too - I haven't found a clean way to truncate text in this event (unless I handle the key_preview separately) - so I'm just not allowing invalid input by undoing.
XAML
<TextBox TextWrapping="Wrap" AcceptsReturn="True"
HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
<i:Interaction.Behaviors>
<lineLimitingTextBoxWpfTest:LineLimitingBehavior TextBoxMaxAllowedLines="5" />
</i:Interaction.Behaviors>
</TextBox>
Code (for behaviour)
/// <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()));
}
}
This requires System.Windows.InterActivity to be added to the project and referenced in XAML thusly:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
That is my simple soultion to set the MaxLines for TextBox and it is working fine, I hope it matches your requirements.
My_Defined_MaxTextLength is a property to set the MaxLenght
My_MaxLines is a property to set Maximum lines
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;
}
};
Best Regards
Ahmed Nour
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With