Winforms MaskedTextBox - Reformatting pasted text to match mask - c#

I have a MaskedTextBox control that, in our case, is collecting social insurance (tax) numbers (without a ValidatingType though since the string representation including the mask literals). A social insurance number is 3 groups of 3 digits separated by dashes. Sometimes spaces may be typed or entered instead of the dashes.
The configuration of the textbox is:
Mask: 999-999-999
ValidationType: null / not required
SkipLiterals: true
CutCopyMaskFormat: IncludeLiterals (only relevant when cut/copy FROM textbox)
TextMaskFormat: IncludeLiterals
-- Let me know if there other properties that you think could be important!
Problem
When pasting the following tax number "450 622 097" because of the spaces it doesn't match the mask. So I end up with "450- 62-2 9" in the text box. Pasting "450-622-097" will successfully paste into the box.
I want to be able to intercept the paste event in order to possibly fix it up to replace the spaces with dashes.
Alternatively, could we make the mask accept dashes OR spaces (but always output dashes)?
Non-solutions
MaskInputRejected event - I can't seem to get a handle on what was originally input (i.e. what's being rejected) so as to compare it with what's sitting at the top of the Clipboard. It merely returns how it was rejected
Validating event - Already occurs after the mask has been applied. I.e. the value of "450- 62-2 9" is in the textbox now.
Use custom ValidatingType with static Parse function - Again, occurs after the mask has been applied.
Detecting Key-Down event - Then if key series is Ctrl-V then manually handle and pass in a cleaned up version of the clipboard text. Could work, but then what about paste via the right click context menu?
Any other ideas?

While this is a hammer solution, there are limitations to the mask string and i don't see another way around it. What you need is to capture the paste event and process the text before it gets in the textbox. See below a simplistic example
class MyMaskedTextbox : MaskedTextBox
{
const int WM_PASTE = 0x0302;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_PASTE:
if (Clipboard.ContainsText())
{
string text = Clipboard.GetText();
text = text.Replace(' ', '-');
//put your processing here
Clipboard.SetText(text);
}
break;
}
base.WndProc(ref m);
}
}

As per #anchandra's response and subsequent comments here is the class to enable processing of the text on a per-control basis.
public class MyMaskedTextBox : MaskedTextBox
{
private const int WM_PASTE = 0x0302;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_PASTE:
if (Clipboard.ContainsText())
{
string text = Clipboard.GetText();
var args = OnPasting(text);
if (args.Cancel)
{
// Swallow it up!
return;
}
// If value changed, then change what we'll paste from the top of the clipboard
if (!args.Value.Equals(text, StringComparison.CurrentCulture))
{
Clipboard.SetText(args.Value);
}
}
break;
}
base.WndProc(ref m);
}
public event EventHandler<PastingEventArgs> Pasting;
protected virtual PastingEventArgs OnPasting(string value)
{
var handler = Pasting;
var args = new PastingEventArgs(value);
if (handler != null)
{
handler(this, args);
}
return args;
}
}
public class PastingEventArgs : CancelEventArgs
{
public string Value { get; set; }
public PastingEventArgs(string value)
{
Value = value;
}
}
And simple usage of the Pasting event to strip out spaces as per:
private void sinTextBox_Pasting(object sender, PastingEventArgs e)
{
e.Value = e.Value.Replace(" ", String.Empty);
}

Related

How to disable / cancel undo button in xamarin

I am creating an application that has an entry. I am trying to restrict the entry to only allow for numeric input. I have already tried using Keyboard = "Numeric". For the iPad, however, the keyboard has more characters than just numbers. So I had to restrict what is entered. When I do this however, if the user types in a parenthesis, for example, it does stop the character from being entered. But then if the user presses undo, it crashes. I assume this is because the software keyboard is separate from the app, so it is looking for that parenthesis character, but it isn't there. Here is my code:
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
Entry theEntry = (Entry)sender;
string entryText = theEntry.Text;
if(entryText != null && entryText.Length != 0)
{
char theMostRecentInput = entryText[entryText.Length - 1];
if(!Extension.IsNumeric(theMostRecentInput))
{
theEntry.TextChanged -= Entry_TextChanged;
theEntry.Text = e.OldTextValue;
theEntry.TextChanged += Entry_TextChanged;
}
}
}
Thanks for the help!
This issue will occur when Validation like special character, Max Limit, etc... for Input field are implemented.
By that time undo action will have extra character count than current input field text characters length in iOS ShouldChangeCharacters Delegate. This leads to app crash.
One of the solution is to return as false in such scenario instead of disabling undo buttons. Below solution worked for me.
public class ExtEntryRenderer : EntryRenderer
{
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control != null)
{
Control.ShouldChangeCharacters += (UITextField textField, NSRange range, string replacementString) =>
{
if (range.Location + range.Length > ((UITextField)textField).Text.Length)
return false;
return true;
};
}
}
}
I would do this in a Entry custom renderer that way you you can control the input via ShouldChangeCharacters and not have to kludge it by allowing the input and then having to remove the handler and change the text back to the old value...
Here is a quick example that allows numeric, It also automatically handles clipboard pasting non-numeric strings as those would be disallowed. I am using the NSCharacterSet.DecimalDigits character set as that would be internationalized by the OS, but you could allow/disallow any chars of your choosing.
You could also include haptic, visual or audio feedback on the disallowed/rejected entries...
[assembly: ExportRenderer(typeof(NumericEntry), typeof(NumericEntryRenderer))]
namespace Forms_PCL_Tester.iOS
{
public class NumericEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement != e.OldElement)
if (Control != null)
{
Control.KeyboardType = UIKeyboardType.NumbersAndPunctuation;
Control.ShouldChangeCharacters += (UITextField textField, NSRange range, string replacementString) =>
{
foreach (var aChar in replacementString)
if (!NSCharacterSet.DecimalDigits.Contains(aChar))
return false;
return true;
};
}
}
}
}

How to paste a serial key automatically [duplicate]

I have a .net application which includes search screen which has a panel with has three text boxes, each with a varying character lengths.
What I'd like to do is capture when the paste command when invoked from the first box and paste my clipboard into the three boxes.
This functionality is similar to many modern applications accepting input for serial keys and phone numbers.
As far as I can find there is no other sensible way of doing this than to capture the WM_PASTE event.
Derive a class from TexBox and implement this method:
using System.Windows.Forms;
using System.ComponentModel;
class TextBoxWithOnPaste : TextBox
{
public delegate void PastedEventHandler();
[Category("Action")]
[Description("Fires when text from the clipboard is pasted.")]
public event PastedEventHandler OnPaste;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x302 && OnPaste != null) // process WM_PASTE only if the event has been subscribed to
{
OnPaste();
}
else
{
base.WndProc(ref m);
}
}
}
Then put three of those custom controls on your form, and assign the OnPaste event on all three textboxes to the same method, in this case I called it textPasted():
private void textPasted()
{
String input = Clipboard.GetText();
int l1 = textBoxWithOnPaste1.MaxLength;
int l2 = textBoxWithOnPaste2.MaxLength;
int l3 = textBoxWithOnPaste3.MaxLength;
try
{
textBoxWithOnPaste1.Text = input.Substring(0, l1);
textBoxWithOnPaste2.Text = input.Substring(l1, l2);
textBoxWithOnPaste3.Text = input.Substring(l2, l3);
}
catch (Exception)
{ }
}
Since you implied "like a serial", I guessed you want the pasted string to be split among the textboxes. The code above is not perfect for that (try pasting a single space into the third text box after entering data manually in all three, so it would be nice if you knew in which textbox the text was pasted, for example by altering the event's parameters and that way sending the sender with it), but it basically works and I guess you can figure out the rest (you could use the Tag property to identify the textbox).
Capture the paste event:
protected override void WndProc(ref Message m) {
// Did paste occur?
if (m.Msg == 0x302) {
//Paste occurred, add your logic here
}
base.WndProc(ref m);
}
Then, access the Clipboard object to get the desired text.
you can get the captured text
String txt = Clipboard.GetText();
and place it in the "Text" property of the other textbox
You can bind key down event, and when you get Ctrl + V or Ctrl + v, you update the value of the three textbox with value in the clipboad. You can do this on TextChanged event off first text box.
You could increase the character limit of the boxes and register for TextChanged and if the pasted (or typed) text is longer jump/cut to the next TextBox.

Pasting into multiple text boxes

I have a .net application which includes search screen which has a panel with has three text boxes, each with a varying character lengths.
What I'd like to do is capture when the paste command when invoked from the first box and paste my clipboard into the three boxes.
This functionality is similar to many modern applications accepting input for serial keys and phone numbers.
As far as I can find there is no other sensible way of doing this than to capture the WM_PASTE event.
Derive a class from TexBox and implement this method:
using System.Windows.Forms;
using System.ComponentModel;
class TextBoxWithOnPaste : TextBox
{
public delegate void PastedEventHandler();
[Category("Action")]
[Description("Fires when text from the clipboard is pasted.")]
public event PastedEventHandler OnPaste;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x302 && OnPaste != null) // process WM_PASTE only if the event has been subscribed to
{
OnPaste();
}
else
{
base.WndProc(ref m);
}
}
}
Then put three of those custom controls on your form, and assign the OnPaste event on all three textboxes to the same method, in this case I called it textPasted():
private void textPasted()
{
String input = Clipboard.GetText();
int l1 = textBoxWithOnPaste1.MaxLength;
int l2 = textBoxWithOnPaste2.MaxLength;
int l3 = textBoxWithOnPaste3.MaxLength;
try
{
textBoxWithOnPaste1.Text = input.Substring(0, l1);
textBoxWithOnPaste2.Text = input.Substring(l1, l2);
textBoxWithOnPaste3.Text = input.Substring(l2, l3);
}
catch (Exception)
{ }
}
Since you implied "like a serial", I guessed you want the pasted string to be split among the textboxes. The code above is not perfect for that (try pasting a single space into the third text box after entering data manually in all three, so it would be nice if you knew in which textbox the text was pasted, for example by altering the event's parameters and that way sending the sender with it), but it basically works and I guess you can figure out the rest (you could use the Tag property to identify the textbox).
Capture the paste event:
protected override void WndProc(ref Message m) {
// Did paste occur?
if (m.Msg == 0x302) {
//Paste occurred, add your logic here
}
base.WndProc(ref m);
}
Then, access the Clipboard object to get the desired text.
you can get the captured text
String txt = Clipboard.GetText();
and place it in the "Text" property of the other textbox
You can bind key down event, and when you get Ctrl + V or Ctrl + v, you update the value of the three textbox with value in the clipboad. You can do this on TextChanged event off first text box.
You could increase the character limit of the boxes and register for TextChanged and if the pasted (or typed) text is longer jump/cut to the next TextBox.

Is there any C# implementation or way to have a textbox that takes a key sequence as input?

I have an implementation which hooks into the keydown event, which suppresses the keypress event and does some magic. Now I want to show something more friendly than "ControlKey" etc, so there's a switch you can see in the code below. Except I've realised things like the number keys along the top end up as D1, D2, etc., and then there's things like Add showing up for the numpad +. In addition, Print Screen doesn't seem to be recognised.
Is there something I'm missing?
This is a hard question for me to describe fluently, but hopefully you get what I mean. If not I guess I'll be replying to comments and improving this as I go.
private int numKeys = 0;
private List<int> shortcutKeys = new List<int>();
private void textBoxRegionKeys_Click(object sender, EventArgs e)
{
textBoxRegionKeys.SelectAll();
}
private void textBoxRegionKeys_KeyDown(object sender, KeyEventArgs e)
{
// There are certain keys we want to ignore...
if (e.KeyCode != Keys.Delete && e.KeyCode != Keys.Back)
{
// We can handle this ourselves, thanks
e.SuppressKeyPress = true;
// Shortern what we show
string ret = e.KeyCode.ToString();
switch (ret)
{
case "ControlKey": ret = "Ctrl"; break;
case "ShiftKey": ret = "Shift"; break;
case "Menu": ret = "Alt"; break;
}
// If we haven't selected anything, we should be appending
if (textBoxRegionKeys.SelectionLength == 0)
{
if (numKeys > 0)
{
// Does our key already exist in the list?
if (shortcutKeys.Exists(x => x == e.KeyValue))
{
return;
}
textBoxRegionKeys.Text += " + ";
}
textBoxRegionKeys.Text += ret;
shortcutKeys.Add(e.KeyValue);
numKeys++;
}
else
{
textBoxRegionKeys.Text = ret;
shortcutKeys.Clear();
shortcutKeys.Add(e.KeyValue);
numKeys = 1;
}
}
}
The TextBox KeyDown/KeyPress etc will only be raised for keys that may be accepted as input in to the text box (and associated modifiers). As such, you will not see keys handled such as Print Screen etc. The best option I can think of is not ideal, but you could override the ProcessKeyPreview or some other Form level Message interceptor to get notified of ANY key press. Something like...
protected override bool ProcessKeyPreview(ref Message m)
{
var keyCode = (Keys)Enum.ToObject(typeof (Keys), m.WParam);
//Insert some logic
return base.ProcessKeyPreview(ref m);
}
Of course, this method will be invoked whenever the FORM has focus, and a key is pressed, so you would have to filter down by doing some form of check (which again is not ideal)
if(ReferenceEquals(ActiveControl, textBoxRegionKeys)) {}
Which if your dealing with things like UserControls will be very unreliable.
As for formatting in to nice friendly messages, I think you basically you will need your own map of special characters... I am not aware of any Globalized lookup for Keys. I will dig a little and update the answer if I find something.
Edit
Did a little digging and couldn't find anything obvious for nice key mappings. I would just create a map of "friendly" key names:
private static readonly Dictionary<Keys, String> KeysMap = new Dictionary<Keys, String>
{
{ Keys.D1, "1"},
{ Keys.D9, "9"}
};
And do something like:
String friendlyKeyCode;
if (!KeysMap.TryGetValue(keyCode, out friendlyKeyCode))
friendlyKeyCode = keyCode.ToString();
Personally, I find this approach better than a massive switch... but that works too.

Looking for a simple C# numeric edit control

I am a MFC programmer who is new to C# and am looking for a simple control that will allow number entry and range validation.
Look at the "NumericUpDown" control. It has range validation, the input will always be numeric, and it has those nifty increment/decrement buttons.
I had to implement a Control which only accepted numbers, integers or reals.
I build the control as a specialization of (read: derived from) TextBox control, and using input control and a regular expresión for the validation.
Adding range validation is terribly easy.
This is the code for building the regex. _numericSeparation is a string with characters accepted as decimal comma values
(for example, a '.' or a ',': $10.50 10,50€
private string ComputeRegexPattern()
{
StringBuilder builder = new StringBuilder();
if (this._forcePositives)
{
builder.Append("([+]|[-])?");
}
builder.Append(#"[\d]*((");
if (!this._useIntegers)
{
for (int i = 0; i < this._numericSeparator.Length; i++)
{
builder.Append("[").Append(this._numericSeparator[i]).Append("]");
if ((this._numericSeparator.Length > 0) && (i != (this._numericSeparator.Length - 1)))
{
builder.Append("|");
}
}
}
builder.Append(#")[\d]*)?");
return builder.ToString();
}
The regular expression matches any number (i.e. any string with numeric characters) with only one character as a numeric separation, and a '+' or a '-' optional character at the beginning of the string.
Once you create the regex (when instanciating the Control), you check if the value is correct overriding the OnValidating method.
CheckValidNumber() just applies the Regex to the introduced text. If the regex match fails, activates an error provider with an specified error (set with ValidationError public property) and raises a ValidationError event.
Here you could do the verification to know if the number is in the requiered range.
private bool CheckValidNumber()
{
if (Regex.Match(this.Text, this.RegexPattern).Value != this.Text)
{
this._errorProvider.SetError(this, this.ValidationError);
return false;
}
this._errorProvider.Clear();
return true;
}
protected override void OnValidating(CancelEventArgs e)
{
bool flag = this.CheckValidNumber();
if (!flag)
{
e.Cancel = true;
this.Text = "0";
}
base.OnValidating(e);
if (!flag)
{
this.ValidationFail(this, EventArgs.Empty);
}
}
As I said, i also prevent the user from input data in the text box other than numeric characteres overriding the OnKeyPress methdod:
protected override void OnKeyPress(KeyPressEventArgs e)
{
if ((!char.IsDigit(e.KeyChar) && !char.IsControl(e.KeyChar)) && (!this._numberSymbols.Contains(e.KeyChar.ToString()) && !this._numericSeparator.Contains(e.KeyChar.ToString())))
{
e.Handled = true;
}
if (this._numberSymbols.Contains(e.KeyChar.ToString()) && !this._forcePositives)
{
e.Handled = true;
}
if (this._numericSeparator.Contains(e.KeyChar.ToString()) && this._useIntegers)
{
e.Handled = true;
}
base.OnKeyPress(e);
}
The elegant touch: I check if the number valid every time the user releases a key, so the user can get feedback as he/she types. (But remember that you must be carefull with the ValidationFail event ;))
protected override void OnKeyUp(KeyEventArgs e)
{
this.CheckValidNumber();
base.OnKeyUp(e);
}
You can use a regular textbox and a Validator control to control input.
Try using an error provider control to validate the textbox. You can use int.TryParse() or double.TryParse() to check if it's numeric and then validate the range.
You can use a combination of the RequiredFieldValidator and CompareValidator (Set to DataTypeCheck for the operator and Type set to Integer)
That will get it with a normal textbox if you would like, otherwise the recommendation above is good.

Categories