using System; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; namespace Developpez.Dotnet.Windows.Behaviors { /// /// Fournit des propriétés attachées permettant d'ajouter des fonctionnalités à une TextBox /// public static class TextBoxBehavior { #region Constructor static TextBoxBehavior() { _pasteCommand = new CommandBinding(ApplicationCommands.Paste); _pasteCommand.PreviewCanExecute += _pasteCommand_PreviewCanExecute; _pasteCommand.PreviewExecuted += _pasteCommand_PreviewExecuted; // Necessary because PreviewExecuted won't be raised otherwise _pasteCommand.Executed += delegate { }; } #endregion #region InfoText /// /// Identifiant de la propriété InfoText /// public static readonly DependencyProperty InfoTextProperty = DependencyProperty.RegisterAttached( "InfoText", typeof(string), typeof(TextBoxBehavior), new UIPropertyMetadata( null, InfoTextChanged)); /// /// Identifiant de la propriété InfoTextForeground /// public static readonly DependencyProperty InfoTextForegroundProperty = DependencyProperty.RegisterAttached("InfoTextForeground", typeof(Brush), typeof(TextBoxBehavior), new UIPropertyMetadata(new SolidColorBrush(Color.FromRgb(0x66, 0x66, 0x66)))); /// /// Obtient le texte d'information en filigrane (watermark) d'une TextBox ou PasswordBox /// /// L'objet pour lequel on veut obtenir le texte en filigrane /// Le texte en filigrane de cet objet [AttachedPropertyBrowsableForType(typeof(TextBox))] [AttachedPropertyBrowsableForType(typeof(PasswordBox))] public static string GetInfoText(DependencyObject obj) { return (string)obj.GetValue(InfoTextProperty); } /// /// Définit le texte d'information en filigrane (watermark) d'une TextBox ou PasswordBox /// /// L'objet pour lequel on veut définir le texte en filigrane /// Le texte à mettre en filigrane sur cet objet public static void SetInfoText(DependencyObject obj, string value) { obj.SetValue(InfoTextProperty, value); } /// /// Obtient le pinceau utilisé pour le texte d'information en filigrane (watermark) d'une TextBox ou PasswordBox /// /// L'objet pour lequel on veut obtenir le pinceau /// Le pinceau utilisé pour cet objet [AttachedPropertyBrowsableForType(typeof(TextBox))] [AttachedPropertyBrowsableForType(typeof(PasswordBox))] public static Brush GetInfoTextForeground(DependencyObject obj) { return (Brush)obj.GetValue(InfoTextForegroundProperty); } /// /// Définit le pinceau utilisé pour le texte d'information en filigrane (watermark) d'une TextBox ou PasswordBox /// /// L'objet pour lequel on veut définir le pinceau /// Le pinceau à utiliser pour cet objet public static void SetInfoTextForeground(DependencyObject obj, Brush value) { obj.SetValue(InfoTextForegroundProperty, value); } private static void InfoTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { var element = o as FrameworkElement; var textBox = o as TextBox; var passwordBox = o as PasswordBox; if (element == null || textBox == null && passwordBox == null) return; var oldValue = (string)e.OldValue; var newValue = (string)e.NewValue; if (!string.IsNullOrEmpty(oldValue) && string.IsNullOrEmpty(newValue)) { element.IsVisibleChanged -= element_IsVisibleChanged; RemoveInfoTextAdorner(element); } else if (!string.IsNullOrEmpty(newValue) && string.IsNullOrEmpty(oldValue)) { element.IsVisibleChanged += element_IsVisibleChanged; if (element.IsVisible) AddInfoTextAdorner(element); } } static void element_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { var element = sender as UIElement; if (element == null) return; if (element.IsVisible) AddInfoTextAdorner(element); else RemoveInfoTextAdorner(element); } private static void AddInfoTextAdorner(UIElement element) { var textBox = element as TextBox; var passwordBox = element as PasswordBox; if (textBox != null) AddInfoTextAdorner(textBox); else if (passwordBox != null) AddInfoTextAdorner(passwordBox); } private static void AddInfoTextAdorner(TextBox textBox) { InfoTextAdorner adorner = null; Action updateVisibility = () => adorner.UpdateVisibility(string.IsNullOrEmpty(textBox.Text)); TextChangedEventHandler handler = delegate { updateVisibility(); }; Action disposeAction = () => textBox.TextChanged -= handler; adorner = new InfoTextAdorner(textBox, disposeAction); textBox.TextChanged += handler; updateVisibility(); AddAdorner(textBox, adorner); } private static void AddInfoTextAdorner(PasswordBox passwordBox) { InfoTextAdorner adorner = null; Action updateVisibility = () => adorner.UpdateVisibility(passwordBox.SecurePassword == null || passwordBox.SecurePassword.Length == 0); RoutedEventHandler handler = delegate { updateVisibility(); }; Action disposeAction = () => passwordBox.PasswordChanged -= handler; adorner = new InfoTextAdorner(passwordBox, disposeAction); passwordBox.PasswordChanged += handler; updateVisibility(); AddAdorner(passwordBox, adorner); } private static void RemoveInfoTextAdorner(UIElement element) { var layer = AdornerLayer.GetAdornerLayer(element); if (layer == null) return; var adorner = layer.GetAdorners(element).OfType().FirstOrDefault(); if (adorner == null) return; layer.Remove(adorner); adorner.Dispose(); } private static void AddAdorner(UIElement adornedElement, Adorner adorner) { var layer = AdornerLayer.GetAdornerLayer(adornedElement); if (layer == null) return; layer.Add(adorner); } private class InfoTextAdorner : Adorner, IDisposable { private readonly DoubleAnimation _showAnimation; private readonly DoubleAnimation _hideAnimation; private readonly DoubleAnimation _gotFocusAnimation; private readonly DoubleAnimation _lostFocusAnimation; private readonly Action _disposeAction; private readonly TextBlock _infoTextBlock; private bool _isVisible = true; public InfoTextAdorner(UIElement adornedElement, Action disposeAction) : base(adornedElement) { _disposeAction = disposeAction; _showAnimation = new DoubleAnimation(0.67, new Duration(TimeSpan.FromSeconds(0.3))); _hideAnimation = new DoubleAnimation(0.0, new Duration(TimeSpan.FromSeconds(0.0))); _gotFocusAnimation = new DoubleAnimation(0.33, new Duration(TimeSpan.FromSeconds(0.3))); _lostFocusAnimation = new DoubleAnimation(0.67, new Duration(TimeSpan.FromSeconds(0.3))); _infoTextBlock = new TextBlock(); _infoTextBlock.FontStyle = FontStyles.Italic; _infoTextBlock.IsHitTestVisible = false; _infoTextBlock.Margin = new Thickness(2, 0, 2, 0); AdornedElement.GotFocus += AdornedElement_GotFocus; AdornedElement.LostFocus += AdornedElement_LostFocus; SetBinding(_infoTextBlock, TextBlock.TextProperty, adornedElement, TextBoxBehavior.InfoTextProperty); SetBinding(_infoTextBlock, TextBlock.FontSizeProperty, adornedElement, TextElement.FontSizeProperty); SetBinding(_infoTextBlock, TextBlock.ForegroundProperty, adornedElement, TextBoxBehavior.InfoTextForegroundProperty); SetBinding(_infoTextBlock, TextBlock.TextAlignmentProperty, adornedElement, TextBlock.TextAlignmentProperty); SetBinding(_infoTextBlock, FrameworkElement.VerticalAlignmentProperty, adornedElement, Control.VerticalContentAlignmentProperty); SetBinding(_infoTextBlock, FrameworkElement.HorizontalAlignmentProperty, adornedElement, Control.HorizontalContentAlignmentProperty); SetBinding(_infoTextBlock, TextBlock.PaddingProperty, adornedElement, Control.PaddingProperty); this.AddVisualChild(_infoTextBlock); } void AdornedElement_LostFocus(object sender, RoutedEventArgs e) { if (_isVisible) AnimateOpacity(_lostFocusAnimation); } void AdornedElement_GotFocus(object sender, RoutedEventArgs e) { if (_isVisible) AnimateOpacity(_gotFocusAnimation); } private static void SetBinding( DependencyObject targetObject, DependencyProperty targetProperty, DependencyObject sourceObject, DependencyProperty sourceProperty) { BindingOperations.SetBinding( targetObject, targetProperty, new Binding { Source = sourceObject, Path = new PropertyPath(sourceProperty) }); } public void UpdateVisibility(bool visible) { _isVisible = visible; AnimateOpacity(visible ? _showAnimation : _hideAnimation); } private void AnimateOpacity(DoubleAnimation animation) { _infoTextBlock.BeginAnimation(UIElement.OpacityProperty, animation); } public void Dispose() { if (_disposeAction != null) _disposeAction(); AdornedElement.GotFocus -= AdornedElement_GotFocus; AdornedElement.LostFocus -= AdornedElement_LostFocus; } protected override Size MeasureOverride(Size constraint) { AdornedElement.Measure(constraint); return AdornedElement.RenderSize; } protected override Size ArrangeOverride(Size finalSize) { _infoTextBlock.Arrange(new Rect(finalSize)); return finalSize; } protected override Visual GetVisualChild(int index) { if (index == 0) return _infoTextBlock; throw new ArgumentOutOfRangeException("index"); } protected override int VisualChildrenCount { get { return 1; } } } #endregion #region SelectAllOnFocus /// /// Obtient la valeur de la propriété SelectAllOnFocus pour l'objet spécifié /// /// L'objet pour lequel on veut obtenir la valeur de la propriété /// true si tout le texte est sélectionné quand l'élément prend le focus, sinon false. [AttachedPropertyBrowsableForType(typeof(TextBoxBase))] [AttachedPropertyBrowsableForType(typeof(PasswordBox))] public static bool GetSelectAllOnFocus(DependencyObject obj) { return (bool)obj.GetValue(SelectAllOnFocusProperty); } /// /// Définit la valeur de la propriété SelectAllOnFocus pour l'objet spécifié /// /// L'objet pour lequel on veut définir la valeur de la propriété /// true pour que tout le texte soit sélectionné quand l'élément prend le focus, sinon false. public static void SetSelectAllOnFocus(DependencyObject obj, bool value) { obj.SetValue(SelectAllOnFocusProperty, value); } /// /// Identifiant de la propriété SelectAllOnFocus /// public static readonly DependencyProperty SelectAllOnFocusProperty = DependencyProperty.RegisterAttached( "SelectAllOnFocus", typeof(bool), typeof(TextBoxBehavior), new UIPropertyMetadata( false, SelectAllOnFocusChanged)); private static void SelectAllOnFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { var element = o as FrameworkElement; var textBox = o as TextBox; var passwordBox = o as PasswordBox; if (element == null || textBox == null && passwordBox == null) return; bool oldValue = (bool)e.OldValue; bool newValue = (bool)e.NewValue; if (oldValue && !newValue) { element.GotKeyboardFocus -= element_GotKeyboardFocus; } else if (newValue && !oldValue) { element.GotKeyboardFocus += element_GotKeyboardFocus; } } static void element_GotKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e) { var textBox = sender as TextBoxBase; var passwordBox = sender as PasswordBox; if (textBox != null) { textBox.SelectAll(); } else if (passwordBox != null) { passwordBox.SelectAll(); } } #endregion #region NumericOnly private static readonly CommandBinding _pasteCommand; /// /// Obtient la valeur de la propriété NumericOnly pour l'objet spécifié /// /// L'objet pour lequel on veut obtenir la valeur de la propriété /// true si la TextBox n'accepte que des nombres, sinon false [AttachedPropertyBrowsableForType(typeof(TextBox))] public static bool GetNumericOnly(TextBox obj) { return (bool)obj.GetValue(NumericOnlyProperty); } /// /// Définit la valeur de la propriété NumericOnly pour l'objet spécifié /// /// L'objet pour lequel on veut définir la valeur de la propriété /// true si la TextBox ne doit accepter que des nombres, sinon false public static void SetNumericOnly(TextBox obj, bool value) { obj.SetValue(NumericOnlyProperty, value); } /// /// Identifiant de la propriété NumericOnly /// public static readonly DependencyProperty NumericOnlyProperty = DependencyProperty.RegisterAttached( "NumericOnly", typeof(bool), typeof(TextBoxBehavior), new UIPropertyMetadata( false, NumericOnlyChanged)); /// /// Obtient la valeur de la propriété AllowDecimal pour l'objet spécifié /// /// L'objet pour lequel on veut obtenir la valeur de la propriété /// true si la TextBox accepte des nombres décimaux, sinon false [AttachedPropertyBrowsableForType(typeof(TextBox))] public static bool GetAllowDecimal(TextBox obj) { return (bool)obj.GetValue(AllowDecimalProperty); } /// /// Définit la valeur de la propriété AllowDecimal pour l'objet spécifié /// /// L'objet pour lequel on veut définir la valeur de la propriété /// true si la TextBox doit accepter des nombres décimaux, sinon false public static void SetAllowDecimal(TextBox obj, bool value) { obj.SetValue(AllowDecimalProperty, value); } /// /// Identifiant de la propriété AllowDecimal /// public static readonly DependencyProperty AllowDecimalProperty = DependencyProperty.RegisterAttached("AllowDecimal", typeof(bool), typeof(TextBoxBehavior), new UIPropertyMetadata(true)); private static void NumericOnlyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { var textBox = o as TextBox; if (textBox == null) return; var oldValue = (bool)e.OldValue; var newValue = (bool)e.NewValue; if (oldValue && !newValue) { DetachNumericTextBox(textBox); } else if (newValue && !oldValue) { AttachNumericTextBox(textBox); } } private static void AttachNumericTextBox(TextBox textBox) { textBox.PreviewTextInput += textBox_PreviewTextInput; textBox.CommandBindings.Add(_pasteCommand); } private static void DetachNumericTextBox(TextBox textBox) { textBox.PreviewTextInput -= textBox_PreviewTextInput; textBox.CommandBindings.Remove(_pasteCommand); } private static bool IsValidInput(TextBox textBox, string input, out string newText) { bool allowDecimal = GetAllowDecimal(textBox); string currentText = textBox.Text; int selStart = textBox.SelectionStart; int selLength = textBox.SelectionLength; string leftText = currentText.Substring(0, selStart); string rightText = currentText.Substring(selStart + selLength); newText = leftText + input + rightText; if (allowDecimal) { newText = newText.Replace(CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator, CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); decimal d; if (!decimal.TryParse(newText, out d) && newText != CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator) { return false; } } else { long n; if (!long.TryParse(newText, out n)) { return false; } } return true; } private static void HandleInput(TextBox textBox, string input, RoutedEventArgs e) { string newText; if (IsValidInput(textBox, input, out newText)) { if (newText != input) { int selStart = textBox.SelectionStart + input.Length; textBox.Text = newText; textBox.SelectionStart = selStart; textBox.SelectionLength = 0; e.Handled = true; } } else { e.Handled = true; } } private static void _pasteCommand_PreviewExecuted(object sender, ExecutedRoutedEventArgs e) { var textBox = sender as TextBox; if (textBox == null) return; if (!Clipboard.ContainsText()) return; string clipboardText = Clipboard.GetText(); HandleInput(textBox, clipboardText, e); } private static void _pasteCommand_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e) { } private static void textBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e) { var textBox = sender as TextBox; if (textBox == null) return; HandleInput(textBox, e.Text, e); } #endregion } }