我正在尝试构建如下所示的IP地址输入框掩码:
我不喜欢的是数字之间的空格。我的问题是,如何防止数字之间的空格并强制用户写一个数字而不是左边的空格。
行为类
public class TextBoxInputMaskBehavior : Behavior<TextBox>
{
public MaskedTextProvider Provider { get; private set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectLoaded;
AssociatedObject.PreviewTextInput += AssociatedObjectPreviewTextInput;
AssociatedObject.PreviewKeyDown += AssociatedObjectPreviewKeyDown;
DataObject.AddPastingHandler(AssociatedObject, Pasting);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObjectLoaded;
AssociatedObject.PreviewTextInput -= AssociatedObjectPreviewTextInput;
AssociatedObject.PreviewKeyDown -= AssociatedObjectPreviewKeyDown;
DataObject.RemovePastingHandler(AssociatedObject, Pasting);
}
/*
Mask Character Accepts Required?
0 Digit (0-9) Required
9 Digit (0-9) or space Optional
# Digit (0-9) or space Required
L Letter (a-z, A-Z) Required
? Letter (a-z, A-Z) Optional
& Any character Required
C Any character Optional
A Alphanumeric (0-9, a-z, A-Z) Required
a Alphanumeric (0-9, a-z, A-Z) Optional
Space separator Required
. Decimal separator Required
, Group (thousands) separator Required
: Time separator Required
/ Date separator Required
$ Currency symbol Required
In addition, the following characters have special meaning:
Mask Character Meaning
< All subsequent characters are converted to lower case
> All subsequent characters are converted to upper case
| Terminates a previous < or >
\ Escape: treat the next character in the mask as literal text rather than a mask symbol
*/
private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
Provider = new MaskedTextProvider(InputMask, CultureInfo.CurrentCulture);
Provider.Set(AssociatedObject.Text);
Provider.PromptChar = PromptChar;
AssociatedObject.Text = Provider.ToDisplayString();
//seems the only way that the text is formatted correct, when source is updated
var textProp = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof (TextBox));
if (textProp != null)
{
textProp.AddValueChanged(AssociatedObject, (s, args) => UpdateText());
}
}
private void AssociatedObjectPreviewTextInput(object sender, TextCompositionEventArgs e)
{
TreatSelectedText();
var position = GetNextCharacterPosition(AssociatedObject.SelectionStart);
if (Keyboard.IsKeyToggled(Key.Insert))
{
if (Provider.Replace(e.Text, position))
position++;
}
else
{
if (Provider.InsertAt(e.Text, position))
position++;
}
position = GetNextCharacterPosition(position);
RefreshText(position);
e.Handled = true;
}
private void AssociatedObjectPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space) //handle the space
{
TreatSelectedText();
var position = GetNextCharacterPosition(AssociatedObject.SelectionStart);
if (Provider.InsertAt(" ", position))
RefreshText(position);
e.Handled = true;
}
if (e.Key == Key.Back) //handle the back space
{
if (TreatSelectedText())
{
RefreshText(AssociatedObject.SelectionStart);
}
else
{
if (AssociatedObject.SelectionStart != 0)
{
if (Provider.RemoveAt(AssociatedObject.SelectionStart - 1))
RefreshText(AssociatedObject.SelectionStart - 1);
}
}
e.Handled = true;
}
if (e.Key == Key.Delete) //handle the delete key
{
//treat selected text
if (TreatSelectedText())
{
RefreshText(AssociatedObject.SelectionStart);
}
else
{
if (Provider.RemoveAt(AssociatedObject.SelectionStart))
RefreshText(AssociatedObject.SelectionStart);
}
e.Handled = true;
}
}
/// <summary>
/// Pasting prüft ob korrekte Daten reingepastet werden
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Pasting(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof (string)))
{
var pastedText = (string) e.DataObject.GetData(typeof (string));
TreatSelectedText();
var position = GetNextCharacterPosition(AssociatedObject.SelectionStart);
if (Provider.InsertAt(pastedText, position))
{
RefreshText(position);
}
}
e.CancelCommand();
}
private void UpdateText()
{
//check Provider.Text + TextBox.Text
if (Provider.ToDisplayString().Equals(AssociatedObject.Text))
return;
//use provider to format
var success = Provider.Set(AssociatedObject.Text);
//ui and mvvm/codebehind should be in sync
SetText(success ? Provider.ToDisplayString() : AssociatedObject.Text);
}
/// <summary>
/// Falls eine Textauswahl vorliegt wird diese entsprechend behandelt.
/// </summary>
/// <returns>true Textauswahl behandelt wurde, ansonsten falls </returns>
private bool TreatSelectedText()
{
if (AssociatedObject.SelectionLength > 0)
{
return Provider.RemoveAt(AssociatedObject.SelectionStart,
AssociatedObject.SelectionStart + AssociatedObject.SelectionLength - 1);
}
return false;
}
private void RefreshText(int position)
{
SetText(Provider.ToDisplayString());
AssociatedObject.SelectionStart = position;
}
private void SetText(string text)
{
AssociatedObject.Text = string.IsNullOrWhiteSpace(text) ? string.Empty : text;
}
private int GetNextCharacterPosition(int startPosition)
{
var position = Provider.FindEditPositionFrom(startPosition, true);
if (position == -1)
return startPosition;
return position;
}
#region DependencyProperties
public static readonly DependencyProperty InputMaskProperty =
DependencyProperty.Register("InputMask", typeof (string), typeof (TextBoxInputMaskBehavior), null);
public string InputMask
{
get { return (string) GetValue(InputMaskProperty); }
set { SetValue(InputMaskProperty, value); }
}
public static readonly DependencyProperty PromptCharProperty =
DependencyProperty.Register("PromptChar", typeof (char), typeof (TextBoxInputMaskBehavior),
new PropertyMetadata('_'));
public char PromptChar
{
get { return (char) GetValue(PromptCharProperty); }
set { SetValue(PromptCharProperty, value); }
}
#endregion
}
和xaml
<TextBox VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Grid.Row="6" Grid.Column="1" FontSize="22" >
<i:Interaction.Behaviors>
<behaviors:TextBoxInputMaskBehavior InputMask="{StaticResource InputMaskIp}" PromptChar="_" />
</i:Interaction.Behaviors>
</TextBox>