PinBox.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Runtime.InteropServices;
  5. using System.Security;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Controls.Primitives;
  9. using System.Windows.Data;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using HandyControl.Data;
  13. using HandyControl.Tools;
  14. using HandyControl.Tools.Extension;
  15. namespace HandyControl.Controls;
  16. [TemplatePart(Name = ElementPanel, Type = typeof(Panel))]
  17. public class PinBox : Control
  18. {
  19. private const string ElementPanel = "PART_Panel";
  20. private static readonly object MinLength = 4;
  21. private Panel _panel;
  22. private int _inputIndex;
  23. private bool _changed;
  24. private bool _isInternalAction;
  25. private List<SecureString> _passwordList;
  26. private RoutedEventHandler _passwordBoxsGotFocusEventHandler;
  27. private RoutedEventHandler _passwordBoxsPasswordChangedEventHandler;
  28. public PinBox()
  29. {
  30. Loaded += PinBox_Loaded;
  31. Unloaded += PinBox_Unloaded;
  32. }
  33. private void PinBox_Unloaded(object sender, RoutedEventArgs e)
  34. {
  35. RemoveHandler(System.Windows.Controls.PasswordBox.PasswordChangedEvent, _passwordBoxsPasswordChangedEventHandler);
  36. RemoveHandler(GotFocusEvent, _passwordBoxsGotFocusEventHandler);
  37. Loaded -= PinBox_Loaded;
  38. Unloaded -= PinBox_Unloaded;
  39. }
  40. private void PinBox_Loaded(object sender, RoutedEventArgs e)
  41. {
  42. _passwordBoxsPasswordChangedEventHandler = PasswordBoxsPasswordChanged;
  43. AddHandler(System.Windows.Controls.PasswordBox.PasswordChangedEvent, _passwordBoxsPasswordChangedEventHandler);
  44. _passwordBoxsGotFocusEventHandler = PasswordBoxsGotFocus;
  45. AddHandler(GotFocusEvent, _passwordBoxsGotFocusEventHandler);
  46. FocusPasswordBox();
  47. }
  48. private void FocusPasswordBox()
  49. {
  50. if (!IsFocused)
  51. {
  52. return;
  53. }
  54. if (_panel.Children.Count == 0)
  55. {
  56. return;
  57. }
  58. if (_panel.Children.OfType<System.Windows.Controls.PasswordBox>().Any(box => box.IsFocused))
  59. {
  60. return;
  61. }
  62. FocusManager.SetFocusedElement(this, _panel.Children[0]);
  63. }
  64. private void PasswordBoxsPasswordChanged(object sender, RoutedEventArgs e)
  65. {
  66. if (_isInternalAction) return;
  67. if (e.OriginalSource is System.Windows.Controls.PasswordBox passwordBox)
  68. {
  69. if (!IsSafeEnabled)
  70. {
  71. SetCurrentValue(UnsafePasswordProperty, Password);
  72. }
  73. if (passwordBox.Password.Length > 0)
  74. {
  75. if (++_inputIndex >= Length)
  76. {
  77. _inputIndex = Length - 1;
  78. if (_panel.Children.OfType<System.Windows.Controls.PasswordBox>()
  79. .All(item => item.Password.Any()))
  80. {
  81. FocusManager.SetFocusedElement(this, null);
  82. Keyboard.ClearFocus();
  83. RaiseEvent(new RoutedEventArgs(CompletedEvent, this));
  84. }
  85. return;
  86. }
  87. }
  88. else
  89. {
  90. if (--_inputIndex < 0)
  91. {
  92. _inputIndex = 0;
  93. return;
  94. }
  95. }
  96. _changed = true;
  97. FocusManager.SetFocusedElement(this, _panel.Children[_inputIndex]);
  98. }
  99. }
  100. private void PasswordBoxsGotFocus(object sender, RoutedEventArgs e)
  101. {
  102. if (e.OriginalSource is System.Windows.Controls.PasswordBox passwordBox)
  103. {
  104. _inputIndex = _panel.Children.IndexOf(passwordBox);
  105. passwordBox.SelectAll();
  106. }
  107. }
  108. protected override void OnPreviewKeyDown(KeyEventArgs e)
  109. {
  110. base.OnPreviewKeyUp(e);
  111. if (e.Key == Key.Left)
  112. {
  113. if (--_inputIndex < 0)
  114. {
  115. _inputIndex = 0;
  116. return;
  117. }
  118. var passwordBox = _panel.Children[_inputIndex] as System.Windows.Controls.PasswordBox;
  119. passwordBox?.SelectAll();
  120. FocusManager.SetFocusedElement(this, passwordBox);
  121. }
  122. else if (e.Key == Key.Right)
  123. {
  124. if (++_inputIndex >= Length)
  125. {
  126. _inputIndex = Length - 1;
  127. return;
  128. }
  129. var passwordBox = _panel.Children[_inputIndex] as System.Windows.Controls.PasswordBox;
  130. passwordBox?.SelectAll();
  131. FocusManager.SetFocusedElement(this, passwordBox);
  132. }
  133. }
  134. protected override void OnPreviewKeyUp(KeyEventArgs e)
  135. {
  136. base.OnPreviewKeyUp(e);
  137. if (_changed)
  138. {
  139. _changed = false;
  140. return;
  141. }
  142. if (e.Key == Key.Delete || e.Key == Key.Back)
  143. {
  144. if (_panel.Children[_inputIndex] is System.Windows.Controls.PasswordBox passwordBox)
  145. {
  146. _isInternalAction = true;
  147. passwordBox.Clear();
  148. _isInternalAction = false;
  149. }
  150. if (--_inputIndex < 0)
  151. {
  152. _inputIndex = 0;
  153. return;
  154. }
  155. FocusManager.SetFocusedElement(this, _panel.Children[_inputIndex]);
  156. }
  157. }
  158. public string Password
  159. {
  160. get
  161. {
  162. return _panel == null
  163. ? string.Empty
  164. : string.Join(string.Empty, _panel.Children.OfType<System.Windows.Controls.PasswordBox>().Select(item => item.Password));
  165. }
  166. set
  167. {
  168. if (_panel == null)
  169. {
  170. _passwordList = new List<SecureString>();
  171. value ??= string.Empty;
  172. foreach (var item in value)
  173. {
  174. var secureString = new SecureString();
  175. secureString.AppendChar(item);
  176. _passwordList.Add(secureString);
  177. }
  178. return;
  179. }
  180. _isInternalAction = true;
  181. if (string.IsNullOrEmpty(value))
  182. {
  183. _panel.Children.OfType<System.Windows.Controls.PasswordBox>().Do(item => item.Clear());
  184. }
  185. else
  186. {
  187. _panel.Children
  188. .OfType<System.Windows.Controls.PasswordBox>()
  189. .Take(Math.Min(Length, value.Length))
  190. .DoWithIndex((item, index) => item.Password = value[index].ToString());
  191. _panel.Children
  192. .OfType<System.Windows.Controls.PasswordBox>()
  193. .Skip(value.Length)
  194. .Take(Length - value.Length)
  195. .Do(item => item.Clear());
  196. }
  197. _isInternalAction = false;
  198. if (!IsSafeEnabled)
  199. {
  200. SetCurrentValue(UnsafePasswordProperty, Password);
  201. }
  202. }
  203. }
  204. /// <summary>
  205. /// 掩码字符
  206. /// </summary>
  207. public static readonly DependencyProperty PasswordCharProperty =
  208. System.Windows.Controls.PasswordBox.PasswordCharProperty.AddOwner(typeof(PinBox),
  209. new FrameworkPropertyMetadata('●'));
  210. public char PasswordChar
  211. {
  212. get => (char) GetValue(PasswordCharProperty);
  213. set => SetValue(PasswordCharProperty, value);
  214. }
  215. public static readonly DependencyProperty LengthProperty = DependencyProperty.Register(
  216. nameof(Length), typeof(int), typeof(PinBox), new PropertyMetadata(MinLength, OnLengthChanged, CoerceLength), ValidateHelper.IsInRangeOfPosInt);
  217. private static void OnLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  218. {
  219. var ctl = (PinBox) d;
  220. ctl.UpdateItems();
  221. }
  222. private static object CoerceLength(DependencyObject d, object basevalue) => (int) basevalue < 4 ? MinLength : basevalue;
  223. public int Length
  224. {
  225. get => (int) GetValue(LengthProperty);
  226. set => SetValue(LengthProperty, value);
  227. }
  228. public static readonly DependencyProperty ItemMarginProperty = DependencyProperty.Register(
  229. nameof(ItemMargin), typeof(Thickness), typeof(PinBox), new PropertyMetadata(default(Thickness)));
  230. public Thickness ItemMargin
  231. {
  232. get => (Thickness) GetValue(ItemMarginProperty);
  233. set => SetValue(ItemMarginProperty, value);
  234. }
  235. public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(
  236. nameof(ItemWidth), typeof(double), typeof(PinBox), new PropertyMetadata(ValueBoxes.Double0Box));
  237. public double ItemWidth
  238. {
  239. get => (double) GetValue(ItemWidthProperty);
  240. set => SetValue(ItemWidthProperty, value);
  241. }
  242. public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(
  243. nameof(ItemHeight), typeof(double), typeof(PinBox), new PropertyMetadata(ValueBoxes.Double0Box));
  244. public double ItemHeight
  245. {
  246. get => (double) GetValue(ItemHeightProperty);
  247. set => SetValue(ItemHeightProperty, value);
  248. }
  249. public static readonly DependencyProperty SelectionBrushProperty =
  250. TextBoxBase.SelectionBrushProperty.AddOwner(typeof(PinBox));
  251. public Brush SelectionBrush
  252. {
  253. get => (Brush) GetValue(SelectionBrushProperty);
  254. set => SetValue(SelectionBrushProperty, value);
  255. }
  256. #if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472)
  257. public static readonly DependencyProperty SelectionTextBrushProperty =
  258. TextBoxBase.SelectionTextBrushProperty.AddOwner(typeof(PinBox));
  259. public Brush SelectionTextBrush
  260. {
  261. get => (Brush) GetValue(SelectionTextBrushProperty);
  262. set => SetValue(SelectionTextBrushProperty, value);
  263. }
  264. #endif
  265. public static readonly DependencyProperty SelectionOpacityProperty =
  266. TextBoxBase.SelectionOpacityProperty.AddOwner(typeof(PinBox));
  267. public double SelectionOpacity
  268. {
  269. get => (double) GetValue(SelectionOpacityProperty);
  270. set => SetValue(SelectionOpacityProperty, value);
  271. }
  272. public static readonly DependencyProperty CaretBrushProperty =
  273. TextBoxBase.CaretBrushProperty.AddOwner(typeof(PinBox));
  274. public Brush CaretBrush
  275. {
  276. get => (Brush) GetValue(CaretBrushProperty);
  277. set => SetValue(CaretBrushProperty, value);
  278. }
  279. public static readonly DependencyProperty IsSafeEnabledProperty = PasswordBox.IsSafeEnabledProperty
  280. .AddOwner(typeof(PinBox), new FrameworkPropertyMetadata(ValueBoxes.TrueBox, OnIsSafeEnabledChanged));
  281. private static void OnIsSafeEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  282. {
  283. var p = (PinBox) d;
  284. p.OnIsSafeEnabledChanged((bool) e.NewValue);
  285. }
  286. private void OnIsSafeEnabledChanged(bool newValue)
  287. {
  288. if (_panel == null)
  289. {
  290. return;
  291. }
  292. SetCurrentValue(UnsafePasswordProperty, !newValue ? Password : string.Empty);
  293. }
  294. public bool IsSafeEnabled
  295. {
  296. get => (bool) GetValue(IsSafeEnabledProperty);
  297. set => SetValue(IsSafeEnabledProperty, ValueBoxes.BooleanBox(value));
  298. }
  299. public static readonly DependencyProperty UnsafePasswordProperty = PasswordBox.UnsafePasswordProperty
  300. .AddOwner(typeof(PinBox), new FrameworkPropertyMetadata(default(string),
  301. FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnUnsafePasswordChanged));
  302. private static void OnUnsafePasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  303. {
  304. var p = (PinBox) d;
  305. if (!p.IsSafeEnabled)
  306. {
  307. p.Password = e.NewValue != null ? e.NewValue.ToString() : string.Empty;
  308. }
  309. }
  310. public string UnsafePassword
  311. {
  312. get => (string) GetValue(UnsafePasswordProperty);
  313. set => SetValue(UnsafePasswordProperty, value);
  314. }
  315. public static readonly RoutedEvent CompletedEvent =
  316. EventManager.RegisterRoutedEvent("Completed", RoutingStrategy.Bubble,
  317. typeof(RoutedEventHandler), typeof(PinBox));
  318. public event RoutedEventHandler Completed
  319. {
  320. add => AddHandler(CompletedEvent, value);
  321. remove => RemoveHandler(CompletedEvent, value);
  322. }
  323. private void UpdateItems()
  324. {
  325. if (_panel == null) return;
  326. _panel.Children.Clear();
  327. var length = Length;
  328. for (var i = 0; i < length; i++)
  329. {
  330. _panel.Children.Add(CreatePasswordBox());
  331. }
  332. }
  333. private System.Windows.Controls.PasswordBox CreatePasswordBox()
  334. {
  335. var passwordBox = new System.Windows.Controls.PasswordBox
  336. {
  337. MaxLength = 1,
  338. HorizontalContentAlignment = HorizontalAlignment.Center,
  339. VerticalAlignment = VerticalAlignment.Center,
  340. Margin = ItemMargin,
  341. Width = ItemWidth,
  342. Height = ItemHeight,
  343. Padding = default,
  344. PasswordChar = PasswordChar,
  345. Foreground = Foreground
  346. };
  347. passwordBox.SetBinding(SelectionBrushProperty, new Binding(SelectionBrushProperty.Name) { Source = this });
  348. #if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472)
  349. passwordBox.SetBinding(SelectionTextBrushProperty, new Binding(SelectionTextBrushProperty.Name) { Source = this });
  350. #endif
  351. passwordBox.SetBinding(SelectionOpacityProperty, new Binding(SelectionOpacityProperty.Name) { Source = this });
  352. passwordBox.SetBinding(CaretBrushProperty, new Binding(CaretBrushProperty.Name) { Source = this });
  353. return passwordBox;
  354. }
  355. public override void OnApplyTemplate()
  356. {
  357. base.OnApplyTemplate();
  358. _panel = GetTemplateChild(ElementPanel) as Panel;
  359. if (_panel != null)
  360. {
  361. UpdateItems();
  362. var length = Length;
  363. if (_passwordList != null && _passwordList.Count == length && _panel.Children.Count == length)
  364. {
  365. for (var i = 0; i < length; i++)
  366. {
  367. var password = _passwordList[i];
  368. if (password.Length > 0)
  369. {
  370. var valuePtr = IntPtr.Zero;
  371. try
  372. {
  373. valuePtr = Marshal.SecureStringToGlobalAllocUnicode(password);
  374. if (_panel.Children[i] is System.Windows.Controls.PasswordBox passwordBox)
  375. {
  376. passwordBox.Password = Marshal.PtrToStringUni(valuePtr) ?? throw new InvalidOperationException();
  377. }
  378. }
  379. finally
  380. {
  381. Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
  382. password.Clear();
  383. }
  384. }
  385. }
  386. _passwordList.Clear();
  387. }
  388. OnIsSafeEnabledChanged(IsSafeEnabled);
  389. }
  390. }
  391. }