diff --git a/src/Shared/HandyControlDemo_Shared/UserControl/Controls/PinBoxDemoCtl.xaml b/src/Shared/HandyControlDemo_Shared/UserControl/Controls/PinBoxDemoCtl.xaml index 77c4a6f4..d5175cbc 100644 --- a/src/Shared/HandyControlDemo_Shared/UserControl/Controls/PinBoxDemoCtl.xaml +++ b/src/Shared/HandyControlDemo_Shared/UserControl/Controls/PinBoxDemoCtl.xaml @@ -1,7 +1,9 @@  - + + + diff --git a/src/Shared/HandyControl_Shared/Controls/Input/PinBox.cs b/src/Shared/HandyControl_Shared/Controls/Input/PinBox.cs index 86634976..addf3612 100644 --- a/src/Shared/HandyControl_Shared/Controls/Input/PinBox.cs +++ b/src/Shared/HandyControl_Shared/Controls/Input/PinBox.cs @@ -1,9 +1,226 @@ -using System.Windows.Controls; +using System; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using HandyControl.Data; +using HandyControl.Tools; 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 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 (e.OriginalSource is System.Windows.Controls.PasswordBox passwordBox) + { + if (passwordBox.Password.Length > 0) + { + if (++_inputIndex >= Length) + { + _inputIndex = Length - 1; + + if (_panel.Children.OfType() + .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); + } + } + + protected override void OnPreviewKeyDown(KeyEventArgs e) + { + base.OnPreviewKeyUp(e); + + if (e.Key == Key.Left) + { + if (--_inputIndex < 0) + { + _inputIndex = 0; + return; + } + + FocusManager.SetFocusedElement(this, _panel.Children[_inputIndex]); + } + else if (e.Key == Key.Right) + { + if (++_inputIndex >= Length) + { + _inputIndex = Length - 1; + return; + } + + FocusManager.SetFocusedElement(this, _panel.Children[_inputIndex]); + } + } + + protected override void OnPreviewKeyUp(KeyEventArgs e) + { + base.OnPreviewKeyUp(e); + + if (_changed) + { + _changed = false; + return; + } + + if (e.Key == Key.Delete || e.Key == Key.Back) + { + if (--_inputIndex < 0) + { + _inputIndex = 0; + return; + } + + FocusManager.SetFocusedElement(this, _panel.Children[_inputIndex]); + } + } + + public static readonly DependencyProperty LengthProperty = DependencyProperty.Register( + "Length", typeof(int), typeof(PinBox), new PropertyMetadata(MinLength, null, CoerceLength), ValidateHelper.IsInRangeOfPosInt); + + 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 RoutedEvent CompletedEvent = + EventManager.RegisterRoutedEvent("Completed", RoutingStrategy.Bubble, + typeof(EventHandler), typeof(PinBox)); + + public event EventHandler Completed + { + add => AddHandler(CompletedEvent, value); + remove => RemoveHandler(CompletedEvent, value); + } + + private void UpdateItems() + { + _panel.Children.Clear(); + var length = Length; + + for (var i = 0; i < length; i++) + { + _panel.Children.Add(CreatePasswordBox()); + } + } + + private System.Windows.Controls.PasswordBox CreatePasswordBox() + { + return new System.Windows.Controls.PasswordBox + { + MaxLength = 1, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Margin = ItemMargin, + Width = ItemWidth, + Height = ItemHeight, + Padding = new Thickness() + }; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _panel = GetTemplateChild(ElementPanel) as Panel; + if (_panel != null) + { + UpdateItems(); + } + } } } diff --git a/src/Shared/HandyControl_Shared/Themes/Styles/Base/PinBoxBaseStyle.xaml b/src/Shared/HandyControl_Shared/Themes/Styles/Base/PinBoxBaseStyle.xaml index 98d80b3f..a81214d7 100644 --- a/src/Shared/HandyControl_Shared/Themes/Styles/Base/PinBoxBaseStyle.xaml +++ b/src/Shared/HandyControl_Shared/Themes/Styles/Base/PinBoxBaseStyle.xaml @@ -2,8 +2,21 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:HandyControl.Controls"> - \ No newline at end of file