Created
November 3, 2018 19:50
-
-
Save heiswayi/f54cd91486f926f066d89c0ca4da5541 to your computer and use it in GitHub Desktop.
WPF utilities: TextBox 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.Windows; | |
using System.Windows.Controls; | |
using System.Globalization; | |
namespace WpfUtilities | |
{ | |
public class TextBoxMaskBehavior | |
{ | |
#region MinimumValue Property | |
public static double GetMinimumValue(DependencyObject obj) | |
{ | |
return (double)obj.GetValue(MinimumValueProperty); | |
} | |
public static void SetMinimumValue(DependencyObject obj, double value) | |
{ | |
obj.SetValue(MinimumValueProperty, value); | |
} | |
public static readonly DependencyProperty MinimumValueProperty = | |
DependencyProperty.RegisterAttached( | |
"MinimumValue", | |
typeof(double), | |
typeof(TextBoxMaskBehavior), | |
new FrameworkPropertyMetadata(double.NaN, MinimumValueChangedCallback) | |
); | |
private static void MinimumValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
TextBox _this = (d as TextBox); | |
ValidateTextBox(_this); | |
} | |
#endregion | |
#region MaximumValue Property | |
public static double GetMaximumValue(DependencyObject obj) | |
{ | |
return (double)obj.GetValue(MaximumValueProperty); | |
} | |
public static void SetMaximumValue(DependencyObject obj, double value) | |
{ | |
obj.SetValue(MaximumValueProperty, value); | |
} | |
public static readonly DependencyProperty MaximumValueProperty = | |
DependencyProperty.RegisterAttached( | |
"MaximumValue", | |
typeof(double), | |
typeof(TextBoxMaskBehavior), | |
new FrameworkPropertyMetadata(double.NaN, MaximumValueChangedCallback) | |
); | |
private static void MaximumValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
TextBox _this = (d as TextBox); | |
ValidateTextBox(_this); | |
} | |
#endregion | |
#region Mask Property | |
public static MaskType GetMask(DependencyObject obj) | |
{ | |
return (MaskType)obj.GetValue(MaskProperty); | |
} | |
public static void SetMask(DependencyObject obj, MaskType value) | |
{ | |
obj.SetValue(MaskProperty, value); | |
} | |
public static readonly DependencyProperty MaskProperty = | |
DependencyProperty.RegisterAttached( | |
"Mask", | |
typeof(MaskType), | |
typeof(TextBoxMaskBehavior), | |
new FrameworkPropertyMetadata(MaskChangedCallback) | |
); | |
private static void MaskChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
if (e.OldValue is TextBox) | |
{ | |
(e.OldValue as TextBox).PreviewTextInput -= TextBox_PreviewTextInput; | |
DataObject.RemovePastingHandler((e.OldValue as TextBox), (DataObjectPastingEventHandler)TextBoxPastingEventHandler); | |
} | |
TextBox _this = (d as TextBox); | |
if (_this == null) | |
return; | |
if ((MaskType)e.NewValue != MaskType.Any) | |
{ | |
_this.PreviewTextInput += TextBox_PreviewTextInput; | |
DataObject.AddPastingHandler(_this, (DataObjectPastingEventHandler)TextBoxPastingEventHandler); | |
} | |
ValidateTextBox(_this); | |
} | |
#endregion | |
#region Private Static Methods | |
private static void ValidateTextBox(TextBox _this) | |
{ | |
if (GetMask(_this) != MaskType.Any) | |
{ | |
_this.Text = ValidateValue(GetMask(_this), _this.Text, GetMinimumValue(_this), GetMaximumValue(_this)); | |
} | |
} | |
private static void TextBoxPastingEventHandler(object sender, DataObjectPastingEventArgs e) | |
{ | |
TextBox _this = (sender as TextBox); | |
string clipboard = e.DataObject.GetData(typeof(string)) as string; | |
clipboard = ValidateValue(GetMask(_this), clipboard, GetMinimumValue(_this), GetMaximumValue(_this)); | |
if (!string.IsNullOrEmpty(clipboard)) | |
{ | |
_this.Text = clipboard; | |
} | |
e.CancelCommand(); | |
e.Handled = true; | |
} | |
private static void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e) | |
{ | |
TextBox _this = (sender as TextBox); | |
bool isValid = IsSymbolValid(GetMask(_this), e.Text); | |
e.Handled = !isValid; | |
if (isValid) | |
{ | |
int caret = _this.CaretIndex; | |
string text = _this.Text; | |
bool textInserted = false; | |
int selectionLength = 0; | |
if (_this.SelectionLength > 0) | |
{ | |
text = text.Substring(0, _this.SelectionStart) + | |
text.Substring(_this.SelectionStart + _this.SelectionLength); | |
caret = _this.SelectionStart; | |
} | |
if (e.Text == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator) | |
{ | |
while (true) | |
{ | |
int ind = text.IndexOf(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator); | |
if (ind == -1) | |
break; | |
text = text.Substring(0, ind) + text.Substring(ind + 1); | |
if (caret > ind) | |
caret--; | |
} | |
if (caret == 0) | |
{ | |
text = "0" + text; | |
caret++; | |
} | |
else | |
{ | |
if (caret == 1 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign) | |
{ | |
text = NumberFormatInfo.CurrentInfo.NegativeSign + "0" + text.Substring(1); | |
caret++; | |
} | |
} | |
if (caret == text.Length) | |
{ | |
selectionLength = 1; | |
textInserted = true; | |
text = text + NumberFormatInfo.CurrentInfo.NumberDecimalSeparator + "0"; | |
caret++; | |
} | |
} | |
else if (e.Text == NumberFormatInfo.CurrentInfo.NegativeSign) | |
{ | |
textInserted = true; | |
if (_this.Text.Contains(NumberFormatInfo.CurrentInfo.NegativeSign)) | |
{ | |
text = text.Replace(NumberFormatInfo.CurrentInfo.NegativeSign, string.Empty); | |
if (caret != 0) | |
caret--; | |
} | |
else | |
{ | |
text = NumberFormatInfo.CurrentInfo.NegativeSign + _this.Text; | |
caret++; | |
} | |
} | |
if (!textInserted) | |
{ | |
text = text.Substring(0, caret) + e.Text + | |
((caret < _this.Text.Length) ? text.Substring(caret) : string.Empty); | |
caret++; | |
} | |
try | |
{ | |
double val = Convert.ToDouble(text); | |
double newVal = ValidateLimits(GetMinimumValue(_this), GetMaximumValue(_this), val); | |
if (val != newVal) | |
{ | |
text = newVal.ToString(); | |
} | |
else if (val == 0) | |
{ | |
if (!text.Contains(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)) | |
text = "0"; | |
} | |
} | |
catch | |
{ | |
text = "0"; | |
} | |
while (text.Length > 1 && text[0] == '0' && string.Empty + text[1] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator) | |
{ | |
text = text.Substring(1); | |
if (caret > 0) | |
caret--; | |
} | |
while (text.Length > 2 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign && text[1] == '0' && string.Empty + text[2] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator) | |
{ | |
text = NumberFormatInfo.CurrentInfo.NegativeSign + text.Substring(2); | |
if (caret > 1) | |
caret--; | |
} | |
if (caret > text.Length) | |
caret = text.Length; | |
_this.Text = text; | |
_this.CaretIndex = caret; | |
_this.SelectionStart = caret; | |
_this.SelectionLength = selectionLength; | |
e.Handled = true; | |
} | |
} | |
private static string ValidateValue(MaskType mask, string value, double min, double max) | |
{ | |
if (string.IsNullOrEmpty(value)) | |
return string.Empty; | |
value = value.Trim(); | |
switch (mask) | |
{ | |
case MaskType.Integer: | |
try | |
{ | |
Convert.ToInt64(value); | |
return value; | |
} | |
catch | |
{ | |
} | |
return string.Empty; | |
case MaskType.Decimal: | |
try | |
{ | |
Convert.ToDouble(value); | |
return value; | |
} | |
catch | |
{ | |
} | |
return string.Empty; | |
} | |
return value; | |
} | |
private static double ValidateLimits(double min, double max, double value) | |
{ | |
if (!min.Equals(double.NaN)) | |
{ | |
if (value < min) | |
return min; | |
} | |
if (!max.Equals(double.NaN)) | |
{ | |
if (value > max) | |
return max; | |
} | |
return value; | |
} | |
private static bool IsSymbolValid(MaskType mask, string str) | |
{ | |
switch (mask) | |
{ | |
case MaskType.Any: | |
return true; | |
case MaskType.Integer: | |
if (str == NumberFormatInfo.CurrentInfo.NegativeSign) | |
return true; | |
break; | |
case MaskType.Decimal: | |
if (str == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator || | |
str == NumberFormatInfo.CurrentInfo.NegativeSign) | |
return true; | |
break; | |
} | |
if (mask.Equals(MaskType.Integer) || mask.Equals(MaskType.Decimal)) | |
{ | |
foreach (char ch in str) | |
{ | |
if (!Char.IsDigit(ch)) | |
return false; | |
} | |
return true; | |
} | |
return false; | |
} | |
#endregion | |
} | |
public enum MaskType | |
{ | |
Any, | |
Integer, | |
Decimal | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment