UWP 自定義密碼框控制元件

貓叔Vincent發表於2020-07-24

1. 概述

微軟官方有提供自己的密碼控制元件,但是控制元件預設的行為是輸入密碼,會立即顯示掩碼,比如 *。如果像檢視真實的文字,需要按檢視按鈕。

而我現在自定義的密碼控制元件是先顯示你輸入的字元2s,然後再顯示成掩碼。當然這種場景並不一定適用於密碼,也可以用在Pin碼。

 

a. 微軟官方的密碼框

 

 

 

b. 自定義的效果

 

 

 

2. 密碼框控制元件實現

要想實現自定義的密碼框,我們當然要繼承微軟的TextBox控制元件,然後加入我們自己需要的東西。

a. 這裡新增了一個加密型別的密碼 Password 欄位,真實密碼文字 RealPassword 欄位,一個Timer定時器來控制顯示掩碼

internal class CustomPasswordBox : TextBox
    {
        #region Member Variables
        public static readonly DependencyProperty PasswordProperty =
            DependencyProperty.Register("Password", typeof(SecureString), typeof(CustomPasswordBox), new PropertyMetadata(new SecureString()));

        public static readonly DependencyProperty RealPasswordProperty =
            DependencyProperty.Register("RealPassword", typeof(string), typeof(CustomPasswordBox), new PropertyMetadata(string.Empty));

        private DispatcherTimer maskTimer;
        #endregion
    }

 

b. 公開控制元件屬性欄位

public SecureString Password
        {
            get
            {
                return (SecureString)GetValue(PasswordProperty);
            }

            set
            {
                SetValue(PasswordProperty, value);
            }
        }


        public string RealPassword
        {
            get
            {
                return (string)GetValue(RealPasswordProperty);
            }

            set
            {
                SetValue(RealPasswordProperty, value);
            }
        }

 

 

c. 然後再建構函式裡新增一個需要相應的事件,還有初始化Timer

public CustomPasswordBox()
        {
            PreviewKeyDown += CustomPasswordBox_PreviewKeyDown;
            CharacterReceived += CustomPasswordBox_CharacterReceived;
            BeforeTextChanging += CustomPasswordBox_BeforeTextChanging;
            SelectionChanged += CustomPasswordBox_SelectionChanged;
            ContextMenuOpening += CustomPasswordBox_ContextMenuOpening;
            TextCompositionStarted += CustomPasswordBox_TextCompositionStarted;

            maskTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 2) };
            maskTimer.Tick += MaskTimer_Tick;
        }


在PreviewKeyDown、CharacterReceived、BeforeTextChanging這三個事件裡,主要處理字元的輸入刪除等邏輯。
在SelectionChanged事件裡,我們主要處理不讓使用者選中文字
在ContextMenuOpening,主要是遮蔽右鍵選單,比如複製剪下貼上
在TextCompositionStarted主要遮蔽中文等組合輸入法

 

另外,在我的場景裡面,我們只讓使用者輸入數字,所以加入了數字驗證,以及在Xbox上開啟了InputScope=NumbericPin模式。

 

d. 新增事件響應程式碼

private void CustomPasswordBox_PreviewKeyDown(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
        {
            switch (e.OriginalKey)
            {
                case VirtualKey.Back:
                case VirtualKey.Delete:
                    if (SelectionLength > 0)
                    {
                        RemoveFromSecureString(SelectionStart, SelectionLength);
                    }
                    else if (e.OriginalKey == VirtualKey.Delete && SelectionStart < Text.Length)
                    {
                        RemoveFromSecureString(SelectionStart, 1);
                    }
                    else if (e.OriginalKey == VirtualKey.Back && SelectionStart > 0)
                    {
                        int caretIndex = SelectionStart;
                        if (SelectionStart > 0 && SelectionStart < Text.Length)
                            caretIndex = caretIndex - 1;
                        RemoveFromSecureString(SelectionStart - 1, 1);
                        //SelectionStart = caretIndex;
                    }

                    e.Handled = true;
                    break;

                default:
                    //e.Handled = true;
                    break;
            }
        }

        private void CustomPasswordBox_CharacterReceived(UIElement sender, Windows.UI.Xaml.Input.CharacterReceivedRoutedEventArgs args)
        {
            if (!char.IsDigit(args.Character))
                return;

            AddToSecureString(args.Character.ToString());
            args.Handled = true;
        }

        private void CustomPasswordBox_BeforeTextChanging(TextBox sender, TextBoxBeforeTextChangingEventArgs args)
        {
            args.Cancel = args.NewText.Any(c => !char.IsDigit(c) && !char.ToString(c).ToString().Equals(""));
            //if (args.NewText.Replace("●", "") != "")
            //    AddToSecureString(args.NewText.Replace("●", ""));
        }

        private void CustomPasswordBox_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            e.Handled = true;
        }

        private void CustomPasswordBox_SelectionChanged(object sender, RoutedEventArgs e)
        {
            SelectionStart = Text.Length;
            SelectionLength = 0;
        }

        private void CustomPasswordBox_TextCompositionStarted(TextBox sender, TextCompositionStartedEventArgs args)
        {
            return;
        }

        private void MaskTimer_Tick(object sender, object e)
        {
            MaskAllDisplayText();
        }

 

e. 實現Timer裡面的2s後將字串變成掩碼

2s後看TextBox裡面的字元數,然後設定對應長度的掩碼

        private void MaskAllDisplayText()
        {
            maskTimer.Stop();
            int caretIndex = SelectionStart;
            Text = new string('', Text.Length);
            SelectionStart = caretIndex;
        }

 

 

f. 新增字元和刪除字元

 private void AddToSecureString(string text)
        {
            if (SelectionLength > 0)
            {
                RemoveFromSecureString(SelectionStart, SelectionLength);
            }

            if (Password.Length >= 4 || RealPassword.Length >= 4)
                return;
            foreach (char c in text)
            {
                System.Diagnostics.Debug.WriteLine(text);
                int caretIndex = SelectionStart;
                if (caretIndex - 1 < 0)
                    Password.InsertAt(0, c);
                else
                    Password.InsertAt(caretIndex - 1, c);
                RealPassword += c.ToString();
                //MaskAllDisplayText();
                if (caretIndex == Text.Length)
                {
                    maskTimer.Stop();
                    maskTimer.Start();
                    //Text = Text.Insert(caretIndex++, c.ToString());
                }
                else
                {
                    //Text = Text.Insert(caretIndex++, "●");
                }
                SelectionStart = Text.Length;
            }
        }

        private void RemoveFromSecureString(int startIndex, int trimLength)
        {
            int caretIndex = SelectionStart;
            for (int i = 0; i < trimLength; ++i)
            {
                Password.RemoveAt(startIndex);
                RealPassword = RealPassword.Remove(startIndex, 1);
            }

            Text = Text.Remove(startIndex, trimLength);
            SelectionStart = caretIndex;
        }

 

 

 

3. 控制元件使用方法

<local:CustomPasswordBox 
            InputScope="NumericPin"
            MaxLength="4" />

 

一個例子

<Grid>
        <local:CustomPasswordBox 
            x:Name="PSW" 
            InputScope="NumericPin" 
            Padding="200 0 0 0" 
            Style="{StaticResource MyTextBox}" 
            Height="Auto" 
            MaxLength="4" 
            CharacterSpacing="1000" 
            FontSize="100" 
            VerticalAlignment="Top"/>
        <TextBlock 
            x:Name="realPSW" 
            FontSize="88" 
            Margin="0 200"
            HorizontalAlignment="Center" 
            Text="{Binding ElementName=PSW, Path=RealPassword, Mode=OneWay}"/>
    </Grid>

 

你也可以看我在Youtube上的視訊演示

https://youtu.be/eHhDG3dW1bI

歡迎點選右下角的訂閱按鈕,還有紅色小鈴鐺。謝謝

 

相關文章