/src/MahApps.Metro/Behaviors/PasswordBoxBindingBehavior.cs

https://github.com/MahApps/MahApps.Metro · C# · 223 lines · 165 code · 27 blank · 31 comment · 17 complexity · bdfa547e70121a43d9bc4d654c11ec19 MD5 · raw file

  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.ComponentModel;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Windows;
  9. using System.Windows.Controls;
  10. using System.Windows.Documents;
  11. using MahApps.Metro.Controls;
  12. using MahApps.Metro.ValueBoxes;
  13. using Microsoft.Xaml.Behaviors;
  14. namespace MahApps.Metro.Behaviors
  15. {
  16. public class PasswordBoxBindingBehavior : Behavior<PasswordBox>
  17. {
  18. /// <summary>
  19. /// Gets or sets the Password property on the PasswordBox control. This is a dependency property.
  20. /// </summary>
  21. public static readonly DependencyProperty PasswordProperty
  22. = DependencyProperty.RegisterAttached(
  23. "Password",
  24. typeof(string),
  25. typeof(PasswordBoxBindingBehavior),
  26. new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPasswordPropertyChanged));
  27. /// <summary>Helper for getting <see cref="PasswordProperty"/> from <paramref name="dpo"/>.</summary>
  28. /// <param name="dpo"><see cref="DependencyObject"/> to read <see cref="PasswordProperty"/> from.</param>
  29. /// <returns>Password property value.</returns>
  30. [Category(AppName.MahApps)]
  31. [AttachedPropertyBrowsableForType(typeof(PasswordBox))]
  32. public static string GetPassword(DependencyObject dpo)
  33. {
  34. return (string)dpo.GetValue(PasswordProperty);
  35. }
  36. /// <summary>Helper for setting <see cref="PasswordProperty"/> on <paramref name="dpo"/>.</summary>
  37. /// <param name="dpo"><see cref="DependencyObject"/> to set <see cref="PasswordProperty"/> on.</param>
  38. /// <param name="value">Password property value.</param>
  39. [Category(AppName.MahApps)]
  40. [AttachedPropertyBrowsableForType(typeof(PasswordBox))]
  41. public static void SetPassword(DependencyObject dpo, string value)
  42. {
  43. dpo.SetValue(PasswordProperty, value);
  44. }
  45. /// <summary>
  46. /// Handles changes to the 'Password' attached property.
  47. /// </summary>
  48. private static void OnPasswordPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
  49. {
  50. if (sender is PasswordBox targetPasswordBox)
  51. {
  52. targetPasswordBox.PasswordChanged -= PasswordBoxPasswordChanged;
  53. if (!GetIsChanging(targetPasswordBox))
  54. {
  55. targetPasswordBox.Password = (string)e.NewValue;
  56. }
  57. targetPasswordBox.PasswordChanged += PasswordBoxPasswordChanged;
  58. }
  59. }
  60. /// <summary>
  61. /// Handle the 'PasswordChanged'-event on the PasswordBox
  62. /// </summary>
  63. private static void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
  64. {
  65. if (sender is PasswordBox passwordBox)
  66. {
  67. SetIsChanging(passwordBox, true);
  68. SetPassword(passwordBox, passwordBox.Password);
  69. SetIsChanging(passwordBox, false);
  70. }
  71. }
  72. private static void SetRevealedPasswordCaretIndex(PasswordBox passwordBox)
  73. {
  74. var textBox = GetRevealedPasswordTextBox(passwordBox);
  75. if (textBox != null)
  76. {
  77. var caretPos = GetPasswordBoxCaretPosition(passwordBox);
  78. textBox.CaretIndex = caretPos;
  79. }
  80. }
  81. private static int GetPasswordBoxCaretPosition(PasswordBox passwordBox)
  82. {
  83. var selection = GetSelection(passwordBox);
  84. var tTextRange = selection?.GetType().GetInterfaces().FirstOrDefault(i => i.Name == "ITextRange");
  85. var oStart = tTextRange?.GetProperty("Start")?.GetGetMethod()?.Invoke(selection, null);
  86. var value = oStart?.GetType().GetProperty("Offset", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(oStart, null) as int?;
  87. var caretPosition = value.GetValueOrDefault(0);
  88. return caretPosition;
  89. }
  90. /// <summary>
  91. /// Called after the behavior is attached to an AssociatedObject.
  92. /// </summary>
  93. /// <remarks>
  94. /// Override this to hook up functionality to the AssociatedObject.
  95. /// </remarks>
  96. protected override void OnAttached()
  97. {
  98. base.OnAttached();
  99. this.AssociatedObject.PasswordChanged += PasswordBoxPasswordChanged;
  100. this.AssociatedObject.Loaded += this.PasswordBoxLoaded;
  101. var selection = GetSelection(this.AssociatedObject);
  102. if (selection != null)
  103. {
  104. selection.Changed += this.PasswordBoxSelectionChanged;
  105. }
  106. }
  107. private void PasswordBoxLoaded(object sender, RoutedEventArgs e)
  108. {
  109. SetPassword(this.AssociatedObject, this.AssociatedObject.Password);
  110. var textBox = this.AssociatedObject.FindChild<TextBox>("RevealedPassword");
  111. if (textBox != null)
  112. {
  113. var selection = GetSelection(this.AssociatedObject);
  114. if (selection is null)
  115. {
  116. var infos = this.AssociatedObject.GetType().GetProperty("Selection", BindingFlags.NonPublic | BindingFlags.Instance);
  117. selection = infos?.GetValue(this.AssociatedObject, null) as TextSelection;
  118. SetSelection(this.AssociatedObject, selection);
  119. if (selection != null)
  120. {
  121. SetRevealedPasswordTextBox(this.AssociatedObject, textBox);
  122. SetRevealedPasswordCaretIndex(this.AssociatedObject);
  123. selection.Changed += this.PasswordBoxSelectionChanged;
  124. }
  125. }
  126. }
  127. }
  128. private void PasswordBoxSelectionChanged(object? sender, EventArgs e)
  129. {
  130. SetRevealedPasswordCaretIndex(this.AssociatedObject);
  131. }
  132. /// <summary>
  133. /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
  134. /// </summary>
  135. /// <remarks>
  136. /// Override this to unhook functionality from the AssociatedObject.
  137. /// </remarks>
  138. protected override void OnDetaching()
  139. {
  140. // it seems, it was already detached, or never attached
  141. if (this.AssociatedObject != null)
  142. {
  143. var selection = GetSelection(this.AssociatedObject);
  144. if (selection != null)
  145. {
  146. selection.Changed -= this.PasswordBoxSelectionChanged;
  147. }
  148. this.AssociatedObject.Loaded -= this.PasswordBoxLoaded;
  149. this.AssociatedObject.PasswordChanged -= PasswordBoxPasswordChanged;
  150. }
  151. base.OnDetaching();
  152. }
  153. private static readonly DependencyProperty IsChangingProperty
  154. = DependencyProperty.RegisterAttached(
  155. "IsChanging",
  156. typeof(bool),
  157. typeof(PasswordBoxBindingBehavior),
  158. new UIPropertyMetadata(BooleanBoxes.FalseBox));
  159. private static bool GetIsChanging(UIElement element)
  160. {
  161. return (bool)element.GetValue(IsChangingProperty);
  162. }
  163. private static void SetIsChanging(UIElement element, bool value)
  164. {
  165. element.SetValue(IsChangingProperty, BooleanBoxes.Box(value));
  166. }
  167. private static readonly DependencyProperty SelectionProperty
  168. = DependencyProperty.RegisterAttached(
  169. "Selection",
  170. typeof(TextSelection),
  171. typeof(PasswordBoxBindingBehavior),
  172. new UIPropertyMetadata(default(TextSelection)));
  173. private static TextSelection? GetSelection(DependencyObject obj)
  174. {
  175. return (TextSelection?)obj.GetValue(SelectionProperty);
  176. }
  177. private static void SetSelection(DependencyObject obj, TextSelection? value)
  178. {
  179. obj.SetValue(SelectionProperty, value);
  180. }
  181. private static readonly DependencyProperty RevealedPasswordTextBoxProperty
  182. = DependencyProperty.RegisterAttached(
  183. "RevealedPasswordTextBox",
  184. typeof(TextBox),
  185. typeof(PasswordBoxBindingBehavior),
  186. new UIPropertyMetadata(default(TextBox)));
  187. private static TextBox? GetRevealedPasswordTextBox(DependencyObject obj)
  188. {
  189. return (TextBox?)obj.GetValue(RevealedPasswordTextBoxProperty);
  190. }
  191. private static void SetRevealedPasswordTextBox(DependencyObject obj, TextBox? value)
  192. {
  193. obj.SetValue(RevealedPasswordTextBoxProperty, value);
  194. }
  195. }
  196. }