如何创建类似 Windows IP 地址字段的 Masked TextBox [英] How to create Masked TextBox like windows IP address fields

查看:15
本文介绍了如何创建类似 Windows IP 地址字段的 Masked TextBox的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何在WPF中创建一个类似windows IP地址字段的numeric Masked TextBox,点击.(DOT)后跳转到下一个区域按钮

How to create a numeric Masked TextBox in WPF that like windows IP Address fields, jump to next area after hit .(DOT) button

推荐答案

为此我使用了 UserControl.我敢肯定这不是有史以来最完美的一次,但也许对您来说是一个很好的起点.

I used a UserControl for this. I'm sure it's not the most perfect one ever, but maybe it is a good starting point for you.

UserControl XAML:

<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>

XAML 由一个主要的 Grid 组成,有七列,四列用于 TextBoxes,您将在其中输入您的数字,三列用于 IP 地址中的固定点.

The XAML consists of a main Grid with seven columns, four for for TextBoxes where you will enter your number and three for the fixed dots in the IP address.

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:

  • 如果用户按下退格键会发生什么,
  • 如果用户按下向左或向右箭头会发生什么,
  • 如果用户按下点会发生什么,
  • 如果用户尝试粘贴某些内容会发生什么.

依此类推.

用法如下:

<ipTextBoxDemo:IPTextBox Width="150" Address="{Binding AddressInVM}"></ipTextBoxDemo:IPTextBox>

如您所见,UserControl 公开了一个名为 Addressstring 属性(默认为双向绑定).

As you can see the UserControl exposes a string property called Address (binding two-way by default).

如果绑定的源发生变化(这里是AddressInVm,所以变化来自VM),那么UserControl 将通过点分割文本并将结果放在四个 TextBox 中的值.

If the source of the binding changes (AddressInVm here, so change coming from the VM) then the UserControl will split the text by the dots and will put the resulting values in the four TextBoxes.

如果绑定的目标发生变化(用户在框中输入内容),四个 TextBoxes 的 Text 属性将被连接起来.

If the target of the binding changes (the user types in something in the box), the four TextBoxes' Text properties will be concatenated.

为了使其正确,您还必须实现验证,因为此 UserControl 中缺少这一点,但我将其留给您作为练习 ;)

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 ;)

结果:

这篇关于如何创建类似 Windows IP 地址字段的 Masked TextBox的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆