wpf 如何创建像 windows IP 地址字段一样的 Masked TextBox
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35324285/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to create Masked TextBox like windows IP address fields
提问by Pingman98
回答by Szabolcs Dézsi
I used a UserControlfor this. I'm sure it's not the most perfect one ever, but maybe it is a good starting point for you.
我UserControl为此使用了一个。我敢肯定它不是有史以来最完美的,但也许它对您来说是一个很好的起点。
UserControlXAML:
UserControlXAML:
<UserControl x:Class="IPTextBoxDemo.IPTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="300"
FocusManager.IsFocusScope="True">
<UserControl.Resources>
<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBoxBase">
<Border BorderThickness="{TemplateBinding Border.BorderThickness}"
BorderBrush="{TemplateBinding Border.BorderBrush}"
Background="{TemplateBinding Panel.Background}"
Name="border"
SnapsToDevicePixels="True">
<ScrollViewer HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
Name="PART_ContentHost"
Focusable="False" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" TargetName="border" Value="0.56" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox x:Name="FirstSegment" Grid.Column="0" TextAlignment="Center" MaxLength="3" BorderThickness="1,1,0,1" VerticalContentAlignment="Center"
TextChanged="TextBoxBase_OnTextChanged" PreviewKeyDown="UIElement_OnPreviewKeyDown" DataObject.Pasting="DataObject_OnPasting" />
<TextBox Grid.Column="1" Text="." TextAlignment="Center" IsReadOnly="True" Focusable="False" BorderThickness="0,1,0,1" VerticalContentAlignment="Center"
IsReadOnlyCaretVisible="False"/>
<TextBox x:Name="SecondSegment" Grid.Column="2" TextAlignment="Center" MaxLength="3" BorderThickness="0,1,0,1" VerticalContentAlignment="Center"
TextChanged="TextBoxBase_OnTextChanged" PreviewKeyDown="UIElement_OnPreviewKeyDown" DataObject.Pasting="DataObject_OnPasting" />
<TextBox Grid.Column="3" Text="." TextAlignment="Center" IsReadOnly="True" Focusable="False" BorderThickness="0,1,0,1" VerticalContentAlignment="Center"
IsReadOnlyCaretVisible="False"/>
<TextBox x:Name="ThirdSegment" Grid.Column="4" TextAlignment="Center" MaxLength="3" BorderThickness="0,1,0,1" VerticalContentAlignment="Center"
TextChanged="TextBoxBase_OnTextChanged" PreviewKeyDown="UIElement_OnPreviewKeyDown" DataObject.Pasting="DataObject_OnPasting" />
<TextBox Grid.Column="5" Text="." TextAlignment="Center" IsReadOnly="True" Focusable="False" BorderThickness="0,1,0,1" VerticalContentAlignment="Center"
IsReadOnlyCaretVisible="False" />
<TextBox x:Name="LastSegment" Grid.Column="6" TextAlignment="Center" MaxLength="3" BorderThickness="0,1,1,1" VerticalContentAlignment="Center"
TextChanged="TextBoxBase_OnTextChanged" PreviewKeyDown="UIElement_OnPreviewKeyDown" DataObject.Pasting="DataObject_OnPasting" />
</Grid>
</UserControl>
The XAML consists of a main Gridwith seven columns, four for for TextBoxes where you will enter your number and three for the fixed dots in the IP address.
XAML 由一个Grid包含七列的主列组成,其中四列用于TextBox输入您的数字,三列用于 IP 地址中的固定点。
The UserControlcode-behind:
在UserControl后台代码:
namespace IPTextBoxDemo
{
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
/// <summary>
/// Interaction logic for IPTextBox.xaml
/// </summary>
public partial class IPTextBox : UserControl
{
private static readonly List<Key> DigitKeys = new List<Key> { Key.D0, Key.D1, Key.D2, Key.D3, Key.D4, Key.D5, Key.D6, Key.D7, Key.D8, Key.D9 };
private static readonly List<Key> MoveForwardKeys = new List<Key> { Key.Right };
private static readonly List<Key> MoveBackwardKeys = new List<Key> { Key.Left };
private static readonly List<Key> OtherAllowedKeys = new List<Key> { Key.Tab, Key.Delete };
private readonly List<TextBox> _segments = new List<TextBox>();
private bool _suppressAddressUpdate = false;
public IPTextBox()
{
InitializeComponent();
_segments.Add(FirstSegment);
_segments.Add(SecondSegment);
_segments.Add(ThirdSegment);
_segments.Add(LastSegment);
}
public static readonly DependencyProperty AddressProperty = DependencyProperty.Register(
"Address", typeof (string), typeof (IPTextBox), new FrameworkPropertyMetadata(default(string), AddressChanged)
{
BindsTwoWayByDefault = true
});
private static void AddressChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var ipTextBox = dependencyObject as IPTextBox;
var text = e.NewValue as string;
if (text != null && ipTextBox != null)
{
ipTextBox._suppressAddressUpdate = true;
var i = 0;
foreach (var segment in text.Split('.'))
{
ipTextBox._segments[i].Text = segment;
i++;
}
ipTextBox._suppressAddressUpdate = false;
}
}
public string Address
{
get { return (string) GetValue(AddressProperty); }
set { SetValue(AddressProperty, value); }
}
private void UIElement_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (DigitKeys.Contains(e.Key))
{
e.Handled = ShouldCancelDigitKeyPress();
HandleDigitPress();
}
else if(MoveBackwardKeys.Contains(e.Key))
{
e.Handled = ShouldCancelBackwardKeyPress();
HandleBackwardKeyPress();
}
else if (MoveForwardKeys.Contains(e.Key))
{
e.Handled = ShouldCancelForwardKeyPress();
HandleForwardKeyPress();
} else if (e.Key == Key.Back)
{
HandleBackspaceKeyPress();
}
else if (e.Key == Key.OemPeriod)
{
e.Handled = true;
HandlePeriodKeyPress();
}
else
{
e.Handled = !AreOtherAllowedKeysPressed(e);
}
}
private bool AreOtherAllowedKeysPressed(KeyEventArgs e)
{
return e.Key == Key.C && ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) != 0) ||
e.Key == Key.V && ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) != 0) ||
e.Key == Key.A && ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) != 0) ||
e.Key == Key.X && ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) != 0) ||
OtherAllowedKeys.Contains(e.Key);
}
private void HandleDigitPress()
{
var currentTextBox = FocusManager.GetFocusedElement(this) as TextBox;
if (currentTextBox != null && currentTextBox.Text.Length == 3 &&
currentTextBox.CaretIndex == 3 && currentTextBox.SelectedText.Length == 0)
{
MoveFocusToNextSegment(currentTextBox);
}
}
private bool ShouldCancelDigitKeyPress()
{
var currentTextBox = FocusManager.GetFocusedElement(this) as TextBox;
return currentTextBox != null &&
currentTextBox.Text.Length == 3 &&
currentTextBox.CaretIndex == 3 &&
currentTextBox.SelectedText.Length == 0;
}
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
if (!_suppressAddressUpdate)
{
Address = string.Format("{0}.{1}.{2}.{3}", FirstSegment.Text, SecondSegment.Text, ThirdSegment.Text, LastSegment.Text);
}
var currentTextBox = FocusManager.GetFocusedElement(this) as TextBox;
if (currentTextBox != null && currentTextBox.Text.Length == 3 && currentTextBox.CaretIndex == 3)
{
MoveFocusToNextSegment(currentTextBox);
}
}
private bool ShouldCancelBackwardKeyPress()
{
var currentTextBox = FocusManager.GetFocusedElement(this) as TextBox;
return currentTextBox != null && currentTextBox.CaretIndex == 0;
}
private void HandleBackspaceKeyPress()
{
var currentTextBox = FocusManager.GetFocusedElement(this) as TextBox;
if (currentTextBox != null && currentTextBox.CaretIndex == 0 && currentTextBox.SelectedText.Length == 0)
{
MoveFocusToPreviousSegment(currentTextBox);
}
}
private void HandleBackwardKeyPress()
{
var currentTextBox = FocusManager.GetFocusedElement(this) as TextBox;
if (currentTextBox != null && currentTextBox.CaretIndex == 0)
{
MoveFocusToPreviousSegment(currentTextBox);
}
}
private bool ShouldCancelForwardKeyPress()
{
var currentTextBox = FocusManager.GetFocusedElement(this) as TextBox;
return currentTextBox != null && currentTextBox.CaretIndex == 3;
}
private void HandleForwardKeyPress()
{
var currentTextBox = FocusManager.GetFocusedElement(this) as TextBox;
if (currentTextBox != null && currentTextBox.CaretIndex == currentTextBox.Text.Length)
{
MoveFocusToNextSegment(currentTextBox);
}
}
private void HandlePeriodKeyPress()
{
var currentTextBox = FocusManager.GetFocusedElement(this) as TextBox;
if (currentTextBox != null && currentTextBox.Text.Length > 0 && currentTextBox.CaretIndex == currentTextBox.Text.Length)
{
MoveFocusToNextSegment(currentTextBox);
}
}
private void MoveFocusToPreviousSegment(TextBox currentTextBox)
{
if (!ReferenceEquals(currentTextBox, FirstSegment))
{
var previousSegmentIndex = _segments.FindIndex(box => ReferenceEquals(box, currentTextBox)) - 1;
currentTextBox.SelectionLength = 0;
currentTextBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
_segments[previousSegmentIndex].CaretIndex = _segments[previousSegmentIndex].Text.Length;
}
}
private void MoveFocusToNextSegment(TextBox currentTextBox)
{
if (!ReferenceEquals(currentTextBox, LastSegment))
{
currentTextBox.SelectionLength = 0;
currentTextBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
private void DataObject_OnPasting(object sender, DataObjectPastingEventArgs e)
{
var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
if (!isText)
{
e.CancelCommand();
return;
}
var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string;
int num;
if (!int.TryParse(text, out num))
{
e.CancelCommand();
}
}
}
}
The code-behind is mostly handling different events:
代码隐藏主要处理不同的事件:
- What should happen if the user presses backspace,
- What should happen if the user presses the left or right arrows,
- What should happen if the user presses the dot,
- What should happen if the user is trying to paste in something.
- 如果用户按下退格键会发生什么,
- 如果用户按下向左或向右箭头会发生什么,
- 如果用户按下点会发生什么,
- 如果用户尝试粘贴某些内容会发生什么。
And so on and so on.
等等等等。
The usage is the following:
用法如下:
<ipTextBoxDemo:IPTextBox Width="150" Address="{Binding AddressInVM}"></ipTextBoxDemo:IPTextBox>
As you can see the UserControlexposes a stringproperty called Address(binding two-way by default).
如您所见,UserControl公开了一个string名为Address(默认为双向绑定)的属性。
If the source of the binding changes (AddressInVmhere, so change coming from the VM) then the UserControlwill split the text by the dots and will put the resulting values in the four TextBoxes.
如果绑定的源发生变化(AddressInVm这里是来自 VM 的变化),那么UserControl将通过点分割文本并将结果值放在四个TextBoxes 中。
If the target of the binding changes (the user types in something in the box), the four TextBoxes' Textproperties will be concatenated.
如果绑定的目标发生变化(用户在框中键入某些内容),则将连接四个TextBoxes 的Text属性。
To make this proper you have to implement validation as well, because that is missing from this UserControl, but I leave that to you as an exercise ;)
为了使这正确,您还必须实施验证,因为这里缺少这一点UserControl,但我将其留给您作为练习;)
The result:
结果:


