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
}
}