|
//------------------------------------------------------------------------------
// <copyright file="TextBox.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Windows.Forms
{
using System;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Globalization;
using System.Security;
using System.Security.Permissions;
using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms.Layout;
using System.Windows.Forms.VisualStyles;
/// <devdoc>
/// MaskedTextBox control definition class.
/// Uses the services from the System.ComponentModel.MaskedTextBoxProvider class.
/// See spec at http://dotnetclient/whidbey/Specs/MaskEdit.doc
/// </devdoc>
[
ComVisible(true),
ClassInterface(ClassInterfaceType.AutoDispatch),
DefaultEvent("MaskInputRejected"),
DefaultBindingProperty("Text"),
DefaultProperty("Mask"),
Designer("System.Windows.Forms.Design.MaskedTextBoxDesigner, " + AssemblyRef.SystemDesign),
SRDescription(SR.DescriptionMaskedTextBox)
]
public class MaskedTextBox : TextBoxBase
{
// Consider: The MaskedTextBox control, when initialized with a non-null/empty mask, processes all
// WM_CHAR messages and always sets the text using the SetWindowText Windows function in the furthest base
// class. This means that the underlying Edit control won't enable Undo operations and the context
// menu behavior will be a bit different (for instance Copy option is enabled when PasswordChar is set).
// To provide Undo functionality and make the context menu behave like the Edit control, we would have
// to implement our own. See http://msdn.microsoft.com/msdnmag/issues/1100/c/default.aspx for more info
// about how to do this. See postponed bug VSWhidbey#218402.
private const bool forward = true;
private const bool backward = false;
private const string nullMask = "<>"; // any char/str is OK here.
private static readonly object EVENT_MASKINPUTREJECTED = new object();
private static readonly object EVENT_VALIDATIONCOMPLETED = new object();
private static readonly object EVENT_TEXTALIGNCHANGED = new object();
private static readonly object EVENT_ISOVERWRITEMODECHANGED = new object();
private static readonly object EVENT_MASKCHANGED = new object();
// The native edit control's default password char (per thread). See corresponding property for more info.
private static char systemPwdChar;
// Values to track changes in IME composition string (if any). Having const variables is a bit more efficient
// than having an enum (which creates a class).
private const byte imeConvertionNone = 0; // no convertion has been performed in the composition string.
private const byte imeConvertionUpdate = 1; // the char being composed has been updated but not coverted yet.
private const byte imeConvertionCompleted = 2; // the char being composed has been fully converted.
///////// Instance fields
// Used for keeping selection when prompt is hidden on leave (text changes).
private int lastSelLength;
// Used for caret positioning.
private int caretTestPos;
// Bit mask - Determines when the Korean IME composition string is completed so converted character can be processed.
private static int IME_ENDING_COMPOSITION = BitVector32.CreateMask();
// Bit mask - Determines when the Korean IME is completing a composition, used when forcing convertion.
private static int IME_COMPLETING = BitVector32.CreateMask(IME_ENDING_COMPOSITION);
// Used for handling characters that have a modifier (Ctrl-A, Shift-Del...).
private static int HANDLE_KEY_PRESS = BitVector32.CreateMask(IME_COMPLETING);
// Bit mask - Used to simulate a null mask. Needed since a MaskedTextProvider object cannot be
// initialized with a null mask but we need one even in this case as a backend for
// default properties. This is to support creating a MaskedTextBox with the default
// constructor, specially at design time.
private static int IS_NULL_MASK = BitVector32.CreateMask(HANDLE_KEY_PRESS);
// Bit mask - Used in conjuction with get_Text to return the text that is actually set in the native
// control. This is required to be able to measure text correctly (GetPreferredSize) and
// to compare against during set_Text (to bail if the same and not to raise TextChanged event).
private static int QUERY_BASE_TEXT = BitVector32.CreateMask(IS_NULL_MASK);
// If true, the input text is rejected whenever a character does not comply with the mask; a MaskInputRejected
// event is fired for the failing character.
// If false, characters in the input string are processed one by one accepting the ones that comply
// with the mask and raising the MaskInputRejected event for the rejected ones.
private static int REJECT_INPUT_ON_FIRST_FAILURE = BitVector32.CreateMask( QUERY_BASE_TEXT );
// Bit masks for boolean properties.
private static int HIDE_PROMPT_ON_LEAVE = BitVector32.CreateMask(REJECT_INPUT_ON_FIRST_FAILURE);
private static int BEEP_ON_ERROR = BitVector32.CreateMask(HIDE_PROMPT_ON_LEAVE);
private static int USE_SYSTEM_PASSWORD_CHAR = BitVector32.CreateMask(BEEP_ON_ERROR);
private static int INSERT_TOGGLED = BitVector32.CreateMask(USE_SYSTEM_PASSWORD_CHAR);
private static int CUTCOPYINCLUDEPROMPT = BitVector32.CreateMask(INSERT_TOGGLED);
private static int CUTCOPYINCLUDELITERALS = BitVector32.CreateMask(CUTCOPYINCLUDEPROMPT);
///////// Properties backend fields. See corresponding property comments for more info.
private char passwordChar; // control's pwd char, it could be different from the one displayed if using system password.
private Type validatingType;
private IFormatProvider formatProvider;
private MaskedTextProvider maskedTextProvider;
private InsertKeyMode insertMode;
private HorizontalAlignment textAlign;
// Bit vector to represent bool variables.
private BitVector32 flagState;
/// <devdoc>
/// Constructs the MaskedTextBox with the specified MaskedTextProvider object.
/// </devdoc>
public MaskedTextBox()
{
MaskedTextProvider maskedTextProvider = new MaskedTextProvider(nullMask, CultureInfo.CurrentCulture);
this.flagState[IS_NULL_MASK] = true;
Initialize(maskedTextProvider);
}
/// <devdoc>
/// Constructs the MaskedTextBox with the specified MaskedTextProvider object.
/// </devdoc>
public MaskedTextBox(string mask)
{
if (mask == null)
{
throw new ArgumentNullException();
}
MaskedTextProvider maskedTextProvider = new MaskedTextProvider(mask, CultureInfo.CurrentCulture);
this.flagState[IS_NULL_MASK] = false;
Initialize(maskedTextProvider);
}
/// <devdoc>
/// Constructs the MaskedTextBox with the specified MaskedTextProvider object.
/// </devdoc>
public MaskedTextBox(MaskedTextProvider maskedTextProvider)
{
if (maskedTextProvider == null)
{
throw new ArgumentNullException();
}
this.flagState[IS_NULL_MASK] = false;
Initialize(maskedTextProvider);
}
/// <devdoc>
/// Initializes the object with the specified MaskedTextProvider object and default
/// property values.
/// </devdoc>
private void Initialize(MaskedTextProvider maskedTextProvider)
{
Debug.Assert(maskedTextProvider != null, "Initializing from a null MaskProvider ref.");
this.maskedTextProvider = maskedTextProvider;
// set the initial display text.
if (!this.flagState[IS_NULL_MASK])
{
SetWindowText();
}
// set default values.
this.passwordChar = this.maskedTextProvider.PasswordChar;
this.insertMode = InsertKeyMode.Default;
this.flagState[HIDE_PROMPT_ON_LEAVE ] = false;
this.flagState[BEEP_ON_ERROR ] = false;
this.flagState[USE_SYSTEM_PASSWORD_CHAR ] = false;
this.flagState[REJECT_INPUT_ON_FIRST_FAILURE] = false;
// CutCopyMaskFormat - set same defaults as TextMaskFormat (IncludePromptAndLiterals).
// It is a lot easier to handle this flags individually since that's the way the MaskedTextProvider does it.
this.flagState[CUTCOPYINCLUDEPROMPT ] = this.maskedTextProvider.IncludePrompt;
this.flagState[CUTCOPYINCLUDELITERALS ] = this.maskedTextProvider.IncludeLiterals;
// fields for internal use.
this.flagState[HANDLE_KEY_PRESS] = true;
this.caretTestPos = 0;
}
/////////////////// Properties
///
/// <devdoc>
/// Unsupported method/property.
/// </devdoc>
[
Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public new bool AcceptsTab
{
get { return false; }
set {}
}
/// <devdoc>
/// Specifies whether the prompt character should be treated as a valid input character or not.
/// The setter resets the underlying MaskedTextProvider object and attempts
/// to add the existing input text (if any) using the new mask, failure is ignored.
/// This property has no particular effect if no mask has been set.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxAllowPromptAsInputDescr),
DefaultValue(true)
]
public bool AllowPromptAsInput
{
get
{
return this.maskedTextProvider.AllowPromptAsInput;
}
set
{
if( value != this.maskedTextProvider.AllowPromptAsInput )
{
// Recreate masked text provider since this property is read-only.
MaskedTextProvider newProvider = new MaskedTextProvider(
this.maskedTextProvider.Mask,
this.maskedTextProvider.Culture,
value,
this.maskedTextProvider.PromptChar,
this.maskedTextProvider.PasswordChar,
this.maskedTextProvider.AsciiOnly );
SetMaskedTextProvider( newProvider );
}
}
}
/// <devdoc>
/// Unsupported method/property.
/// </devdoc>
[
Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public new event EventHandler AcceptsTabChanged
{
add { }
remove { }
}
/// <devdoc>
/// Specifies whether only ASCII characters are accepted as valid input.
/// This property has no particular effect if no mask has been set.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxAsciiOnlyDescr),
RefreshProperties(RefreshProperties.Repaint),
DefaultValue(false)
]
public bool AsciiOnly
{
get
{
return this.maskedTextProvider.AsciiOnly;
}
set
{
if( value != this.maskedTextProvider.AsciiOnly )
{
// Recreate masked text provider since this property is read-only.
MaskedTextProvider newProvider = new MaskedTextProvider(
this.maskedTextProvider.Mask,
this.maskedTextProvider.Culture,
this.maskedTextProvider.AllowPromptAsInput,
this.maskedTextProvider.PromptChar,
this.maskedTextProvider.PasswordChar,
value );
SetMaskedTextProvider( newProvider );
}
}
}
/// <devdoc>
/// Specifies whether to play a beep when the input is not valid according to the mask.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxBeepOnErrorDescr),
DefaultValue(false)
]
public bool BeepOnError
{
get
{
return this.flagState[BEEP_ON_ERROR];
}
set
{
this.flagState[BEEP_ON_ERROR] = value;
}
}
/// <devdoc>
/// Gets a value indicating whether the user can undo the previous operation in a text box control.
/// Unsupported method/property.
/// WndProc ignores EM_CANUNDO.
/// </devdoc>
[
Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public new bool CanUndo
{
get
{
return false;
}
}
/// <devdoc>
/// Returns the parameters needed to create the handle. Inheriting classes
/// can override this to provide extra functionality. They should not,
/// however, forget to call base.getCreateParams() first to get the struct
/// filled up with the basic info.
/// </devdoc>
protected override CreateParams CreateParams
{
[SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
get
{
CreateParams cp = base.CreateParams;
// Translate for Rtl if necessary
//
HorizontalAlignment align = RtlTranslateHorizontal(textAlign);
cp.ExStyle &= ~NativeMethods.WS_EX_RIGHT; // WS_EX_RIGHT overrides the ES_XXXX alignment styles
switch (align)
{
case HorizontalAlignment.Left:
cp.Style |= NativeMethods.ES_LEFT;
break;
case HorizontalAlignment.Center:
cp.Style |= NativeMethods.ES_CENTER;
break;
case HorizontalAlignment.Right:
cp.Style |= NativeMethods.ES_RIGHT;
break;
}
return cp;
}
}
/// <devdoc>
/// The culture that determines the value of the localizable mask language separators and placeholders.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxCultureDescr),
RefreshProperties(RefreshProperties.Repaint),
]
public CultureInfo Culture
{
get
{
return this.maskedTextProvider.Culture;
}
set
{
if( value == null )
{
throw new ArgumentNullException();
}
if( !this.maskedTextProvider.Culture.Equals(value) )
{
// Recreate masked text provider since this property is read-only.
MaskedTextProvider newProvider = new MaskedTextProvider(
this.maskedTextProvider.Mask,
value,
this.maskedTextProvider.AllowPromptAsInput,
this.maskedTextProvider.PromptChar,
this.maskedTextProvider.PasswordChar,
this.maskedTextProvider.AsciiOnly );
SetMaskedTextProvider( newProvider );
}
}
}
/// <devdoc>
/// Specifies the formatting options for text cut/copited to the clipboard (Whether the mask returned from the Text
/// property includes Literals and/or prompt characters).
/// When prompt characters are excluded, theyare returned as spaces in the string returned.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxCutCopyMaskFormat),
RefreshProperties(RefreshProperties.Repaint),
DefaultValue(MaskFormat.IncludeLiterals)
]
public MaskFormat CutCopyMaskFormat
{
get
{
if( this.flagState[CUTCOPYINCLUDEPROMPT] )
{
if( this.flagState[CUTCOPYINCLUDELITERALS] )
{
return MaskFormat.IncludePromptAndLiterals;
}
return MaskFormat.IncludePrompt;
}
if( this.flagState[CUTCOPYINCLUDELITERALS] )
{
return MaskFormat.IncludeLiterals;
}
return MaskFormat.ExcludePromptAndLiterals;
}
set
{
//valid values are 0x0 to 0x3
if (!ClientUtils.IsEnumValid(value, (int)value, (int)MaskFormat.ExcludePromptAndLiterals, (int)MaskFormat.IncludePromptAndLiterals))
{
throw new InvalidEnumArgumentException("value", (int)value, typeof(MaskFormat));
}
if( value == MaskFormat.IncludePrompt )
{
this.flagState[CUTCOPYINCLUDEPROMPT] = true;
this.flagState[CUTCOPYINCLUDELITERALS] = false;
}
else if( value == MaskFormat.IncludeLiterals )
{
this.flagState[CUTCOPYINCLUDEPROMPT] = false;
this.flagState[CUTCOPYINCLUDELITERALS] = true;
}
else // value == MaskFormat.IncludePromptAndLiterals || value == MaskFormat.ExcludePromptAndLiterals
{
bool include = value == MaskFormat.IncludePromptAndLiterals;
this.flagState[CUTCOPYINCLUDEPROMPT] = include;
this.flagState[CUTCOPYINCLUDELITERALS] = include;
}
}
}
/// <devdoc>
/// Specifies the IFormatProvider to be used when parsing the string to the ValidatingType.
/// </devdoc>
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public IFormatProvider FormatProvider
{
get
{
return this.formatProvider;
}
set
{
this.formatProvider = value;
}
}
/// <devdoc>
/// Specifies whether the PromptCharacter is displayed when the control loses focus.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxHidePromptOnLeaveDescr),
RefreshProperties(RefreshProperties.Repaint),
DefaultValue(false)
]
public bool HidePromptOnLeave
{
get
{
return this.flagState[HIDE_PROMPT_ON_LEAVE];
}
set
{
if( this.flagState[HIDE_PROMPT_ON_LEAVE] != value )
{
this.flagState[HIDE_PROMPT_ON_LEAVE] = value;
// If the control is not focused and there are available edit positions (mask not full) we need to
// update the displayed text.
if( !this.flagState[IS_NULL_MASK]&& !this.Focused && !this.MaskFull && !this.DesignMode )
{
SetWindowText();
}
}
}
}
/// <devdoc>
/// Specifies whether to include mask literal characters when formatting the text.
/// </devdoc>
private bool IncludeLiterals
{
get
{
return this.maskedTextProvider.IncludeLiterals;
}
set
{
this.maskedTextProvider.IncludeLiterals = value;
}
}
/// <devdoc>
/// Specifies whether to include the mask prompt character when formatting the text in places
/// where an edit char has not being assigned.
/// </devdoc>
private bool IncludePrompt
{
get
{
return this.maskedTextProvider.IncludePrompt;
}
set
{
this.maskedTextProvider.IncludePrompt = value;
}
}
/// <devdoc>
/// Specifies the text insertion mode of the text box. This can be used to simulated the Access masked text
/// control behavior where insertion is set to TextInsertionMode.AlwaysOverwrite
/// This property has no particular effect if no mask has been set.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxInsertKeyModeDescr),
DefaultValue(InsertKeyMode.Default)
]
public InsertKeyMode InsertKeyMode
{
get
{
return this.insertMode;
}
set
{
//valid values are 0x0 to 0x2
if (!ClientUtils.IsEnumValid(value, (int)value, (int)InsertKeyMode.Default, (int)InsertKeyMode.Overwrite))
{
throw new InvalidEnumArgumentException("value", (int)value, typeof(InsertKeyMode));
}
if (this.insertMode != value)
{
bool isOverwrite = this.IsOverwriteMode;
this.insertMode = value;
if (isOverwrite != this.IsOverwriteMode)
{
OnIsOverwriteModeChanged(EventArgs.Empty);
}
}
}
}
/// <devdoc>
/// Overridden to handle unsupported RETURN key.
/// </devdoc>
protected override bool IsInputKey(Keys keyData)
{
if ((keyData & Keys.KeyCode) == Keys.Return)
{
return false;
}
return base.IsInputKey(keyData);
}
/// <devdoc>
/// Specifies whether text insertion mode in 'on' or not.
/// </devdoc>
[
Browsable(false)
]
public bool IsOverwriteMode
{
get
{
if( this.flagState[IS_NULL_MASK])
{
return false; // EditBox always inserts.
}
switch (this.insertMode)
{
case InsertKeyMode.Overwrite:
return true;
case InsertKeyMode.Insert:
return false;
case InsertKeyMode.Default:
// Note that the insert key state should be per process and its initial state insert, this is the
// behavior of apps like WinWord, WordPad and VS; so we have to keep track of it and not query its
// system value.
//return Control.IsKeyLocked(Keys.Insert);
return this.flagState[INSERT_TOGGLED];
default:
Debug.Fail("Invalid InsertKeyMode. This code path should have never been executed.");
return false;
}
}
}
/// <devdoc>
/// Event to notify when the insert mode has changed. This is required for data binding.
/// </devdoc>
[
SRCategory(SR.CatPropertyChanged),
SRDescription(SR.MaskedTextBoxIsOverwriteModeChangedDescr)
]
public event EventHandler IsOverwriteModeChanged
{
add
{
Events.AddHandler(EVENT_ISOVERWRITEMODECHANGED, value);
}
remove
{
Events.RemoveHandler(EVENT_ISOVERWRITEMODECHANGED, value);
}
}
/// <devdoc>
/// Unsupported method/property.
/// </devdoc>
[
Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public new string[] Lines
{
get
{
string[] lines;
this.flagState[QUERY_BASE_TEXT] = true;
try
{
lines = base.Lines;
}
finally
{
this.flagState[QUERY_BASE_TEXT] = false;
}
return lines;
}
set {}
}
/// <devdoc>
/// The mask applied to this control. The setter resets the underlying MaskedTextProvider object and attempts
/// to add the existing input text (if any) using the new mask, failure is ignored.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxMaskDescr),
RefreshProperties(RefreshProperties.Repaint),
DefaultValue(""),
MergableProperty(false),
Localizable(true),
Editor("System.Windows.Forms.Design.MaskPropertyEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor))
]
public string Mask
{
get
{
return this.flagState[IS_NULL_MASK]? string.Empty : this.maskedTextProvider.Mask;
}
set
{
//
// We dont' do anything if:
// 1. IsNullOrEmpty( value )->[Reset control] && this.flagState[IS_NULL_MASK]==>Already Reset.
// 2. !IsNullOrEmpty( value )->[Set control] && !this.flagState[IS_NULL_MASK][control is set] && [value is the same]==>No need to update.
//
if( this.flagState[IS_NULL_MASK] == string.IsNullOrEmpty( value ) && (this.flagState[IS_NULL_MASK] || value == this.maskedTextProvider.Mask) )
{
return;
}
string text = null;
string newMask = value;
// We need to update the this.flagState[IS_NULL_MASK]field before raising any events (when setting the maskedTextProvider) so
// querying for properties from an event handler returns the right value (i.e: Text).
if( string.IsNullOrEmpty( value ) ) // Resetting the control, the native edit control will be in charge.
{
// Need to get the formatted & unformatted text before resetting the mask, they'll be used to determine whether we need to
// raise the TextChanged event.
string formattedText = TextOutput;
string unformattedText = this.maskedTextProvider.ToString(false, false);
this.flagState[IS_NULL_MASK] = true;
if( this.maskedTextProvider.IsPassword )
{
SetEditControlPasswordChar(this.maskedTextProvider.PasswordChar);
}
// Set the window text to the unformatted text before raising events. Also, TextChanged needs to be raised after MaskChanged so
// pass false to SetWindowText 'raiseTextChanged' param.
SetWindowText(unformattedText, false, false );
EventArgs e = EventArgs.Empty;
OnMaskChanged(e);
if( unformattedText != formattedText )
{
OnTextChanged(e);
}
newMask = nullMask;
}
else // Setting control to a new value.
{
foreach( char c in value )
{
if( !MaskedTextProvider.IsValidMaskChar( c ) )
{
// Same message as in SR.MaskedTextProviderMaskInvalidChar in System.txt
throw new ArgumentException( SR.GetString( SR.MaskedTextBoxMaskInvalidChar) );
}
}
if( this.flagState[IS_NULL_MASK] )
{
// If this.IsNullMask, we are setting the mask to a new value; in this case we need to get the text because
// the underlying MTP does not have it (used as a property backend only) and pass it to SetMaskedTextProvider
// method below to update the provider.
text = this.Text;
}
}
// Recreate masked text provider since this property is read-only.
MaskedTextProvider newProvider = new MaskedTextProvider(
newMask,
this.maskedTextProvider.Culture,
this.maskedTextProvider.AllowPromptAsInput,
this.maskedTextProvider.PromptChar,
this.maskedTextProvider.PasswordChar,
this.maskedTextProvider.AsciiOnly );
//text == null when setting to a different mask value or when resetting the mask to null.
//text != null only when setting the mask from null to some value.
SetMaskedTextProvider( newProvider, text );
}
}
/// <devdoc>
/// Event to notify when the mask has changed.
/// </devdoc>
[
SRCategory(SR.CatPropertyChanged),
SRDescription(SR.MaskedTextBoxMaskChangedDescr)
]
public event EventHandler MaskChanged
{
add
{
Events.AddHandler(EVENT_MASKCHANGED, value);
}
remove
{
Events.RemoveHandler(EVENT_MASKCHANGED, value);
}
}
/// <devdoc>
/// Specifies whether the test string required input positions, as specified by the mask, have
/// all been assigned.
/// </devdoc>
[
Browsable(false)
]
public bool MaskCompleted
{
get
{
return this.maskedTextProvider.MaskCompleted;
}
}
/// <devdoc>
/// Specifies whether all inputs (required and optional) have been provided into the mask successfully.
/// </devdoc>
[
Browsable(false)
]
public bool MaskFull
{
get
{
return this.maskedTextProvider.MaskFull;
}
}
/// <devdoc>
/// Returns a copy of the control's internal MaskedTextProvider. This is useful for user's to provide
/// cloning semantics for the control (we don't want to do it) w/o incurring in any perf penalty since
/// some of the properties require recreating the underlying provider when they are changed.
/// </devdoc>
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public MaskedTextProvider MaskedTextProvider
{
get
{
return this.flagState[IS_NULL_MASK] ? null : (MaskedTextProvider) this.maskedTextProvider.Clone();
}
}
/// <devdoc>
/// Event to notify when an input has been rejected according to the mask.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxMaskInputRejectedDescr)
]
public event MaskInputRejectedEventHandler MaskInputRejected
{
add
{
Events.AddHandler(EVENT_MASKINPUTREJECTED, value);
}
remove
{
Events.RemoveHandler(EVENT_MASKINPUTREJECTED, value);
}
}
/// <devdoc>
/// Unsupported method/property.
/// WndProc ignores EM_LIMITTEXT & this is a virtual method.
/// </devdoc>
[
Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public override int MaxLength
{
get{ return base.MaxLength; }
set{}
}
/// <devdoc>
/// Unsupported method/property.
/// virtual method.
/// </devdoc>
[
Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public override bool Multiline
{
get { return false; }
set {}
}
/// <devdoc>
/// Unsupported method/property.
/// </devdoc>
[
Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public new event EventHandler MultilineChanged
{
add { }
remove { }
}
/// <devdoc>
/// Specifies the character to be used in the formatted string in place of editable characters, if
/// set to any printable character, the text box becomes a password text box, to reset it use the null
/// character.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxPasswordCharDescr),
RefreshProperties(RefreshProperties.Repaint),
DefaultValue('\0') // This property is shadowed by MaskedTextBoxDesigner.
]
public char PasswordChar
{
get
{
// The password char could be the one set in the control or the system password char,
// in any case the maskedTextProvider has the correct one.
return this.maskedTextProvider.PasswordChar;
}
set
{
if( !MaskedTextProvider.IsValidPasswordChar(value) ) // null character accepted (resets value)
{
// Same message as in SR.MaskedTextProviderInvalidCharError.
throw new ArgumentException(SR.GetString(SR.MaskedTextBoxInvalidCharError) );
}
if( this.passwordChar != value )
{
if( value == this.maskedTextProvider.PromptChar )
{
// Prompt and password chars must be different.
throw new InvalidOperationException( SR.GetString(SR.MaskedTextBoxPasswordAndPromptCharError) );
}
this.passwordChar = value;
// UseSystemPasswordChar take precedence over PasswordChar...Let's check.
if (!this.UseSystemPasswordChar)
{
this.maskedTextProvider.PasswordChar = value;
if( this.flagState[IS_NULL_MASK])
{
SetEditControlPasswordChar(value);
}
else
{
SetWindowText();
}
VerifyImeRestrictedModeChanged();
}
}
}
}
/// <devdoc>
/// Determines if the control is in password protect mode.
/// </devdoc>
internal override bool PasswordProtect
{
get
{
if( this.maskedTextProvider != null ) // could be queried during object construction.
{
return this.maskedTextProvider.IsPassword;
}
return base.PasswordProtect;
}
}
/// <devdoc>
/// Specifies the prompt character to be used in the formatted string for unsupplied characters.
/// </devdoc>
[
SRCategory(SR.CatAppearance),
SRDescription(SR.MaskedTextBoxPromptCharDescr),
RefreshProperties(RefreshProperties.Repaint),
Localizable(true),
DefaultValue('_')
]
public char PromptChar
{
get
{
return this.maskedTextProvider.PromptChar;
}
set
{
if( !MaskedTextProvider.IsValidInputChar(value) )
{
// This message is the same as the one in SR.MaskedTextProviderInvalidCharError.
throw new ArgumentException(SR.GetString(SR.MaskedTextBoxInvalidCharError) );
}
if( this.maskedTextProvider.PromptChar != value )
{
// We need to check maskedTextProvider password char in case it is using the system password.
if( value == this.passwordChar || value == this.maskedTextProvider.PasswordChar )
{
// Prompt and password chars must be different.
throw new InvalidOperationException( SR.GetString(SR.MaskedTextBoxPasswordAndPromptCharError) );
}
// Recreate masked text provider to be consistent with AllowPromptAsInput - current text may have chars with same value as new prompt.
MaskedTextProvider newProvider = new MaskedTextProvider(
this.maskedTextProvider.Mask,
this.maskedTextProvider.Culture,
this.maskedTextProvider.AllowPromptAsInput,
value,
this.maskedTextProvider.PasswordChar,
this.maskedTextProvider.AsciiOnly );
SetMaskedTextProvider( newProvider );
}
}
}
/// <devdoc>
/// Overwrite base class' property.
/// </devdoc>
public new bool ReadOnly
{
get
{
return base.ReadOnly;
}
set
{
if (this.ReadOnly != value)
{
// if true, this disables IME in the base class.
base.ReadOnly = value;
if (!this.flagState[IS_NULL_MASK])
{
// Prompt will be hidden.
SetWindowText();
}
}
}
}
/// <devdoc>
/// Specifies whether to include the mask prompt character when formatting the text in places
/// where an edit char has not being assigned.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxRejectInputOnFirstFailureDescr),
DefaultValue(false)
]
public bool RejectInputOnFirstFailure
{
get
{
return this.flagState[REJECT_INPUT_ON_FIRST_FAILURE];
}
set
{
this.flagState[REJECT_INPUT_ON_FIRST_FAILURE] = value;
}
}
/// <devdoc>
/// Designe time support for resetting the Culture property.
/// </devdoc>
/* No longer needed since Culture has been removed from the property browser - Left here for documentation.
[EditorBrowsable(EditorBrowsableState.Never)]
private void ResetCulture()
{
this.Culture = CultureInfo.CurrentCulture;
}*/
/// <devdoc>
/// Specifies whether to reset and skip the current position if editable, when the input character
/// has the same value as the prompt. This property takes precedence over AllowPromptAsInput.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxResetOnPrompt),
DefaultValue(true)
]
public bool ResetOnPrompt
{
get
{
return this.maskedTextProvider.ResetOnPrompt;
}
set
{
this.maskedTextProvider.ResetOnPrompt = value;
}
}
/// <devdoc>
/// Specifies whether to reset and skip the current position if editable, when the input
/// is the space character.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxResetOnSpace),
DefaultValue(true)
]
public bool ResetOnSpace
{
get
{
return this.maskedTextProvider.ResetOnSpace;
}
set
{
this.maskedTextProvider.ResetOnSpace = value;
}
}
/// <devdoc>
/// Specifies whether to skip the current position if non-editable and the input character has
/// the same value as the literal at that position.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxSkipLiterals),
DefaultValue(true)
]
public bool SkipLiterals
{
get
{
return this.maskedTextProvider.SkipLiterals;
}
set
{
this.maskedTextProvider.SkipLiterals = value;
}
}
/// <devdoc>
/// The currently selected text (if any) in the control.
/// </devdoc>
public override string SelectedText
{
get
{
if( this.flagState[IS_NULL_MASK])
{
return base.SelectedText;
}
return GetSelectedText();
}
set
{
SetSelectedTextInternal(value, true);
}
}
internal override void SetSelectedTextInternal(string value, bool clearUndo)
{
if (this.flagState[IS_NULL_MASK])
{
base.SetSelectedTextInternal(value, true); // Operates as a regular text box base.
return;
}
PasteInt( value );
}
/// <devdoc>
/// Set the composition string as the result string.
/// </devdoc>
private void ImeComplete()
{
this.flagState[IME_COMPLETING] = true;
ImeNotify(NativeMethods.CPS_COMPLETE);
}
/// <devdoc>
/// Notifies the IMM about changes to the status of the IME input context.
/// </devdoc>
private void ImeNotify(int action)
{
HandleRef handle = new HandleRef(this, this.Handle);
IntPtr inputContext = UnsafeNativeMethods.ImmGetContext(handle);
if (inputContext != IntPtr.Zero)
{
try
{
UnsafeNativeMethods.ImmNotifyIME(new HandleRef(null, inputContext), NativeMethods.NI_COMPOSITIONSTR, action, 0);
}
finally
{
UnsafeNativeMethods.ImmReleaseContext(handle, new HandleRef(null, inputContext));
}
}
else
{
Debug.Fail("Could not get IME input context.");
}
}
/// <devdoc>
/// Sets the underlying edit control's password char to the one obtained from this.PasswordChar.
/// This is used when the control is passworded and this.flagState[IS_NULL_MASK].
/// </devdoc>
private void SetEditControlPasswordChar( char pwdChar )
{
if (this.IsHandleCreated)
{
// This message does not return a value.
SendMessage(NativeMethods.EM_SETPASSWORDCHAR, pwdChar, 0);
Invalidate();
}
}
/// <devdoc>
/// The value of the Edit control default password char.
/// </devdoc>
private char SystemPasswordChar
{
get
{
if (MaskedTextBox.systemPwdChar == '\0')
{
// This is the hard way to get the password char - left here for information.
// It is picked up from Comctl32.dll. If VisualStyles is enabled it will get the dot char.
/*
StringBuilder charVal = new StringBuilder(20); // it could be 0x0000000000009999 format.
bool foundRsc = false;
int IDS_PASSWORDCHAR = 0x1076; // %ntsdx%\shell\comctrl32\v6\rcids.h
// defined in en.rc as: IDS_PASSWORDCHAR "9679" // 0x25cf - Black Circle
IntSecurity.UnmanagedCode.Assert();
try
{
// The GetModuleHandle function returns a handle to a mapped module without incrementing its reference count.
// @"C:\windows\winsxs\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.10.0_x-ww_f7fb5805\comctl32.dll if VisulaStyles enabled.
IntPtr hModule = UnsafeNativeMethods.GetModuleHandle("comctl32.dll");
Debug.Assert(hModule != IntPtr.Zero, String.Format("Could not get a handle to comctl32.dll - Error: 0x{0:X8}", Marshal.GetLastWin32Error()));
foundRsc = UnsafeNativeMethods.LoadString(new HandleRef(null, hModule), IDS_PASSWORDCHAR, charVal, charVal.Capacity);
}
catch( Exception ex )
{
if( ClientUtils.IsSecurityOrCriticalException( ex ) )
{
throw;
}
}
finally
{
CodeAccessPermission.RevertAssert();
}
MaskedTextBox.systemPwdChar = foundRsc ? (char) int.Parse(charVal.ToString()) : MaskedTextProvider.DefaultPasswordChar;
*/
// We need to temporarily create an edit control to get the default password character.
// We cannot use this control because we would have to reset the native control's password char to use
// the defult one so we can get it; this would change the text displayed in the box (even for a short time)
// opening a sec hole.
TextBox txtBox = new TextBox();
txtBox.UseSystemPasswordChar = true; // this forces the creation of the control handle.
MaskedTextBox.systemPwdChar = txtBox.PasswordChar;
txtBox.Dispose();
}
return MaskedTextBox.systemPwdChar;
}
}
/// <devdoc>
/// The Text setter validates the input char by char, raising the MaskInputRejected event for invalid chars.
/// The Text getter returns the formatted text according to the IncludeLiterals and IncludePrompt properties.
/// </devdoc>
[
Editor("System.Windows.Forms.Design.MaskedTextBoxTextEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
SRCategory(SR.CatAppearance),
RefreshProperties(RefreshProperties.Repaint),
Bindable(true),
DefaultValue(""), // This property is shadowed by MaskedTextBoxDesigner.
Localizable(true)
]
public override string Text
{
get
{
if( this.flagState[IS_NULL_MASK] || this.flagState[QUERY_BASE_TEXT])
{
return base.Text;
}
return TextOutput;
}
set
{
if (this.flagState[IS_NULL_MASK])
{
base.Text = value;
return;
}
if (string.IsNullOrEmpty(value))
{
// reset the input text.
Delete(Keys.Delete, 0, this.maskedTextProvider.Length);
}
else
{
if( this.RejectInputOnFirstFailure )
{
MaskedTextResultHint hint;
string oldText = TextOutput;
if (this.maskedTextProvider.Set(value, out this.caretTestPos, out hint))
{
//if( hint == MaskedTextResultHint.Success || hint == MaskedTextResultHint.SideEffect )
if( TextOutput != oldText )
{
SetText();
}
this.SelectionStart = ++this.caretTestPos;
}
else
{
OnMaskInputRejected(new MaskInputRejectedEventArgs(this.caretTestPos, hint));
}
}
else
{
Replace(value, /*startPosition*/ 0, /*selectionLen*/ this.maskedTextProvider.Length);
}
}
}
}
/// <devdoc>
/// Returns the length of the displayed text. See VSW#502543.
/// </devdoc>
[Browsable( false )]
public override int TextLength
{
get
{
if( this.flagState[IS_NULL_MASK] )
{
return base.TextLength;
}
// In Win9x systems TextBoxBase.TextLength calls Text.Length directly and does not query the window for the actual text length.
// If TextMaskFormat is set to a anything different from IncludePromptAndLiterals or HidePromptOnLeave is true the return value
// may be incorrect because the Text property value and the display text may be different. We need to handle this here.
return GetFormattedDisplayString().Length;
}
}
/// <devdoc>
/// The formatted text, it is what the Text getter returns when a mask has been applied to the control.
/// The text format follows the IncludeLiterals and IncludePrompt properties (See MaskedTextProvider.ToString()).
/// </devdoc>
private string TextOutput
{
get
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
return this.maskedTextProvider.ToString();
}
}
/// <devdoc>
/// Gets or sets how text is aligned in the control.
/// Note: This code is duplicated in TextBox for simplicity.
/// </devdoc>
[
Localizable(true),
SRCategory(SR.CatAppearance),
DefaultValue(HorizontalAlignment.Left),
SRDescription(SR.TextBoxTextAlignDescr)
]
public HorizontalAlignment TextAlign
{
get
{
return textAlign;
}
set
{
if (textAlign != value)
{
//verify that 'value' is a valid enum type...
//valid values are 0x0 to 0x2
if (!ClientUtils.IsEnumValid(value, (int)value, (int)HorizontalAlignment.Left, (int)HorizontalAlignment.Center))
{
throw new InvalidEnumArgumentException("value", (int)value, typeof(HorizontalAlignment));
}
textAlign = value;
RecreateHandle();
OnTextAlignChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// Event to notify the text alignment has changed.
/// </devdoc>
[
SRCategory(SR.CatPropertyChanged),
SRDescription(SR.RadioButtonOnTextAlignChangedDescr)
]
public event EventHandler TextAlignChanged
{
add
{
Events.AddHandler(EVENT_TEXTALIGNCHANGED, value);
}
remove
{
Events.RemoveHandler(EVENT_TEXTALIGNCHANGED, value);
}
}
/// <devdoc>
/// Specifies the formatting options for text output (Whether the mask returned from the Text
/// property includes Literals and/or prompt characters).
/// When prompt characters are excluded, theyare returned as spaces in the string returned.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxTextMaskFormat),
RefreshProperties(RefreshProperties.Repaint),
DefaultValue(MaskFormat.IncludeLiterals)
]
public MaskFormat TextMaskFormat
{
get
{
if( this.IncludePrompt )
{
if( this.IncludeLiterals )
{
return MaskFormat.IncludePromptAndLiterals;
}
return MaskFormat.IncludePrompt;
}
if( this.IncludeLiterals )
{
return MaskFormat.IncludeLiterals;
}
return MaskFormat.ExcludePromptAndLiterals;
}
set
{
if( this.TextMaskFormat == value )
{
return;
}
//valid values are 0x0 to 0x3
if (!ClientUtils.IsEnumValid(value, (int)value, (int)MaskFormat.ExcludePromptAndLiterals, (int)MaskFormat.IncludePromptAndLiterals))
{
throw new InvalidEnumArgumentException("value", (int)value, typeof(MaskFormat));
}
// Changing the TextMaskFormat will likely change the 'output' text (Text getter value). Cache old value to
// verify it against the new value and raise OnTextChange if needed.
string oldText = this.flagState[IS_NULL_MASK] ? null : TextOutput;
if( value == MaskFormat.IncludePrompt )
{
this.IncludePrompt = true;
this.IncludeLiterals = false;
}
else if( value == MaskFormat.IncludeLiterals )
{
this.IncludePrompt = false;
this.IncludeLiterals = true;
}
else // value == MaskFormat.IncludePromptAndLiterals || value == MaskFormat.ExcludePromptAndLiterals
{
bool include = value == MaskFormat.IncludePromptAndLiterals;
this.IncludePrompt = include;
this.IncludeLiterals = include;
}
if( oldText != null && oldText != TextOutput )
{
OnTextChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// Provides some interesting information for the TextBox control in String form.
/// Returns the test string (no password, including literals and prompt).
/// </devdoc>
public override string ToString()
{
if( this.flagState[IS_NULL_MASK] )
{
return base.ToString();
}
// base.ToString will call Text, we want to always display prompt and literals.
bool includePrompt = this.IncludePrompt;
bool includeLits = this.IncludeLiterals;
string str;
try
{
this.IncludePrompt = this.IncludeLiterals = true;
str = base.ToString();
}
finally
{
this.IncludePrompt = includePrompt;
this.IncludeLiterals = includeLits;
}
return str;
}
/// <devdoc>
/// Event to notify when the validating object completes parsing the formatted text.
/// </devdoc>
[
SRCategory(SR.CatFocus),
SRDescription(SR.MaskedTextBoxTypeValidationCompletedDescr)
]
public event TypeValidationEventHandler TypeValidationCompleted
{
add
{
Events.AddHandler(EVENT_VALIDATIONCOMPLETED, value);
}
remove
{
Events.RemoveHandler(EVENT_VALIDATIONCOMPLETED, value);
}
}
/// <devdoc>
/// Indicates if the text in the edit control should appear as the default password character.
/// This property has precedence over the PasswordChar property.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.MaskedTextBoxUseSystemPasswordCharDescr),
RefreshProperties(RefreshProperties.Repaint),
DefaultValue(false)
]
public bool UseSystemPasswordChar
{
get
{
return this.flagState[USE_SYSTEM_PASSWORD_CHAR];
}
set
{
if (value != this.flagState[USE_SYSTEM_PASSWORD_CHAR])
{
if (value)
{
if( this.SystemPasswordChar == this.PromptChar )
{
// Prompt and password chars must be different.
throw new InvalidOperationException( SR.GetString(SR.MaskedTextBoxPasswordAndPromptCharError) );
}
this.maskedTextProvider.PasswordChar = this.SystemPasswordChar;
}
else
{
// this.passwordChar could be '\0', in which case we are resetting the display to show the input char.
this.maskedTextProvider.PasswordChar = this.passwordChar;
}
this.flagState[USE_SYSTEM_PASSWORD_CHAR] = value;
if( this.flagState[IS_NULL_MASK])
{
SetEditControlPasswordChar(this.maskedTextProvider.PasswordChar);
}
else
{
SetWindowText();
}
VerifyImeRestrictedModeChanged();
}
}
}
/// <devdoc>
/// Type of the object to be used to parse the text when the user leaves the control.
/// A ValidatingType object must implement a method with one fo the following signature:
/// public static Object Parse(string)
/// public static Object Parse(string, IFormatProvider)
/// See DateTime.Parse(...) for an example.
/// </devdoc>
[
Browsable(false),
DefaultValue(null)
]
public Type ValidatingType
{
get
{
return this.validatingType;
}
set
{
if( this.validatingType != value )
{
this.validatingType = value;
}
}
}
/// <devdoc>
/// Unsupported method/property.
/// </devdoc>
[
Browsable(false),
EditorBrowsable(EditorBrowsableState.Never),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public new bool WordWrap
{
get { return false; }
set {}
}
////////////// Methods
/// <devdoc>
/// Clears information about the most recent operation from the undo buffer of the control.
/// Unsupported property/method.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Never)
]
public new void ClearUndo()
{
}
/// <devdoc>
/// Creates a handle for this control. This method is called by the .NET Framework, this should
/// not be called. Inheriting classes should always call base.createHandle when overriding this method.
/// Overridden to be able to set the control text with the masked (passworded) value when recreating
/// handle, since the underlying native edit control is not aware of it.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Advanced),
UIPermission(SecurityAction.InheritanceDemand, Window = UIPermissionWindow.AllWindows)
]
protected override void CreateHandle()
{
if (!this.flagState[IS_NULL_MASK] && RecreatingHandle)
{
// update cached text value in Control. Don't preserve caret, cannot query for selection start at this time.
SetWindowText(GetFormattedDisplayString(), false, false);
}
base.CreateHandle();
}
///
/// <devdoc>
/// Deletes characters from the control's text according to the key pressed (Delete/Backspace).
/// Returns true if something gets actually deleted, false otherwise.
/// </devdoc>
private void Delete(Keys keyCode, int startPosition, int selectionLen)
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
Debug.Assert( keyCode == Keys.Delete || keyCode == Keys.Back, "Delete called with keyCode == " + keyCode.ToString() );
Debug.Assert( startPosition >= 0 && ((startPosition + selectionLen) <= this.maskedTextProvider.Length), "Invalid position range." );
// On backspace, moving the start postion back by one has the same effect as delete. If text is selected, there is no
// need for moving the position back.
this.caretTestPos = startPosition;
if( selectionLen == 0 )
{
if( keyCode == Keys.Back )
{
if( startPosition == 0 ) // At beginning of string, backspace does nothing.
{
return;
}
startPosition--; // so it can be treated as delete.
}
else // (keyCode == Keys.Delete)
{
if( (startPosition + selectionLen) == this.maskedTextProvider.Length ) // At end of string, delete does nothing.
{
return;
}
}
}
int tempPos;
int endPos = selectionLen > 0 ? startPosition + selectionLen - 1 : startPosition;
MaskedTextResultHint hint;
string oldText = TextOutput;
if (this.maskedTextProvider.RemoveAt(startPosition, endPos, out tempPos, out hint))
{
//if( hint == MaskedTextResultHint.Success || hint == MaskedTextResultHint.SideEffect) // Text was changed.
if( TextOutput != oldText )
{
SetText();
this.caretTestPos = startPosition;
}
else
{
// If succeeded but nothing removed, the caret should move as follows:
// 1. If selectionLen > 0, or on back and hint == SideEffect: move to selectionStart.
// 2. If hint == NoEffect, On Delete move to next edit position, if any or not already in one.
// On back move to the next edit postion at the left if no more assigned position at the right,
// in such case find an assigned position and move one past or one position left if no assigned pos found
// (taken care by 'startPosition--' above).
// 3. If hint == SideEffect, on Back move like arrow key, (startPosition is already moved, startPosition-- above).
if( selectionLen > 0 )
{
this.caretTestPos = startPosition;
}
else
{
if( hint == MaskedTextResultHint.NoEffect ) // Case 2.
{
if( keyCode == Keys.Delete )
{
this.caretTestPos = this.maskedTextProvider.FindEditPositionFrom(startPosition, forward);
}
else
{
if( this.maskedTextProvider.FindAssignedEditPositionFrom( startPosition, forward ) == MaskedTextProvider.InvalidIndex )
{
// No assigned position at the right, nothing to shift then move to the next assigned position at the
// left (if any).
this.caretTestPos = this.maskedTextProvider.FindAssignedEditPositionFrom(startPosition, backward);
}
else
{
// there are assigned positions at the right so move to an edit position at the left to get ready for
// removing the character on it or just shifting the characters at the right
this.caretTestPos = this.maskedTextProvider.FindEditPositionFrom(startPosition, backward);
}
if( this.caretTestPos != MaskedTextProvider.InvalidIndex )
{
this.caretTestPos++; // backspace gets ready to remove one position past the edit position.
}
}
if( this.caretTestPos == MaskedTextProvider.InvalidIndex )
{
this.caretTestPos = startPosition;
}
}
else // (hint == MaskedTextProvider.OperationHint.SideEffect)
{
if( keyCode == Keys.Back ) // Case 3.
{
this.caretTestPos = startPosition;
}
}
}
}
}
else
{
OnMaskInputRejected(new MaskInputRejectedEventArgs(tempPos, hint));
}
// Reposition caret. Call base.SelectInternal for perf reasons.
//this.SelectionLength = 0;
//this.SelectionStart = this.caretTestPos; // new caret position.
base.SelectInternal( this.caretTestPos, 0, this.maskedTextProvider.Length );
return;
}
/// <devdoc>
/// Returns the character nearest to the given point.
/// </devdoc>
public override char GetCharFromPosition(Point pt)
{
char ch;
this.flagState[QUERY_BASE_TEXT] = true;
try
{
ch = base.GetCharFromPosition(pt);
}
finally
{
this.flagState[QUERY_BASE_TEXT] = false;
}
return ch;
}
/// <devdoc>
/// Returns the index of the character nearest to the given point.
/// </devdoc>
public override int GetCharIndexFromPosition(Point pt)
{
int index;
this.flagState[QUERY_BASE_TEXT] = true;
try
{
index = base.GetCharIndexFromPosition(pt);
}
finally
{
this.flagState[QUERY_BASE_TEXT] = false;
}
return index;
}
/// <devdoc>
/// Returns the position of the last input character (or if available, the next edit position).
/// This is used by base.AppendText.
/// </devdoc>
internal override int GetEndPosition()
{
if( this.flagState[IS_NULL_MASK])
{
return base.GetEndPosition();
}
int pos = this.maskedTextProvider.FindEditPositionFrom( this.maskedTextProvider.LastAssignedPosition + 1, forward );
if( pos == MaskedTextProvider.InvalidIndex )
{
pos = this.maskedTextProvider.LastAssignedPosition + 1;
}
return pos;
}
/// <devdoc>
/// Unsupported method/property.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Never)
]
public new int GetFirstCharIndexOfCurrentLine()
{
return 0;
}
/// <devdoc>
/// Unsupported method/property.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Never)
]
public new int GetFirstCharIndexFromLine(int lineNumber)
{
return 0;
}
/// <devdoc>
/// Gets the string in the text box following the formatting parameters includePrompt and includeLiterals and
/// honoring the PasswordChar property.
/// </devdoc>
private string GetFormattedDisplayString()
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
bool includePrompt;
if (this.ReadOnly) // Always hide prompt.
{
includePrompt = false;
}
else if (this.DesignMode) // Not RO and at design time, always show prompt.
{
includePrompt = true;
}
else // follow HidePromptOnLeave property.
{
includePrompt = !(this.HidePromptOnLeave && !this.Focused);
}
return this.maskedTextProvider.ToString(/*ignorePwdChar */ false, includePrompt, /*includeLiterals*/ true, 0, this.maskedTextProvider.Length);
}
/// <devdoc>
/// Unsupported method/property.
/// virtual method.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Never)
]
public override int GetLineFromCharIndex(int index)
{
return 0;
}
/// <devdoc>
/// Returns the location of the character at the given index.
/// </devdoc>
public override Point GetPositionFromCharIndex(int index)
{
Point pos;
this.flagState[QUERY_BASE_TEXT] = true;
try
{
pos = base.GetPositionFromCharIndex(index);
}
finally
{
this.flagState[QUERY_BASE_TEXT] = false;
}
return pos;
}
/// <devdoc>
/// Need to override this method so when get_Text is called we return the text that is actually
/// painted in the control so measuring text works on the actual text and not the formatted one.
/// </devdoc>
internal override Size GetPreferredSizeCore(Size proposedConstraints)
{
Size size;
this.flagState[QUERY_BASE_TEXT] = true;
try
{
size = base.GetPreferredSizeCore( proposedConstraints );
}
finally
{
this.flagState[QUERY_BASE_TEXT] = false;
}
return size;
}
/// <devdoc>
/// The selected text in the control according to the CutCopyMaskFormat properties (IncludePrompt/IncludeLiterals).
/// This is used in Cut/Copy operations (SelectedText).
/// The prompt character is always replaced with a blank character.
/// </devdoc>
private string GetSelectedText()
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
int selStart, selLength;
base.GetSelectionStartAndLength( out selStart, out selLength );
if( selLength == 0 )
{
return string.Empty;
}
bool includePrompt = (CutCopyMaskFormat & MaskFormat.IncludePrompt ) != 0;
bool includeLiterals = (CutCopyMaskFormat & MaskFormat.IncludeLiterals) != 0;
return this.maskedTextProvider.ToString( /*ignorePasswordChar*/ true, includePrompt, includeLiterals, selStart, selLength );
}
/// <include file='doc\MaskedTextBox.uex' path='docs/doc[@for="MaskedTextBox.OnBackColorChanged"]/*' />
protected override void OnBackColorChanged(EventArgs e)
{
base.OnBackColorChanged(e);
// VSWhidbey 465708. Force repainting of the entire window frame
if (Application.RenderWithVisualStyles && this.IsHandleCreated && this.BorderStyle == BorderStyle.Fixed3D)
{
SafeNativeMethods.RedrawWindow(new HandleRef(this, this.Handle), null, NativeMethods.NullHandleRef, NativeMethods.RDW_INVALIDATE | NativeMethods.RDW_FRAME);
}
}
/// <devdoc>
/// Overridden to update the newly created handle with the settings of the PasswordChar properties
/// if no mask has been set.
/// </devdoc>
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
base.SetSelectionOnHandle();
if( this.flagState[IS_NULL_MASK]&& this.maskedTextProvider.IsPassword )
{
SetEditControlPasswordChar(this.maskedTextProvider.PasswordChar);
}
}
/// <devdoc>
/// Raises the IsOverwriteModeChanged event.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Advanced)
]
protected virtual void OnIsOverwriteModeChanged(EventArgs e)
{
EventHandler eh = Events[EVENT_ISOVERWRITEMODECHANGED] as EventHandler;
if (eh != null)
{
eh(this, e);
}
}
/// <devdoc>
/// Raises the <see cref='System.Windows.Forms.Control.KeyDown'/> event.
/// </devdoc>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if( this.flagState[IS_NULL_MASK])
{
// Operates as a regular text box base.
return;
}
Keys keyCode = e.KeyCode;
// Special-case Return & Esc since they generate invalid characters we should not process OnKeyPress.
if( keyCode == Keys.Return || keyCode == Keys.Escape )
{
this.flagState[HANDLE_KEY_PRESS] = false;
}
// Insert is toggled when not modified with some other key (ctrl, shift...). Note that shift-Insert is
// same as paste.
if (keyCode == Keys.Insert && e.Modifiers == Keys.None && this.insertMode == InsertKeyMode.Default)
{
this.flagState[INSERT_TOGGLED] = !this.flagState[INSERT_TOGGLED];
OnIsOverwriteModeChanged(EventArgs.Empty);
return;
}
if (e.Control && char.IsLetter((char)keyCode))
{
switch (keyCode)
{
// Unsupported keys should not be handled to allow generatating the corresponding message
// which is handled in the WndProc.
//case Keys.Z: // ctrl-z == Undo.
//case Keys.Y: // ctrl-y == Redo.
// e.Handled = true;
// return;
// Note: Ctrl-Insert (Copy -Shortcut.CtrlIns) and Shft-Insert (Paste - Shortcut.ShiftIns) are
// handled by the base class and behavior depend on ShortcutsEnabled property.
// Special cases: usually cases where the native edit control would modify the mask.
case Keys.H: // ctrl-h == Backspace == '\b'
keyCode = Keys.Back; // handle it below.
break;
default:
// Next OnKeyPress should not be handled to allow Ctrl-<x/c/v/a> to be processed in the
// base class so corresponding messages can be generated (WM_CUT/WM_COPY/WM_PASTE).
// Combined characters don't generate OnKeyDown by themselves but they generate OnKeyPress.
this.flagState[HANDLE_KEY_PRESS] = false;
return;
}
}
if ( keyCode == Keys.Delete || keyCode == Keys.Back ) // Deletion keys.
{
if (!this.ReadOnly)
{
int selectionLen;
int startPosition;
base.GetSelectionStartAndLength( out startPosition, out selectionLen );
switch (e.Modifiers)
{
case Keys.Shift:
if( keyCode == Keys.Delete )
{
keyCode = Keys.Back;
}
goto default;
case Keys.Control:
if( selectionLen == 0 ) // In other case, the selected text should be deleted.
{
if( keyCode == Keys.Delete ) // delete to the end of the string.
{
selectionLen = this.maskedTextProvider.Length - startPosition;
}
else // ( keyCode == Keys.Back ) // delete to the beginning of the string.
{
selectionLen = startPosition == this.maskedTextProvider.Length /*at end of text*/ ? startPosition : startPosition + 1;
startPosition = 0;
}
}
goto default;
default:
if( !this.flagState[HANDLE_KEY_PRESS] )
{
this.flagState[HANDLE_KEY_PRESS] = true;
}
break;
}
//
// Handle special case when using Korean IME and ending a composition.
//
/* This code is no longer needed after fixing bug#517013 - Left here for reference DON'T DELETE.
if ((ImeModeConversion.InputLanguageTable == ImeModeConversion.KoreanTable) && this.flagState[IME_ENDING_COMPOSITION])
{
// Korean IME & Edit control weirdness in action:
// When pressing Del/Esc/Enter/BckSpc during a composition, the character is converted and a
// corresponding WM_IME_CHAR in placed in the app queue.
if(keyCode == Keys.Back && selectionLen == 0)
{
// After the WM_IME_CHAR message is processed, a WM_CHAR message is queued for the backspace; the
// edit control processes the message deleting the previous character. Since we don't pass this
// message to the edit control we need to do it ourselves (move position one ahead because it is
// set at the composition window which is inserted in the test string.
startPosition++;
// Note: If the converted character is invalid and there is a character already placed in the previous
// position, it will be deleted. THIS IS BY DESIGN: It is exactly like the user performing the convertion
// and then pressing backspace, there's no way to differenciate these two scenarios.
// See VSWhidbey#200690 for the sequence of messages generated.
}
}
*/
Delete(keyCode, startPosition, selectionLen);
e.SuppressKeyPress = true;
}
}
}
/// <devdoc>
/// Raises the <see cref='System.Windows.Forms.Control.KeyPress'/> event.
/// </devdoc>
protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);
if( this.flagState[IS_NULL_MASK])
{
// Operates as a regular text box base.
return;
}
// This key may be a combined key involving a letter, like Ctrl-A; let the native control handle it.
if( !this.flagState[HANDLE_KEY_PRESS] )
{
this.flagState[HANDLE_KEY_PRESS] = true;
// When the combined key involves a letter, the final character is not a letter. There are some
// Ctrl combined keys that generate a letter and can be confusing; we do not mean to pass those
// characters to the underlying Edit control. These combinations are: Ctrl-F<#> and Ctrl-Atl-<someKey>
if (!char.IsLetter(e.KeyChar))
{
return;
}
}
if( !this.ReadOnly)
{
// At this point the character needs to be processed ...
MaskedTextResultHint hint;
int selectionStart;
int selectionLen;
base.GetSelectionStartAndLength( out selectionStart, out selectionLen );
string oldText = TextOutput;
if (PlaceChar(e.KeyChar, selectionStart, selectionLen, this.IsOverwriteMode, out hint))
{
//if( hint == MaskedTextResultHint.Success || hint == MaskedTextResultHint.SideEffect )
if( TextOutput != oldText )
{
SetText(); // Now set the text in the display.
}
this.SelectionStart = ++this.caretTestPos; // caretTestPos is updated in PlaceChar.
if (ImeModeConversion.InputLanguageTable == ImeModeConversion.KoreanTable)
{
// Korean IMEs complete composition when a character has been fully converted, so the composition string
// is only one-character long; once composed we block the IME if there ins't more room in the test string.
int editPos = this.maskedTextProvider.FindUnassignedEditPositionFrom(this.caretTestPos, forward);
if (editPos == MaskedTextProvider.InvalidIndex)
{
ImeComplete(); // Force completion of compostion.
}
}
}
else
{
OnMaskInputRejected(new MaskInputRejectedEventArgs(this.caretTestPos, hint)); // caretTestPos is updated in PlaceChar.
}
if( selectionLen > 0 )
{
this.SelectionLength = 0;
}
e.Handled = true;
}
}
/// <devdoc>
/// <para>Raises the <see cref='System.Windows.Forms.Control.KeyUp'/> event.</para>
/// </devdoc>
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
// KeyUp is the last message to be processed so it is the best place to reset these flags.
if (this.flagState[IME_COMPLETING])
{
this.flagState[IME_COMPLETING] = false;
}
if( this.flagState[IME_ENDING_COMPOSITION] )
{
this.flagState[IME_ENDING_COMPOSITION] = false;
}
}
/// <devdoc>
/// Raises the MaskChanged event.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Advanced)
]
protected virtual void OnMaskChanged(EventArgs e)
{
EventHandler eh = Events[EVENT_MASKCHANGED] as EventHandler;
if (eh != null)
{
eh(this, e);
}
}
/// <devdoc>
/// Raises the MaskInputRejected event.
/// </devdoc>
private void OnMaskInputRejected(MaskInputRejectedEventArgs e)
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
if (this.BeepOnError)
{
System.Media.SoundPlayer sp = new System.Media.SoundPlayer();
sp.Play();
}
MaskInputRejectedEventHandler eh = Events[EVENT_MASKINPUTREJECTED] as MaskInputRejectedEventHandler;
if (eh != null)
{
eh(this, e);
}
}
/// <devdoc>
/// Unsupported method/property.
/// virtual method.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Never)
]
protected override void OnMultilineChanged(EventArgs e)
{
}
/// <devdoc>
/// Raises the TextAlignChanged event.
/// </devdoc>
protected virtual void OnTextAlignChanged(EventArgs e)
{
EventHandler eh = Events[EVENT_TEXTALIGNCHANGED] as EventHandler;
if (eh != null)
{
eh(this, e);
}
}
/// <devdoc>
/// Raises the TypeValidationCompleted event.
/// </devdoc>
private void OnTypeValidationCompleted(TypeValidationEventArgs e)
{
TypeValidationEventHandler eh = Events[EVENT_VALIDATIONCOMPLETED] as TypeValidationEventHandler;
if (eh != null)
{
eh(this, e);
}
}
/// <devdoc>
/// Raises the System.Windows.Forms.Control.Validating event.
/// Overridden here to be able to control the order validating events are
/// raised [TypeValidationCompleted - Validating - Validated - Leave - KillFocus]
/// </devdoc>
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected override void OnValidating(CancelEventArgs e)
{
// Note: It seems impractical to perform type validation here if the control is read only but we need
// to be consistent with other TextBoxBase controls which don't check for RO; and we don't want
// to fix them to avoid introducing breaking changes.
PerformTypeValidation(e);
base.OnValidating(e);
}
/// <devdoc>
/// Raises the TextChanged event and related Input/Output text events when mask is null.
/// Overriden here to be able to control order of text changed events.
/// </devdoc>
protected override void OnTextChanged(EventArgs e)
{
// A text changed event handler will most likely query for the Text value, we need to return the
// formatted one.
bool queryBaseText = this.flagState[QUERY_BASE_TEXT];
this.flagState[QUERY_BASE_TEXT] = false;
try
{
base.OnTextChanged(e);
}
finally
{
this.flagState[QUERY_BASE_TEXT] = queryBaseText;
}
}
/// <devdoc>
/// Replaces the current selection in the text box specified by the startPosition and selectionLen parameters
/// with the contents of the supplied string.
/// </devdoc>
private void Replace(string text, int startPosition, int selectionLen)
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
Debug.Assert(text != null, "text is null.");
// Clone the MaskedTextProvider so text properties are not modified until the paste operation is
// completed. This is needed in case one of these properties is retreived in a MaskedInputRejected
// event handler (clipboard text is attempted to be set into the input text char by char).
MaskedTextProvider clonedProvider = (MaskedTextProvider) this.maskedTextProvider.Clone();
// Cache the current caret position so we restore it in case the text does not change. VSW#498875.
int currentCaretPos = this.caretTestPos;
// First replace characters in the selection (if any and if any edit positions) until completed, or the test position falls
// outside the selection range, or there's no more room in the test string for editable characters.
// Then insert any remaining characters from the input.
MaskedTextResultHint hint = MaskedTextResultHint.NoEffect;
int endPos = startPosition + selectionLen - 1;
if( this.RejectInputOnFirstFailure )
{
bool succeeded;
succeeded = (startPosition > endPos) ?
clonedProvider.InsertAt(text, startPosition, out this.caretTestPos, out hint ) :
clonedProvider.Replace(text, startPosition, endPos, out this.caretTestPos, out hint);
if( !succeeded )
{
OnMaskInputRejected(new MaskInputRejectedEventArgs(this.caretTestPos, hint));
}
}
else
{
// temp hint used to preserve the 'primary' operation hint (no side effects).
MaskedTextResultHint tempHint = hint;
int testPos;
foreach (char ch in text)
{
if( !this.maskedTextProvider.VerifyEscapeChar( ch, startPosition )) // char won't be escaped, find and edit position for it.
{
// Observe that we look for a position w/o respecting the selection length, because the input text could be larger than
// the number of edit positions in the selection.
testPos = clonedProvider.FindEditPositionFrom(startPosition, forward);
if( testPos == MaskedTextProvider.InvalidIndex )
{
// this will continue to execute (fail) until the end of the text so we fire the event for each remaining char.
OnMaskInputRejected(new MaskInputRejectedEventArgs(startPosition, MaskedTextResultHint.UnavailableEditPosition));
continue;
}
startPosition = testPos;
}
int length = endPos >= startPosition ? 1 : 0;
// if length > 0 we are (re)placing the input char in the current startPosition, otherwise we are inserting the input.
bool replace = length > 0;
if (PlaceChar(clonedProvider, ch, startPosition, length, replace, out tempHint))
{
// caretTestPos is updated in PlaceChar call.
startPosition = this.caretTestPos + 1;
// place char will insert or replace a single character so the hint must be success, and that will be the final operation
// result hint.
if (tempHint == MaskedTextResultHint.Success && hint != tempHint)
{
hint = tempHint;
}
}
else
{
OnMaskInputRejected(new MaskInputRejectedEventArgs(startPosition, tempHint));
}
}
if (selectionLen > 0)
{
// At this point we have processed all characters from the input text (if any) but still need to
// remove remaining characters from the selected text (if editable and valid chars).
if (startPosition <= endPos)
{
if (!clonedProvider.RemoveAt(startPosition, endPos, out this.caretTestPos, out tempHint))
{
OnMaskInputRejected(new MaskInputRejectedEventArgs(this.caretTestPos, tempHint));
}
// If 'replace' is not actually performed (maybe the input is empty which means 'remove', hint will be whatever
// the 'remove' operation result hint is.
if (hint == MaskedTextResultHint.NoEffect && hint != tempHint)
{
hint = tempHint;
}
}
}
}
bool updateText = TextOutput != clonedProvider.ToString();
// Always set the mtp, the formatted text could be the same but the assigned positions may be different.
this.maskedTextProvider = clonedProvider;
// Update text if needed.
if( updateText )
{
SetText();
// Update caret position.
this.caretTestPos = startPosition;
base.SelectInternal( this.caretTestPos, 0, this.maskedTextProvider.Length );
}
else
{
this.caretTestPos = currentCaretPos;
}
return;
}
/// <devdoc>
/// Pastes specified text over the currently selected text (if any) shifting upper characters if
/// input is longer than selected text, and/or removing remaining characters from the selection if
/// input contains less characters.
/// </devdoc>
private void PasteInt( string text )
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
int selStart, selLength;
base.GetSelectionStartAndLength(out selStart, out selLength);
if( string.IsNullOrEmpty(text) )
{
Delete( Keys.Delete, selStart, selLength );
}
else
{
Replace(text, selStart, selLength);
}
}
/// <devdoc>
/// Performs validation of the input string using the provided ValidatingType object (if any).
/// Returns an object created from the formatted text.
/// If the CancelEventArgs param is not null, it is assumed the control is leaving focus and
/// the validation event chain is being executed (TypeValidationCompleted - Validating - Validated...);
/// the value of the CancelEventArgs.Cancel property is the same as the TypeValidationEventArgs.Cancel
/// on output (Cancel provides proper handling of focus shifting at the Control class level).
/// Note: The text being validated does not include prompt chars.
/// </devdoc>
private object PerformTypeValidation(CancelEventArgs e)
{
object parseRetVal = null;
if (this.validatingType != null)
{
string message = null;
if (!this.flagState[IS_NULL_MASK]&& this.maskedTextProvider.MaskCompleted == false)
{
message = SR.GetString(SR.MaskedTextBoxIncompleteMsg);
}
else
{
string textValue;
if( !this.flagState[IS_NULL_MASK]) // replace prompt with space.
{
textValue = this.maskedTextProvider.ToString(/*includePrompt*/ false, this.IncludeLiterals);
}
else
{
textValue = base.Text;
}
try
{
parseRetVal = Formatter.ParseObject(
textValue, // data
this.validatingType, // targetType
typeof(string), // sourceType
null, // targetConverter
null, // sourceConverter
this.formatProvider, // formatInfo
null, // nullValue
Formatter.GetDefaultDataSourceNullValue(this.validatingType)); // dataSourceNullValue
}
catch (Exception exception)
{
if (ClientUtils.IsSecurityOrCriticalException(exception))
{
throw;
}
if (exception.InnerException != null) // Outer exception is a generic TargetInvocationException.
{
exception = exception.InnerException;
}
message = exception.GetType().ToString() + ": " + exception.Message;
}
}
bool isValidInput = false;
if (message == null)
{
isValidInput = true;
message = SR.GetString(SR.MaskedTextBoxTypeValidationSucceeded);
}
TypeValidationEventArgs tve = new TypeValidationEventArgs(this.validatingType, isValidInput, parseRetVal, message);
OnTypeValidationCompleted(tve);
if( e != null )
{
e.Cancel = tve.Cancel;
}
}
return parseRetVal;
}
/// <devdoc>
/// Insert or replaces the specified character into the control's text and updates the caret position.
/// If overwrite is true, it replaces the character at the selection start position.
/// </devdoc>
private bool PlaceChar(char ch, int startPosition, int length, bool overwrite,
out MaskedTextResultHint hint)
{
return PlaceChar(this.maskedTextProvider, ch, startPosition, length, overwrite, out hint );
}
/// <devdoc>
/// Override version to be able to perform the operation on a cloned provider.
/// </devdoc>
private bool PlaceChar(MaskedTextProvider provider, char ch, int startPosition, int length, bool overwrite,
out MaskedTextResultHint hint)
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
this.caretTestPos = startPosition;
if (startPosition < this.maskedTextProvider.Length)
{
if (length > 0) // Replacing selection with input char.
{
int endPos = startPosition + length - 1;
return provider.Replace(ch, startPosition, endPos, out this.caretTestPos, out hint);
}
else
{
if (overwrite)
{
// overwrite character at next edit position from startPosition (inclusive).
return provider.Replace(ch, startPosition, out this.caretTestPos, out hint);
}
else // insert.
{
return provider.InsertAt(ch, startPosition, out this.caretTestPos, out hint);
}
}
}
hint = MaskedTextResultHint.UnavailableEditPosition;
return false;
}
/// <devdoc>
/// <From Control.cs>:
/// Processes a command key. This method is called during message
/// pre-processing to handle command keys. Command keys are keys that always
/// take precedence over regular input keys. Examples of command keys
/// include accelerators and menu shortcuts. The method must return true to
/// indicate that it has processed the command key, or false to indicate
/// that the key is not a command key.
///
/// processCmdKey() first checks if the control has a context menu, and if
/// so calls the menu's processCmdKey() to check for menu shortcuts. If the
/// command key isn't a menu shortcut, and if the control has a parent, the
/// key is passed to the parent's processCmdKey() method. The net effect is
/// that command keys are "bubbled" up the control hierarchy.
///
/// When overriding processCmdKey(), a control should return true to
/// indicate that it has processed the key. For keys that aren't processed by
/// the control, the result of "base.processCmdKey()" should be returned.
///
/// Controls will seldom, if ever, need to override this method.
/// </From Control.cs>
///
/// Implements the handling of Ctrl+A (select all). Note: Code copied from TextBox.
/// </devdoc>
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
//
// The base class should be called first because it implements ShortcutsEnabled,
// which takes precedence over Ctrl+A
//
bool msgProcessed = base.ProcessCmdKey(ref msg, keyData);
if (!msgProcessed)
{
if ((int)keyData == (int)Shortcut.CtrlA)
{
base.SelectAll();
msgProcessed = true; // This prevents generating a WM_CHAR for 'A'.
}
}
return msgProcessed;
}
/// <devdoc>
/// We need to override this method so we can handle input language changes properly. Control
/// doesn't handle the WM_CHAR messages generated after WM_IME_CHAR messages, it passes them
/// to DefWndProc (the characters would be displayed in the text box always).
///
/// </devdoc>
[SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
protected internal override bool ProcessKeyMessage(ref Message m)
{
// call base's method so the WM_CHAR and other messages are processed; this gives Control the
// chance to flush all pending WM_CHAR processing after WM_IME_CHAR messages are generated.
bool msgProcessed = base.ProcessKeyMessage(ref m);
if (this.flagState[IS_NULL_MASK])
{
return msgProcessed; // Operates as a regular text box base.
}
// If this WM_CHAR message is sent after WM_IME_CHAR, we ignore it since we already processed
// the corresponding WM_IME_CHAR message.
if( m.Msg == NativeMethods.WM_CHAR && base.ImeWmCharsToIgnore > 0 ) {
return true; // meaning, we handled the message so it is not passed to the default WndProc.
}
return msgProcessed;
}
/// <devdoc>
/// Designe time support for resetting Culture property..
/// </devdoc>
private void ResetCulture()
{
this.Culture = CultureInfo.CurrentCulture;
}
/// <devdoc>
/// Unsupported method/property.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Never)
]
public new void ScrollToCaret()
{
}
/// <devdoc>
/// Sets the underlying MaskedTextProvider object. Used when the control is initialized
/// and one of its properties, backed up by the MaskedTextProvider, changes; this requires
/// recreating the provider because it is immutable.
/// </devdoc>
private void SetMaskedTextProvider( MaskedTextProvider newProvider )
{
SetMaskedTextProvider( newProvider, null);
}
/// <devdoc>
/// Overload to allow for passing the text when the mask is being changed from null,
/// in this case the maskedTextProvider holds backend info only (not the text).
/// </devdoc>
private void SetMaskedTextProvider( MaskedTextProvider newProvider, string textOnInitializingMask )
{
Debug.Assert( newProvider != null, "Initializing from a null MaskProvider ref." );
// Set R/W properties.
newProvider.IncludePrompt = this.maskedTextProvider.IncludePrompt;
newProvider.IncludeLiterals = this.maskedTextProvider.IncludeLiterals;
newProvider.SkipLiterals = this.maskedTextProvider.SkipLiterals;
newProvider.ResetOnPrompt = this.maskedTextProvider.ResetOnPrompt;
newProvider.ResetOnSpace = this.maskedTextProvider.ResetOnSpace;
// If mask not initialized and not initializing it, the new provider is just a property backend.
// Change won't have any effect in text.
if( this.flagState[IS_NULL_MASK] && textOnInitializingMask == null)
{
this.maskedTextProvider = newProvider;
return;
}
int testPos = 0;
bool raiseOnMaskInputRejected = false; // Raise if new provider rejects old text.
MaskedTextResultHint hint = MaskedTextResultHint.NoEffect;
MaskedTextProvider oldProvider = this.maskedTextProvider;
// Attempt to add previous text.
// If the mask is the same, we need to preserve the caret and character positions if the text is added successfully.
bool preserveCharPos = oldProvider.Mask == newProvider.Mask;
// Cache text output text before setting the new provider to determine whether we need to raise the TextChanged event.
string oldText;
// NOTE: Whenever changing the MTP, the text is lost if any character in the old text violates the new provider's mask.
if( textOnInitializingMask != null ) // Changing Mask (from null), which is the only RO property that requires passing text.
{
oldText = textOnInitializingMask;
raiseOnMaskInputRejected = !newProvider.Set( textOnInitializingMask, out testPos, out hint );
}
else
{
oldText = TextOutput;
// We need to attempt to set the input characters one by one in the edit positions so they are not
// escaped.
int assignedCount = oldProvider.AssignedEditPositionCount;
int srcPos = 0;
int dstPos = 0;
while( assignedCount > 0 )
{
srcPos = oldProvider.FindAssignedEditPositionFrom( srcPos, forward );
Debug.Assert( srcPos != MaskedTextProvider.InvalidIndex, "InvalidIndex unexpected at this time." );
if (preserveCharPos)
{
dstPos = srcPos;
}
else
{
dstPos = newProvider.FindEditPositionFrom(dstPos, forward);
if (dstPos == MaskedTextProvider.InvalidIndex)
{
newProvider.Clear();
testPos = newProvider.Length;
hint = MaskedTextResultHint.UnavailableEditPosition;
break;
}
}
if( !newProvider.Replace( oldProvider[srcPos], dstPos, out testPos, out hint ))
{
preserveCharPos = false;
newProvider.Clear();
break;
}
srcPos++;
dstPos++;
assignedCount--;
}
raiseOnMaskInputRejected = !MaskedTextProvider.GetOperationResultFromHint(hint);
}
// Set provider.
this.maskedTextProvider = newProvider;
if( this.flagState[IS_NULL_MASK] )
{
this.flagState[IS_NULL_MASK] = false;
}
// Raising events need to be done only after the new provider has been set so the MTB is in a state where properties
// can be queried from event handlers safely.
if( raiseOnMaskInputRejected )
{
OnMaskInputRejected(new MaskInputRejectedEventArgs(testPos, hint));
}
if( newProvider.IsPassword )
{
// Reset native edit control so the MaskedTextBox will take control over the characters that
// need to be replaced with the password char (the input text characters).
// MTB takes over.
SetEditControlPasswordChar('\0');
}
EventArgs e = EventArgs.Empty;
if (textOnInitializingMask != null /*changing mask from null*/ || oldProvider.Mask != newProvider.Mask)
{
OnMaskChanged(e);
}
SetWindowText(GetFormattedDisplayString(), oldText != TextOutput, preserveCharPos);
}
/// <devdoc>
/// Sets the control's text to the formatted text obtained from the underlying MaskedTextProvider.
/// TextChanged is raised always, this assumes the display or the output text changed.
/// The caret position is lost (unless cached somewhere else like when lossing the focus).
/// This is the common way of changing the text in the control.
/// </devdoc>
private void SetText()
{
SetWindowText(GetFormattedDisplayString(), true, false);
}
/// <devdoc>
/// Sets the control's text to the formatted text obtained from the underlying MaskedTextProvider.
/// TextChanged is not raised. [PasswordChar]
/// The caret position is preserved.
/// </devdoc>
private void SetWindowText()
{
SetWindowText(GetFormattedDisplayString(), false, true);
}
/// <devdoc>
/// Sets the text directly in the underlying edit control to the value specified.
/// The 'raiseTextChangedEvent' param determines whether TextChanged event is raised or not.
/// The 'preserveCaret' param determines whether an attempt to preserve the caret position should be made or not
/// after the call to SetWindowText (WindowText) is performed.
/// </devdoc>
private void SetWindowText(string text, bool raiseTextChangedEvent, bool preserveCaret)
{
this.flagState[QUERY_BASE_TEXT] = true;
try
{
if( preserveCaret )
{
this.caretTestPos = this.SelectionStart;
}
WindowText = text; // this calls Win32::SetWindowText directly, no OnTextChanged raised.
if( raiseTextChangedEvent )
{
OnTextChanged(EventArgs.Empty);
}
if( preserveCaret )
{
this.SelectionStart = this.caretTestPos;
}
}
finally
{
this.flagState[QUERY_BASE_TEXT] = false;
}
}
/// <devdoc>
/// Designe time support for checking if Culture value in the designer should be serialized.
/// </devdoc>
private bool ShouldSerializeCulture()
{
return !CultureInfo.CurrentCulture.Equals(this.Culture);
}
/// <devdoc>
/// Undoes the last edit operation in the text box.
/// Unsupported property/method.
/// WndProc ignores EM_UNDO.
/// </devdoc>
[
EditorBrowsable(EditorBrowsableState.Never)
]
public new void Undo()
{
}
/// <devdoc>
/// Forces type validation. Returns the validated text value.
/// </devdoc>
public object ValidateText()
{
return PerformTypeValidation(null);
}
/// <devdoc>
/// Deletes all input characters in the current selection.
/// </devdoc>
private bool WmClear()
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
if( !this.ReadOnly )
{
int selStart, selLength;
base.GetSelectionStartAndLength( out selStart, out selLength );
Delete(Keys.Delete, selStart, selLength);
return true;
}
return false;
}
/// <devdoc>
/// Copies current selection text to the clipboard, formatted according to the IncludeLiterals properties but
/// ignoring the prompt character.
/// Returns true if the operation succeeded, false otherwise.
/// </devdoc>
private bool WmCopy()
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
if (this.maskedTextProvider.IsPassword) // cannot copy password to clipboard.
{
return false;
}
string text = GetSelectedText();
try
{
// SECREVIEW : Copy needs to work even if the OwnClipboard permission isn't available. We need to assert here.
// This assert is ok, observe that if the control is in password mode it won't reach this point,
// see code above.
// Be careful if code is added after the security assert, you may need to put it in a try-finally
// block to revert the security assert.
IntSecurity.ClipboardWrite.Assert();
if (text.Length == 0)
{
Clipboard.Clear();
}
else
{
Clipboard.SetText(text);
}
}
catch (Exception ex)
{
// Note: Sometimes the above operation throws but it successfully sets the
// data in the clipboard. This usually happens when the Application's Main
// is not attributed with [STAThread].
if (ClientUtils.IsSecurityOrCriticalException(ex))
{
throw;
}
}
return true;
}
/// <devdoc>
/// Processes the WM_IME_COMPOSITION message when using Korean IME.
/// Korean IME uses the control's caret as the composition string (it processes only one character at a time),
/// we need to have special message handling for it.
/// Returns true if the message is handled, false otherwise.
/// </devdoc>
private bool WmImeComposition(ref Message m)
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
#if DEBUG
if (this.ReadOnly || this.maskedTextProvider.IsPassword)
{
// This should have been already handled by the ReadOnly, PasswordChar and ImeMode properties.
Debug.Assert(this.ImeMode == ImeMode.Disable, "IME enabled when in RO or Pwd mode.");
}
#endif
// Non-Korean IMEs complete compositon when all characters in the string has been composed (when user hits enter);
// Currently, we don't support checking the composition string characters because it would require similar logic
// as the MaskedTextBox itself.
if (ImeModeConversion.InputLanguageTable == ImeModeConversion.KoreanTable)
{
byte imeConvertionType = imeConvertionNone;
// Check if there's an update to the compositon string:
if ((m.LParam.ToInt32() & NativeMethods.GCS_COMPSTR) != 0)
{
// The character in the composition has been updated but not yet converted.
imeConvertionType = imeConvertionUpdate;
}
else if ((m.LParam.ToInt32() & NativeMethods.GCS_RESULTSTR) != 0)
{
// The character(s) in the composition has been fully converted.
imeConvertionType = imeConvertionCompleted;
}
// Process any update in the composition string.
if (imeConvertionType != imeConvertionNone)
{
if (this.flagState[IME_ENDING_COMPOSITION])
{
// If IME is completing the convertion, we don't want to process further characters.
return this.flagState[IME_COMPLETING];
}
}
}
return false; //message not handled.
}
/// <devdoc>
/// Processes the WM_IME_STARTCOMPOSITION message.
/// Returns true if the message is handled, false otherwise.
/// </devdoc>
private bool WmImeStartComposition()
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
// Position the composition window in a valid place.
int startPosition, selectionLen;
base.GetSelectionStartAndLength( out startPosition, out selectionLen );
int startEditPos = this.maskedTextProvider.FindEditPositionFrom( startPosition, forward );
if( startEditPos != MaskedTextProvider.InvalidIndex )
{
if (selectionLen > 0 && (ImeModeConversion.InputLanguageTable == ImeModeConversion.KoreanTable))
{
// Korean IME: We need to delete the selected text and reposition the caret so the IME processes one
// character only, otherwise it would overwrite the selection with the caret (composition string),
// deleting a portion of the mask.
int endEditPos = this.maskedTextProvider.FindEditPositionFrom(startPosition + selectionLen - 1, backward);
if (endEditPos >= startEditPos)
{
selectionLen = endEditPos - startEditPos + 1;
Delete(Keys.Delete, startEditPos, selectionLen);
}
else
{
ImeComplete();
OnMaskInputRejected(new MaskInputRejectedEventArgs(startPosition, MaskedTextResultHint.UnavailableEditPosition));
return true;
}
}
// update caret position.
if( startPosition != startEditPos )
{
this.caretTestPos = startEditPos;
this.SelectionStart = this.caretTestPos;
}
this.SelectionLength = 0;
}
else
{
ImeComplete();
OnMaskInputRejected(new MaskInputRejectedEventArgs(startPosition, MaskedTextResultHint.UnavailableEditPosition));
return true;
}
return false;
}
/// <devdoc>
/// Processes the WM_PASTE message. Copies the text from the clipboard, if is valid,
/// formatted according to the mask applied to this control.
/// Returns true if the operation succeeded, false otherwise.
/// </devdoc>
private void WmPaste()
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
if( this.ReadOnly )
{
return;
}
// Get the text from the clipboard.
string text;
try
{
IntSecurity.ClipboardRead.Assert();
text = Clipboard.GetText();
}
catch (Exception ex)
{
if (ClientUtils.IsSecurityOrCriticalException(ex))
{
throw;
}
Debug.Fail(ex.ToString());
return;
}
PasteInt( text );
}
private void WmPrint(ref Message m) {
base.WndProc(ref m);
if ((NativeMethods.PRF_NONCLIENT & unchecked( (int) (long)m.LParam)) != 0 && Application.RenderWithVisualStyles && this.BorderStyle == BorderStyle.Fixed3D) {
IntSecurity.UnmanagedCode.Assert();
try {
using (Graphics g = Graphics.FromHdc(m.WParam)) {
Rectangle rect = new Rectangle(0, 0, this.Size.Width - 1, this.Size.Height - 1);
using (Pen pen = new Pen(VisualStyleInformation.TextControlBorder)) {
g.DrawRectangle(pen, rect);
}
rect.Inflate(-1, -1);
g.DrawRectangle(SystemPens.Window, rect);
}
}
finally {
CodeAccessPermission.RevertAssert();
}
}
}
/// <devdoc>
/// We need to override the WndProc method to have full control over what characters can be
/// displayed in the text box; particularly, we have special handling when IME is turned on.
/// </devdoc>
[SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
protected override void WndProc(ref Message m)
{
// Handle messages for special cases (unsupported operations or cases where mask doesn not matter).
switch (m.Msg)
{
case NativeMethods.WM_PRINT:
WmPrint(ref m);
return;
case NativeMethods.WM_CONTEXTMENU:
case NativeMethods.EM_CANUNDO:
base.ClearUndo(); // resets undo buffer.
base.WndProc(ref m);
return;
case NativeMethods.EM_SCROLLCARET: // No scroll for single-line control.
case NativeMethods.EM_LIMITTEXT: // Max/Min text is defined by the mask.
case NativeMethods.EM_UNDO:
case NativeMethods.WM_UNDO:
return;
default:
break; // continue.
}
if( this.flagState[IS_NULL_MASK])
{
base.WndProc(ref m); // Operates as a regular text box base.
return;
}
switch (m.Msg)
{
case NativeMethods.WM_IME_STARTCOMPOSITION:
if( WmImeStartComposition() )
{
break;
}
goto default;
case NativeMethods.WM_IME_ENDCOMPOSITION:
this.flagState[IME_ENDING_COMPOSITION] = true;
goto default;
case NativeMethods.WM_IME_COMPOSITION:
if( WmImeComposition( ref m ) )
{
break;
}
goto default;
case NativeMethods.WM_CUT:
if (!this.ReadOnly && WmCopy())
{
WmClear();
}
break;
case NativeMethods.WM_COPY:
WmCopy();
break;
case NativeMethods.WM_PASTE:
WmPaste();
break;
case NativeMethods.WM_CLEAR:
WmClear();
break;
case NativeMethods.WM_KILLFOCUS:
base.WndProc(ref m);
WmKillFocus();
break;
case NativeMethods.WM_SETFOCUS:
WmSetFocus();
base.WndProc(ref m);
break;
default:
base.WndProc(ref m);
break;
}
}
/// <devdoc>
/// Processes the WM_KILLFOCUS message. Updates control's text replacing promp chars with space.
/// </devdoc>
private void WmKillFocus()
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
base.GetSelectionStartAndLength( out this.caretTestPos, out this.lastSelLength );
if (this.HidePromptOnLeave && !this.MaskFull)
{
SetWindowText(); // Update text w/ no prompt.
// We need to update selection info in case the control is queried for it while it doesn't have the focus.
base.SelectInternal( this.caretTestPos, this.lastSelLength, this.maskedTextProvider.Length );
}
}
/// <devdoc>
/// Processes the WM_SETFOCUS message. Updates control's text with formatted text according to
/// the include prompt property.
/// </devdoc>
private void WmSetFocus()
{
Debug.Assert( !this.flagState[IS_NULL_MASK], "This method must be called when a Mask is provided." );
if (this.HidePromptOnLeave && !this.MaskFull) // Prompt will show up.
{
SetWindowText();
}
// Restore previous selection. Do this always (as opposed to within the condition above as in WmKillFocus)
// because HidePromptOnLeave could have changed while the control did not have the focus.
base.SelectInternal( this.caretTestPos, this.lastSelLength, this.maskedTextProvider.Length );
}
}
}
|