Files
HandyControl/src/Shared/HandyControl_Shared/Controls/Input/PinBox.cs
T

390 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using HandyControl.Data;
using HandyControl.Tools;
using HandyControl.Tools.Extension;
namespace HandyControl.Controls
{
[TemplatePart(Name = ElementPanel, Type = typeof(Panel))]
public class PinBox : Control
{
private const string ElementPanel = "PART_Panel";
private static readonly object MinLength = 4;
private Panel _panel;
private int _inputIndex;
private bool _changed;
private bool _isInternalAction;
private List<SecureString> _passwordList;
private RoutedEventHandler _passwordBoxsGotFocusEventHandler;
private RoutedEventHandler _passwordBoxsPasswordChangedEventHandler;
public PinBox()
{
Loaded += PinBox_Loaded;
Unloaded += PinBox_Unloaded;
}
private void PinBox_Unloaded(object sender, RoutedEventArgs e)
{
RemoveHandler(System.Windows.Controls.PasswordBox.PasswordChangedEvent, _passwordBoxsPasswordChangedEventHandler);
RemoveHandler(GotFocusEvent, _passwordBoxsGotFocusEventHandler);
Loaded -= PinBox_Loaded;
Unloaded -= PinBox_Unloaded;
}
private void PinBox_Loaded(object sender, RoutedEventArgs e)
{
_passwordBoxsPasswordChangedEventHandler = PasswordBoxsPasswordChanged;
AddHandler(System.Windows.Controls.PasswordBox.PasswordChangedEvent, _passwordBoxsPasswordChangedEventHandler);
_passwordBoxsGotFocusEventHandler = PasswordBoxsGotFocus;
AddHandler(GotFocusEvent, _passwordBoxsGotFocusEventHandler);
}
private void PasswordBoxsPasswordChanged(object sender, RoutedEventArgs e)
{
if (_isInternalAction) return;
if (e.OriginalSource is System.Windows.Controls.PasswordBox passwordBox)
{
if (passwordBox.Password.Length > 0)
{
if (++_inputIndex >= Length)
{
_inputIndex = Length - 1;
if (_panel.Children.OfType<System.Windows.Controls.PasswordBox>()
.All(item => item.Password.Any()))
{
FocusManager.SetFocusedElement(this, null);
Keyboard.ClearFocus();
RaiseEvent(new RoutedEventArgs(CompletedEvent, this));
}
return;
}
}
else
{
if (--_inputIndex < 0)
{
_inputIndex = 0;
return;
}
}
_changed = true;
FocusManager.SetFocusedElement(this, _panel.Children[_inputIndex]);
}
}
private void PasswordBoxsGotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is System.Windows.Controls.PasswordBox passwordBox)
{
_inputIndex = _panel.Children.IndexOf(passwordBox);
passwordBox.SelectAll();
}
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyUp(e);
if (e.Key == Key.Left)
{
if (--_inputIndex < 0)
{
_inputIndex = 0;
return;
}
var passwordBox = _panel.Children[_inputIndex] as System.Windows.Controls.PasswordBox;
passwordBox?.SelectAll();
FocusManager.SetFocusedElement(this, passwordBox);
}
else if (e.Key == Key.Right)
{
if (++_inputIndex >= Length)
{
_inputIndex = Length - 1;
return;
}
var passwordBox = _panel.Children[_inputIndex] as System.Windows.Controls.PasswordBox;
passwordBox?.SelectAll();
FocusManager.SetFocusedElement(this, passwordBox);
}
}
protected override void OnPreviewKeyUp(KeyEventArgs e)
{
base.OnPreviewKeyUp(e);
if (_changed)
{
_changed = false;
return;
}
if (e.Key == Key.Delete || e.Key == Key.Back)
{
if (_panel.Children[_inputIndex] is System.Windows.Controls.PasswordBox passwordBox)
{
_isInternalAction = true;
passwordBox.Clear();
_isInternalAction = false;
}
if (--_inputIndex < 0)
{
_inputIndex = 0;
return;
}
FocusManager.SetFocusedElement(this, _panel.Children[_inputIndex]);
}
}
public string Password
{
get
{
return string.Join("", _panel.Children.OfType<System.Windows.Controls.PasswordBox>().Select(item => item.Password));
}
set
{
if (_panel == null)
{
_passwordList = new List<SecureString>();
value ??= string.Empty;
foreach (var item in value)
{
var secureString = new SecureString();
secureString.AppendChar(item);
_passwordList.Add(secureString);
}
return;
}
var length = Length;
if (string.IsNullOrEmpty(value) || value.Length != length || value.Length != _panel.Children.Count)
{
_panel.Children.OfType<System.Windows.Controls.PasswordBox>().Do(item => item.Clear());
}
else
{
_panel.Children.OfType<System.Windows.Controls.PasswordBox>().DoWithIndex((item, index) => item.Password = value[index].ToString());
}
}
}
/// <summary>
/// 掩码字符
/// </summary>
public static readonly DependencyProperty PasswordCharProperty =
System.Windows.Controls.PasswordBox.PasswordCharProperty.AddOwner(typeof(PinBox),
new FrameworkPropertyMetadata('●'));
public char PasswordChar
{
get => (char) GetValue(PasswordCharProperty);
set => SetValue(PasswordCharProperty, value);
}
public static readonly DependencyProperty LengthProperty = DependencyProperty.Register(
"Length", typeof(int), typeof(PinBox), new PropertyMetadata(MinLength, OnLengthChanged, CoerceLength), ValidateHelper.IsInRangeOfPosInt);
private static void OnLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (PinBox) d;
ctl.UpdateItems();
}
private static object CoerceLength(DependencyObject d, object basevalue) => (int) basevalue < 4 ? MinLength : basevalue;
public int Length
{
get => (int) GetValue(LengthProperty);
set => SetValue(LengthProperty, value);
}
public static readonly DependencyProperty ItemMarginProperty = DependencyProperty.Register(
"ItemMargin", typeof(Thickness), typeof(PinBox), new PropertyMetadata(default(Thickness)));
public Thickness ItemMargin
{
get => (Thickness) GetValue(ItemMarginProperty);
set => SetValue(ItemMarginProperty, value);
}
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(
"ItemWidth", typeof(double), typeof(PinBox), new PropertyMetadata(ValueBoxes.Double0Box));
public double ItemWidth
{
get => (double) GetValue(ItemWidthProperty);
set => SetValue(ItemWidthProperty, value);
}
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(
"ItemHeight", typeof(double), typeof(PinBox), new PropertyMetadata(ValueBoxes.Double0Box));
public double ItemHeight
{
get => (double) GetValue(ItemHeightProperty);
set => SetValue(ItemHeightProperty, value);
}
public static readonly DependencyProperty SelectionBrushProperty =
TextBoxBase.SelectionBrushProperty.AddOwner(typeof(PinBox));
public Brush SelectionBrush
{
get => (Brush) GetValue(SelectionBrushProperty);
set => SetValue(SelectionBrushProperty, value);
}
#if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472)
public static readonly DependencyProperty SelectionTextBrushProperty =
TextBoxBase.SelectionTextBrushProperty.AddOwner(typeof(PinBox));
public Brush SelectionTextBrush
{
get => (Brush) GetValue(SelectionTextBrushProperty);
set => SetValue(SelectionTextBrushProperty, value);
}
#endif
public static readonly DependencyProperty SelectionOpacityProperty =
TextBoxBase.SelectionOpacityProperty.AddOwner(typeof(PinBox));
public double SelectionOpacity
{
get => (double) GetValue(SelectionOpacityProperty);
set => SetValue(SelectionOpacityProperty, value);
}
public static readonly DependencyProperty CaretBrushProperty =
TextBoxBase.CaretBrushProperty.AddOwner(typeof(PinBox));
public Brush CaretBrush
{
get => (Brush) GetValue(CaretBrushProperty);
set => SetValue(CaretBrushProperty, value);
}
public static readonly RoutedEvent CompletedEvent =
EventManager.RegisterRoutedEvent("Completed", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(PinBox));
public event RoutedEventHandler Completed
{
add => AddHandler(CompletedEvent, value);
remove => RemoveHandler(CompletedEvent, value);
}
private void UpdateItems()
{
if (_panel == null) return;
_panel.Children.Clear();
var length = Length;
for (var i = 0; i < length; i++)
{
_panel.Children.Add(CreatePasswordBox());
}
}
private System.Windows.Controls.PasswordBox CreatePasswordBox()
{
var passwordBox = new System.Windows.Controls.PasswordBox
{
MaxLength = 1,
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Margin = ItemMargin,
Width = ItemWidth,
Height = ItemHeight,
Padding = default,
PasswordChar = PasswordChar,
Foreground = Foreground
};
passwordBox.SetBinding(SelectionBrushProperty, new Binding(SelectionBrushProperty.Name) { Source = this });
#if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472)
passwordBox.SetBinding(SelectionTextBrushProperty, new Binding(SelectionTextBrushProperty.Name) { Source = this });
#endif
passwordBox.SetBinding(SelectionOpacityProperty, new Binding(SelectionOpacityProperty.Name) { Source = this });
passwordBox.SetBinding(CaretBrushProperty, new Binding(CaretBrushProperty.Name) { Source = this });
return passwordBox;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_panel = GetTemplateChild(ElementPanel) as Panel;
if (_panel != null)
{
UpdateItems();
var length = Length;
if (_passwordList != null && _passwordList.Count == length && _panel.Children.Count == length)
{
for (var i = 0; i < length; i++)
{
var password = _passwordList[i];
if (password.Length > 0)
{
var valuePtr = IntPtr.Zero;
try
{
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(password);
if (_panel.Children[i] is System.Windows.Controls.PasswordBox passwordBox)
{
passwordBox.Password = Marshal.PtrToStringUni(valuePtr) ?? throw new InvalidOperationException();
}
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
password.Clear();
}
}
}
_passwordList.Clear();
}
}
}
}
}