在Winform中当我们对输入内容有限制。。比如说必须是时间呀。。货币呀。。。等等时,我们或许可以使用MaskedTextBox,但是在WPF中不存在这个控件。。所以我们可以自己写一个这样的空间作为用户空间来使用它。。。
下面就是一个自写的MaskedTextBox全码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MaskedTextBox.Control
{
public class MaskedTextBox : TextBox
{
public static readonly DependencyProperty InputMaskProperty;
private List<InputMaskChar> _maskChars;
private int _caretIndex;
static MaskedTextBox()
{
TextProperty.OverrideMetadata(typeof(MaskedTextBox),
new FrameworkPropertyMetadata(null, new CoerceValueCallback(Text_CoerceValue)));
InputMaskProperty = DependencyProperty.Register("InputMask", typeof(string), typeof(MaskedTextBox),
new PropertyMetadata(string.Empty, new PropertyChangedCallback(InputMask_Changed)));
}
public MaskedTextBox()
{
this._maskChars = new List<InputMaskChar>();
DataObject.AddPastingHandler(this, new DataObjectPastingEventHandler(MaskedTextBox_Paste));
}
/// <summary>
/// Get or Set the input mask.
/// </summary>
public string InputMask
{
get { return this.GetValue(InputMaskProperty) as string; }
set { this.SetValue(InputMaskProperty, value); }
}
[Flags]
protected enum InputMaskValidationFlags
{
None = 0,
AllowInteger = 1,
AllowDecimal = 2,
AllowAlphabet = 4,
AllowAlphanumeric = 8
}
/// <summary>
/// Returns a value indicating if the current text value is valid.
/// </summary>
/// <returns></returns>
public bool IsTextValid()
{
string value;
return this.ValidateTextInternal(this.Text, out value);
}
private class InputMaskChar
{
private InputMaskValidationFlags _validationFlags;
private char _literal;
public InputMaskChar(InputMaskValidationFlags validationFlags)
{
this._validationFlags = validationFlags;
this._literal = (char)0;
}
public InputMaskChar(char literal)
{
this._literal = literal;
}
public InputMaskValidationFlags ValidationFlags
{
get { return this._validationFlags; }
set { this._validationFlags = value; }
}
public char Literal
{
get { return this._literal; }
set { this._literal = value; }
}
public bool IsLiteral()
{
return (this._literal != (char)0);
}
public char GetDefaultChar()
{
return (this.IsLiteral()) ? this.Literal : '_';
}
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
//DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TextProperty, typeof(TextBox));
//if (dpd != null)
//{
// dpd.AddValueChanged(this, delegate
// {
// this.UpdateInputMask();
// });
//}
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
this._caretIndex = this.CaretIndex;
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
//no mask specified, just function as a normal textbox
if (this._maskChars.Count == 0)
return;
if (e.Key == Key.Delete)
{
//delete key pressed: delete all text
this.Text = this.GetDefaultText();
this._caretIndex = this.CaretIndex = 0;
e.Handled = true;
}
else
{
//backspace key pressed
if (e.Key == Key.Back)
{
if (this._caretIndex > 0 || this.SelectionLength > 0)
{
if (this.SelectionLength > 0)
{
//if one or more characters selected, delete them
this.DeleteSelectedText();
}
else
{
//if no characters selected, shift the caret back to the previous non-literal char and delete it
this.MoveBack();
char[] characters = this.Text.ToCharArray();
characters[this._caretIndex] = this._maskChars[this._caretIndex].GetDefaultChar();
this.Text = new string(characters);
}
//update the base class caret index, and swallow the event
this.CaretIndex = this._caretIndex;
e.Handled = true;
}
}
else if (e.Key == Key.Left)
{
//move back to the previous non-literal character
this.MoveBack();
e.Handled = true;
}
else if (e.Key == Key.Right || e.Key == Key.Space)
{
//move forwards to the next non-literal character
this.MoveForward();
e.Handled = true;
}
}
}
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
base.OnPreviewTextInput(e);
//no mask specified, just function as a normal textbox
if (this._maskChars.Count == 0)
return;
this._caretIndex = this.CaretIndex = this.SelectionStart;
if (this._caretIndex == this._maskChars.Count)
{
//at the end of the character count defined by the input mask- no more characters allowed
e.Handled = true;
}
else
{
//validate the character against its validation scheme
bool isValid = this.ValidateInputChar(char.Parse(e.Text),
this._maskChars[this._caretIndex].ValidationFlags);
if (isValid)
{
//delete any selected text
if (this.SelectionLength > 0)
{
this.DeleteSelectedText();
}
//insert the new character
char[] characters = this.Text.ToCharArray();
characters[this._caretIndex] = char.Parse(e.Text);
this.Text = new string(characters);
//move the caret on
this.MoveForward();
}
e.Handled = true;
}
}
/// <summary>
/// Validates the specified character against all selected validation schemes.
/// </summary>
/// <param name="input"></param>
/// <param name="validationFlags"></param>
/// <returns></returns>
protected virtual bool ValidateInputChar(char input, InputMaskValidationFlags validationFlags)
{
bool valid = (validationFlags == InputMaskValidationFlags.None);
if (!valid)
{
Array values = Enum.GetValues(typeof(InputMaskValidationFlags));
//iterate through the validation schemes
foreach (object o in values)
{
InputMaskValidationFlags instance = (InputMaskValidationFlags)(int)o;
if ((instance & validationFlags) != 0)
{
if (this.ValidateCharInternal(input, instance))
{
valid = true;
break;
}
}
}
}
return valid;
}
/// <summary>
/// Returns a value indicating if the current text value is valid.
/// </summary>
/// <returns></returns>
protected virtual bool ValidateTextInternal(string text, out string displayText)
{
if (this._maskChars.Count == 0)
{
displayText = text;
return true;
}
StringBuilder displayTextBuilder = new StringBuilder(this.GetDefaultText());
bool valid = (!string.IsNullOrEmpty(text) &&
text.Length <= this._maskChars.Count);
if (valid)
{
for (int i = 0; i < text.Length; i++)
{
if (!this._maskChars[i].IsLiteral())
{
if (this.ValidateInputChar(text[i], this._maskChars[i].ValidationFlags))
{
displayTextBuilder[i] = text[i];
}
else
{
valid = false;
}
}
}
}
displayText = displayTextBuilder.ToString();
return valid;
}
/// <summary>
/// Deletes the currently selected text.
/// </summary>
protected virtual void DeleteSelectedText()
{
StringBuilder text = new StringBuilder(this.Text);
string defaultText = this.GetDefaultText();
int selectionStart = this.SelectionStart;
int selectionLength = this.SelectionLength;
text.Remove(selectionStart, selectionLength);
text.Insert(selectionStart, defaultText.Substring(selectionStart, selectionLength));
this.Text = text.ToString();
//reset the caret position
this.CaretIndex = this._caretIndex = selectionStart;
}
/// <summary>
/// Returns a value indicating if the specified input mask character is a placeholder.
/// </summary>
/// <param name="character"></param>
/// <param name="validationFlags">If the character is a placeholder, returns the relevant validation scheme.</param>
/// <returns></returns>
protected virtual bool IsPlaceholderChar(char character, out InputMaskValidationFlags validationFlags)
{
validationFlags = InputMaskValidationFlags.None;
switch (character.ToString().ToUpper())
{
case "I":
validationFlags = InputMaskValidationFlags.AllowInteger;
break;
case "D":
validationFlags = InputMaskValidationFlags.AllowDecimal;
break;
case "A":
validationFlags = InputMaskValidationFlags.AllowAlphabet;
break;
case "W":
validationFlags = (InputMaskValidationFlags.AllowAlphanumeric);
break;
}
return (validationFlags != InputMaskValidationFlags.None);
}
/// <summary>
/// Invoked when the coerce value callback is invoked.
/// </summary>
/// <param name="obj"></param>
/// <param name="e"></param>
private static object Text_CoerceValue(DependencyObject obj, object value)
{
MaskedTextBox mtb = (MaskedTextBox)obj;
if (value == null || value.Equals(string.Empty))
value = mtb.GetDefaultText();
else if (value.ToString().Length > 0)
{
string displayText;
mtb.ValidateTextInternal(value.ToString(), out displayText);
value = displayText;
}
return value;
}
/// <summary>
/// Invoked when the InputMask dependency property reports a change.
/// </summary>
/// <param name="obj"></param>
/// <param name="e"></param>
private static void InputMask_Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as MaskedTextBox).UpdateInputMask();
}
/// <summary>
/// Invokes when a paste event is raised.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MaskedTextBox_Paste(object sender, DataObjectPastingEventArgs e)
{
//TODO: play nicely here?
//
if (e.DataObject.GetDataPresent(typeof(string)))
{
string value = e.DataObject.GetData(typeof(string)).ToString();
string displayText;
if (this.ValidateTextInternal(value, out displayText))
{
this.Text = displayText;
}
}
e.CancelCommand();
}
/// <summary>
/// Rebuilds the InputMaskChars collection when the input mask property is updated.
/// </summary>
private void UpdateInputMask()
{
string text = this.Text;
this._maskChars.Clear();
this.Text = string.Empty;
string mask = this.InputMask;
if (string.IsNullOrEmpty(mask))
return;
InputMaskValidationFlags validationFlags = InputMaskValidationFlags.None;
for (int i = 0; i < mask.Length; i++)
{
bool isPlaceholder = this.IsPlaceholderChar(mask[i], out validationFlags);
if (isPlaceholder)
{
this._maskChars.Add(new InputMaskChar(validationFlags));
}
else
{
this._maskChars.Add(new InputMaskChar(mask[i]));
}
}
string displayText;
if (text.Length > 0 && this.ValidateTextInternal(text, out displayText))
{
this.Text = displayText;
}
else
{
this.Text = this.GetDefaultText();
}
}
/// <summary>
/// Validates the specified character against its input mask validation scheme.
/// </summary>
/// <param name="input"></param>
/// <param name="validationType"></param>
/// <returns></returns>
private bool ValidateCharInternal(char input, InputMaskValidationFlags validationType)
{
bool valid = false;
switch (validationType)
{
case InputMaskValidationFlags.AllowInteger:
case InputMaskValidationFlags.AllowDecimal:
int i;
if (validationType == InputMaskValidationFlags.AllowDecimal &&
input == '.' && !this.Text.Contains('.'))
{
valid = true;
}
else
{
valid = int.TryParse(input.ToString(), out i);
}
break;
case InputMaskValidationFlags.AllowAlphabet:
valid = char.IsLetter(input);
break;
case InputMaskValidationFlags.AllowAlphanumeric:
valid = (char.IsLetter(input) || char.IsNumber(input));
break;
}
return valid;
}
/// <summary>
/// Builds the default display text for the control.
/// </summary>
/// <returns></returns>
private string GetDefaultText()
{
StringBuilder text = new StringBuilder();
foreach (InputMaskChar maskChar in this._maskChars)
{
text.Append(maskChar.GetDefaultChar());
}
return text.ToString();
}
/// <summary>
/// Moves the caret forward to the next non-literal position.
/// </summary>
private void MoveForward()
{
int pos = this._caretIndex;
while (pos < this._maskChars.Count)
{
if (++pos == this._maskChars.Count || !this._maskChars[pos].IsLiteral())
{
this._caretIndex = this.CaretIndex = pos;
break;
}
}
}
/// <summary>
/// Moves the caret backward to the previous non-literal position.
/// </summary>
private void MoveBack()
{
int pos = this._caretIndex;
while (pos > 0)
{
if (--pos == 0 || !this._maskChars[pos].IsLiteral())
{
this._caretIndex = this.CaretIndex = pos;
break;
}
}
}
}
}
下面是一个例子。。使用了上面的用户控件:
XMAL代码:
<Window x:Class="TestApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:MaskedTextBox.Control;assembly=MaskedTextBox.Control"
Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ctrl:MaskedTextBox Name="txt" InputMask="ii:ii" TextChanged="txt_TextChanged"></ctrl:MaskedTextBox>
<ctrl:MaskedTextBox Name="txt1" Grid.Row="1" InputMask="ii:ii" Text="{Binding Path=Time}" TextChanged="txt_TextChanged"></ctrl:MaskedTextBox>
<Border Grid.Row="2" Margin="115.69,30.167,98,30.167" BorderBrush="Black">
<TextBlock Name="txbStatus" HorizontalAlignment="Center" Foreground="Red" Height="18"></TextBlock>
</Border>
</Grid>
</Window>
后台代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TestApp
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
Booking _booking;
public Window1()
{
InitializeComponent();
//create a booking instance with a time of 12:00
this._booking = new Booking();
this._booking.Time = "12:00";
}
private void txt_TextChanged(object sender, TextChangedEventArgs e)
{
if (!this.IsLoaded) return;
bool valid = this.txt.IsTextValid();
this.txbStatus.Text = valid ? "VALID" : "INVALID";
this.txbStatus.Foreground = valid ? Brushes.Green : Brushes.Red;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//set the datacontext of the masked textbox
this.txt1.DataContext = this._booking;
}
}
}
。。。另外有一个Booking类文件,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestApp
{
public class Booking
{
public string Time
{
get;
set;
}
}
}
结果如下: