Created
August 14, 2012 08:18
-
-
Save JakeGinnivan/3347486 to your computer and use it in GitHub Desktop.
Masking
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Linq; | |
| using System.Text.RegularExpressions; | |
| using System.Windows; | |
| using System.Windows.Controls; | |
| using System.Windows.Input; | |
| using CCWin.Core.Extensions; | |
| namespace CCWin.App.Infrastructure.Behaviours | |
| { | |
| /// <summary> | |
| /// Provides masking behavior for any <see cref="TextBox"/>. | |
| /// </summary> | |
| /// <remarks> | |
| /// Taken from a StackOverflow answer: http://stackoverflow.com/questions/1103765/wpf-textbox-how-to-define-some-restriction | |
| /// </remarks> | |
| public static class Masking | |
| { | |
| private static readonly DependencyPropertyKey _maskExpressionPropertyKey = | |
| DependencyProperty.RegisterAttachedReadOnly("MaskExpression", | |
| typeof(Regex), typeof(Masking), new FrameworkPropertyMetadata()); | |
| /// <summary> | |
| /// Identifies the <see cref="Mask"/> dependency property. | |
| /// </summary> | |
| public static readonly DependencyProperty MaskProperty = | |
| DependencyProperty.RegisterAttached("Mask", typeof(string), typeof(Masking), new FrameworkPropertyMetadata(OnMaskChanged)); | |
| /// <summary> | |
| /// Identifies the <see cref="MaskExpression"/> dependency property. | |
| /// </summary> | |
| public static readonly DependencyProperty MaskExpressionProperty = _maskExpressionPropertyKey.DependencyProperty; | |
| /// <summary> | |
| /// Gets the mask for a given <see cref="TextBox"/>. | |
| /// </summary> | |
| /// <param name="textBox"> | |
| /// The <see cref="TextBox"/> whose mask is to be retrieved. | |
| /// </param> | |
| /// <returns> | |
| /// The mask, or <see langword="null"/> if no mask has been set. | |
| /// </returns> | |
| public static string GetMask(TextBox textBox) | |
| { | |
| if (textBox == null) | |
| { | |
| throw new ArgumentNullException("textBox"); | |
| } | |
| return textBox.GetValue(MaskProperty) as string; | |
| } | |
| /// <summary> | |
| /// Sets the mask for a given <see cref="TextBox"/>. | |
| /// </summary> | |
| /// <param name="textBox"> | |
| /// The <see cref="TextBox"/> whose mask is to be set. | |
| /// </param> | |
| /// <param name="mask"> | |
| /// The mask to set, or <see langword="null"/> to remove any existing mask from <paramref name="textBox"/>. | |
| /// </param> | |
| public static void SetMask(TextBox textBox, string mask) | |
| { | |
| if (textBox == null) | |
| { | |
| throw new ArgumentNullException("textBox"); | |
| } | |
| textBox.SetValue(MaskProperty, mask); | |
| } | |
| /// <summary> | |
| /// Gets the mask expression for the <see cref="TextBox"/>. | |
| /// </summary> | |
| /// <remarks> | |
| /// This method can be used to retrieve the actual <see cref="Regex"/> instance created as a result of setting the mask on a <see cref="TextBox"/>. | |
| /// </remarks> | |
| /// <param name="textBox"> | |
| /// The <see cref="TextBox"/> whose mask expression is to be retrieved. | |
| /// </param> | |
| /// <returns> | |
| /// The mask expression as an instance of <see cref="Regex"/>, or <see langword="null"/> if no mask has been applied to <paramref name="textBox"/>. | |
| /// </returns> | |
| public static Regex GetMaskExpression(TextBox textBox) | |
| { | |
| if (textBox == null) | |
| { | |
| throw new ArgumentNullException("textBox"); | |
| } | |
| return textBox.GetValue(MaskExpressionProperty) as Regex; | |
| } | |
| private static void SetMaskExpression(TextBox textBox, Regex regex) | |
| { | |
| textBox.SetValue(_maskExpressionPropertyKey, regex); | |
| } | |
| private static void OnMaskChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) | |
| { | |
| var textBox = dependencyObject as TextBox; | |
| var mask = e.NewValue as string; | |
| textBox.PreviewTextInput -= textBox_PreviewTextInput; | |
| textBox.PreviewKeyDown -= textBox_PreviewKeyDown; | |
| DataObject.RemovePastingHandler(textBox, Pasting); | |
| if (mask == null) | |
| { | |
| textBox.ClearValue(MaskProperty); | |
| textBox.ClearValue(MaskExpressionProperty); | |
| } | |
| else | |
| { | |
| textBox.SetValue(MaskProperty, mask); | |
| SetMaskExpression(textBox, new Regex(mask, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace)); | |
| textBox.PreviewTextInput += textBox_PreviewTextInput; | |
| textBox.PreviewKeyDown += textBox_PreviewKeyDown; | |
| DataObject.AddPastingHandler(textBox, Pasting); | |
| } | |
| } | |
| private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e) | |
| { | |
| var textBox = sender as TextBox; | |
| var maskExpression = GetMaskExpression(textBox); | |
| if (maskExpression == null) | |
| { | |
| return; | |
| } | |
| var proposedText = GetProposedText(textBox, e.Text); | |
| if (!maskExpression.IsMatch(proposedText)) | |
| { | |
| e.Handled = true; | |
| } | |
| } | |
| private static void textBox_PreviewKeyDown(object sender, KeyEventArgs e) | |
| { | |
| var textBox = sender as TextBox; | |
| var maskExpression = GetMaskExpression(textBox); | |
| if (maskExpression == null) | |
| { | |
| return; | |
| } | |
| //pressing space doesn't raise PreviewTextInput - no idea why, but we need to handle | |
| //explicitly here | |
| if (e.Key == Key.Space) | |
| { | |
| var proposedText = GetProposedText(textBox, " "); | |
| if (!maskExpression.IsMatch(proposedText)) | |
| { | |
| e.Handled = true; | |
| } | |
| } | |
| } | |
| private static void Pasting(object sender, DataObjectPastingEventArgs e) | |
| { | |
| var textBox = sender as TextBox; | |
| var maskExpression = GetMaskExpression(textBox); | |
| if (maskExpression == null) | |
| { | |
| return; | |
| } | |
| if (e.DataObject.GetDataPresent(typeof(string))) | |
| { | |
| var pastedText = e.DataObject.GetData(typeof(string)) as string; | |
| var proposedText = GetProposedText(textBox, pastedText); | |
| if (!maskExpression.IsMatch(proposedText)) | |
| { | |
| e.CancelCommand(); | |
| } | |
| } | |
| else | |
| { | |
| e.CancelCommand(); | |
| } | |
| } | |
| /// <summary> | |
| /// Yikes - in .net 4.0 you can't believe the Text Property. If you bind a decimal to a textbox and enter '25.' and have | |
| /// UpdateSourceTrigger = PropertyChanged, the textbox displays '25.' but its Text property returns as '25'. | |
| /// This is broken from .net 3.5 (it's actually coz of the changes to the data binding pipeline). | |
| /// Hence the nasty Enumerable.Range crap in this method to get what the textbox is actually displaying :o( | |
| /// </summary> | |
| /// <param name="textBox">The text box.</param> | |
| /// <param name="newText">The new text.</param> | |
| /// <returns></returns> | |
| private static string GetProposedText(TextBox textBox, string newText) | |
| { | |
| var text = Enumerable.Range(0, textBox.LineCount).Aggregate("", (s, i) => s + textBox.GetLineText(i) + (textBox.LineCount - 1 > i ? Environment.NewLine : "")); | |
| if (textBox.SelectionStart != -1) | |
| { | |
| text = text.Remove(textBox.SelectionStart, textBox.SelectionLength); | |
| } | |
| text = text.Insert(textBox.CaretIndex, newText); | |
| return text; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment