Reputation: 193
I am developing a WinUI 3 application and would like to use TextBoxExtensions.Mask from the Windows Community Toolkit to restrict input to IP addresses.
For example: 192.1.1.0 or 255.255.255.255.
I tried the following, but the numeric parts have a fixed width, and I haven't been able to specify a range of 1 to 3 characters correctly.
Is there a good way to achieve this?
<TextBox controls:TextBoxExtensions.Mask="999.999.999.999" />
Upvotes: 1
Views: 60
Reputation: 13666
You can also create an AttachedProperty:
<TextBox local:TextBoxExtensions.EnableIpAddressMask="True" />
public static partial class TextBoxExtensions
{
public static readonly DependencyProperty EnableIpAddressMaskProperty =
DependencyProperty.RegisterAttached(
"EnableIpAddressMask",
typeof(bool),
typeof(TextBoxExtensions),
new PropertyMetadata(default, OnEnableIpAddressMaskChanged));
public static readonly DependencyProperty PreviousTextProperty =
DependencyProperty.RegisterAttached(
"PreviousText",
typeof(string),
typeof(TextBoxExtensions),
new PropertyMetadata(string.Empty));
public static bool GetEnableIpAddressMask(DependencyObject obj) => (bool)obj.GetValue(EnableIpAddressMaskProperty);
public static void SetEnableIpAddressMask(DependencyObject obj, bool value) => obj.SetValue(EnableIpAddressMaskProperty, value);
public static string GetPreviousText(DependencyObject obj) => (string)obj.GetValue(PreviousTextProperty);
public static void SetPreviousText(DependencyObject obj, string value) => obj.SetValue(PreviousTextProperty, value);
private static void OnEnableIpAddressMaskChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not TextBox textBox)
{
return;
}
textBox.Text = "...";
textBox.PreviewKeyDown -= TextBox_PreviewKeyDown;
textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
textBox.TextChanged -= TextBox_TextChanged;
textBox.TextChanged += TextBox_TextChanged;
}
private static void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (sender is not TextBox textBox)
{
return;
}
string previousText = GetPreviousText(textBox);
if (textBox.Text.Length < previousText.Length)
{
return;
}
var segments = textBox.Text.Split('.');
int currentSegmentIndex = textBox.Text[..textBox.SelectionStart].Count(c => c == '.');
if (textBox.Text.ElementAtOrDefault(textBox.SelectionStart) is '.' &&
segments[currentSegmentIndex].Length == 3)
{
textBox.SelectionStart++;
}
SetPreviousText(textBox, textBox.Text);
}
private static void TextBox_PreviewKeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (sender is not TextBox textBox)
{
return;
}
if (IsInvalidKey(e.Key) is true ||
IsRemovingSeparator(e.Key, textBox.Text, textBox.SelectionStart) is true)
{
e.Handled = true;
return;
}
if (TryGetNumber(e.Key, out string number) is false)
{
return;
}
var nextText = textBox.Text.Insert(textBox.SelectionStart, number);
var segments = nextText.Split('.');
foreach (var segment in segments)
{
if (int.TryParse(segment, out int segmentValue) is false)
{
continue;
}
if (segmentValue < 0 || segmentValue > 255)
{
e.Handled = true;
return;
}
}
}
private static bool IsRemovingSeparator(VirtualKey key, string text, int selectionStart)
{
if (key == VirtualKey.Back &&
text.ElementAtOrDefault(selectionStart - 1) is '.')
{
return true;
}
if (key == VirtualKey.Delete &&
text.ElementAtOrDefault(selectionStart) is '.')
{
return true;
}
return false;
}
private static bool IsInvalidKey(VirtualKey key)
{
return key switch
{
VirtualKey.Number0 or
VirtualKey.Number1 or
VirtualKey.Number2 or
VirtualKey.Number3 or
VirtualKey.Number4 or
VirtualKey.Number5 or
VirtualKey.Number6 or
VirtualKey.Number7 or
VirtualKey.Number8 or
VirtualKey.Number9 or
VirtualKey.NumberPad0 or
VirtualKey.NumberPad1 or
VirtualKey.NumberPad2 or
VirtualKey.NumberPad3 or
VirtualKey.NumberPad4 or
VirtualKey.NumberPad5 or
VirtualKey.NumberPad6 or
VirtualKey.NumberPad7 or
VirtualKey.NumberPad8 or
VirtualKey.NumberPad9 or
VirtualKey.Back or
VirtualKey.Delete or
VirtualKey.Left or
VirtualKey.Right or
VirtualKey.Home or
VirtualKey.End => false,
_ => true,
};
}
private static bool TryGetNumber(VirtualKey key, out string number)
{
number = key switch
{
VirtualKey.Number0 or VirtualKey.NumberPad0 => "0",
VirtualKey.Number1 or VirtualKey.NumberPad1 => "1",
VirtualKey.Number2 or VirtualKey.NumberPad2 => "2",
VirtualKey.Number3 or VirtualKey.NumberPad3 => "3",
VirtualKey.Number4 or VirtualKey.NumberPad4 => "4",
VirtualKey.Number5 or VirtualKey.NumberPad5 => "5",
VirtualKey.Number6 or VirtualKey.NumberPad6 => "6",
VirtualKey.Number7 or VirtualKey.NumberPad7 => "7",
VirtualKey.Number8 or VirtualKey.NumberPad8 => "8",
VirtualKey.Number9 or VirtualKey.NumberPad9 => "9",
_ => string.Empty,
};
return number.Length > 0;
}
}
Upvotes: 1
Reputation: 13666
Unfortunately, it seems that you can't achieve it with the CommunityToolkit's TextBoxExtensions.
The following control might not be perfect but should give something to work on:
public partial class IPAddressBox : TextBox
{
public IPAddressBox()
{
PreviewKeyDown += IPAddressBox_PreviewKeyDown;
TextChanged += IPAddressBox_TextChanged;
Text = "...";
}
private string PreviousText { get; set; } = string.Empty;
private static bool IsInvalidKey(VirtualKey key)
{
return key switch
{
VirtualKey.Number0 or
VirtualKey.Number1 or
VirtualKey.Number2 or
VirtualKey.Number3 or
VirtualKey.Number4 or
VirtualKey.Number5 or
VirtualKey.Number6 or
VirtualKey.Number7 or
VirtualKey.Number8 or
VirtualKey.Number9 or
VirtualKey.NumberPad0 or
VirtualKey.NumberPad1 or
VirtualKey.NumberPad2 or
VirtualKey.NumberPad3 or
VirtualKey.NumberPad4 or
VirtualKey.NumberPad5 or
VirtualKey.NumberPad6 or
VirtualKey.NumberPad7 or
VirtualKey.NumberPad8 or
VirtualKey.NumberPad9 or
VirtualKey.Back or
VirtualKey.Delete or
VirtualKey.Left or
VirtualKey.Right or
VirtualKey.Home or
VirtualKey.End => false,
_ => true,
};
}
private static bool TryGetNumber(VirtualKey key, out string number)
{
number = key switch
{
VirtualKey.Number0 or VirtualKey.NumberPad0 => "0",
VirtualKey.Number1 or VirtualKey.NumberPad1 => "1",
VirtualKey.Number2 or VirtualKey.NumberPad2 => "2",
VirtualKey.Number3 or VirtualKey.NumberPad3 => "3",
VirtualKey.Number4 or VirtualKey.NumberPad4 => "4",
VirtualKey.Number5 or VirtualKey.NumberPad5 => "5",
VirtualKey.Number6 or VirtualKey.NumberPad6 => "6",
VirtualKey.Number7 or VirtualKey.NumberPad7 => "7",
VirtualKey.Number8 or VirtualKey.NumberPad8 => "8",
VirtualKey.Number9 or VirtualKey.NumberPad9 => "9",
_ => string.Empty,
};
return number.Length > 0;
}
private void IPAddressBox_PreviewKeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (IsInvalidKey(e.Key) is true ||
IsRemovingSeparator(e.Key) is true)
{
e.Handled = true;
return;
}
if (TryGetNumber(e.Key, out string number) is false)
{
return;
}
var nextText = Text.Insert(SelectionStart, number);
var segments = nextText.Split('.');
foreach (var segment in segments)
{
if (int.TryParse(segment, out int segmentValue) is false)
{
continue;
}
if (segmentValue < 0 || segmentValue > 255)
{
e.Handled = true;
return;
}
}
}
private bool IsRemovingSeparator(VirtualKey key)
{
if (key == VirtualKey.Back &&
Text.ElementAtOrDefault(SelectionStart - 1) is '.')
{
return true;
}
if (key == VirtualKey.Delete &&
Text.ElementAtOrDefault(SelectionStart) is '.')
{
return true;
}
return false;
}
private void IPAddressBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (Text.Length < PreviousText.Length)
{
return;
}
var segments = Text.Split('.');
int currentSegmentIndex = Text[..SelectionStart].Count(c => c == '.');
if (Text.ElementAtOrDefault(SelectionStart) is '.' &&
segments[currentSegmentIndex].Length == 3)
{
SelectionStart++;
}
PreviousText = Text;
}
}
Upvotes: 1