Limiting text length in a TextBox by its encoded representation

I have a TextBox in WPF. I want to limit the length of text in a TextBox . There is an easy way to limit the number of characters with the MaxLength property.

In my use case, I need to limit the text not to the number of characters, but to the length of the binary representation of the text in this encoding. Since the program is used by German, there are some umlauts that consume two bytes.

I already have a method that checks if a given string matches a given length:

 public bool IsInLength(string text, int maxLength, Encoding encoding) { return encoding.GetByteCount(text) < maxLength; } 

Does anyone have an idea to associate this function with a text field in such a way that the user is not able to enter too many characters to exceed the maximum byte length.

Decisions without EventHandler are preferable as TextBox is in DataTemplate .

+5
source share
2 answers

A ValidationRule may be what matches the score here. Here is an example implementation:

 public sealed class ByteCountValidationRule : ValidationRule { // For this example I test using an emoji (😄) which will take 2 bytes and fail this rule. static readonly int MaxByteCount = 1; static readonly ValidationResult ByteCountExceededResult = new ValidationResult(false, $"Byte count exceeds the maximum allowed limit of {MaxByteCount}"); public override ValidationResult Validate(object value, CultureInfo cultureInfo) { var val = value as string; return val != null && Encoding.UTF8.GetByteCount(val) > MaxByteCount ? ByteCountExceededResult : ValidationResult.ValidResult; } } 

And using XAML:

  <TextBox.Text> <Binding Path="Text" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:ByteCountValidationRule /> </Binding.ValidationRules> </Binding> </TextBox.Text> 

Now you can either put 1 emoji or 2 ascii characters to cause a crash (as either it will exceed 1 byte limit).

+4
source

I have expanded Alex Klaus' solution to prevent typing too long texts.

 public class TextBoxMaxLengthBehavior : Behavior<TextBox> { public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register( nameof(MaxLength), typeof(int), typeof(TextBoxMaxLengthBehavior), new FrameworkPropertyMetadata(0)); public int MaxLength { get { return (int) GetValue(MaxLengthProperty); } set { SetValue(MaxLengthProperty, value); } } public static readonly DependencyProperty LengthEncodingProperty = DependencyProperty.Register( nameof(LengthEncoding), typeof(Encoding), typeof(TextBoxMaxLengthBehavior), new FrameworkPropertyMetadata(Encoding.Default)); public Encoding LengthEncoding { get { return (Encoding) GetValue(LengthEncodingProperty); } set { SetValue(LengthEncodingProperty, value); } } protected override void OnAttached() { base.OnAttached(); AssociatedObject.PreviewTextInput += PreviewTextInputHandler; DataObject.AddPastingHandler(AssociatedObject, PastingHandler); } protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.PreviewTextInput -= PreviewTextInputHandler; DataObject.RemovePastingHandler(AssociatedObject, PastingHandler); } private void PreviewTextInputHandler(object sender, TextCompositionEventArgs e) { string text; if (AssociatedObject.Text.Length < AssociatedObject.CaretIndex) text = AssociatedObject.Text; else { // Remaining text after removing selected text. string remainingTextAfterRemoveSelection; text = TreatSelectedText(out remainingTextAfterRemoveSelection) ? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text) : AssociatedObject.Text.Insert(AssociatedObject.CaretIndex, e.Text); } e.Handled = !ValidateText(text); } private bool TreatSelectedText(out string text) { text = null; if (AssociatedObject.SelectionLength <= 0) return false; var length = AssociatedObject.Text.Length; if (AssociatedObject.SelectionStart >= length) return true; if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length) AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart; text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength); return true; } private void PastingHandler(object sender, DataObjectPastingEventArgs e) { if (e.DataObject.GetDataPresent(DataFormats.Text)) { var pastedText = Convert.ToString(e.DataObject.GetData(DataFormats.Text)); var text = ModifyTextToFit(pastedText); if (!ValidateText(text)) e.CancelCommand(); else if (text != pastedText) e.DataObject.SetData(DataFormats.Text, text); } else e.CancelCommand(); } private string ModifyTextToFit(string text) { var result = text.Remove(MaxLength); while (!string.IsNullOrEmpty(result) && !ValidateText(result)) result = result.Remove(result.Length - 1); return result; } private bool ValidateText(string text) { return LengthEncoding.GetByteCount(text) <= MaxLength; } } 

In XAML, I can use it as follows:

 <DataTemplate DataType="{x:Type vm:StringViewModel}"> <TextBox Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"> <i:Interaction.Behaviors> <b:TextBoxMaxLengthBehavior MaxLength="{Binding MaxLength}" LengthEncoding="{Binding LengthEncoding}" /> </i:Interaction.Behaviors> </TextBox> </DataTemplate> 

where xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" . Hope this helps someone else.

-2
source

Source: https://habr.com/ru/post/1270420/


All Articles