Option Strict On '===================================================================================== ' Classe : NumericTextbox.vb ' ' Auteur : Eric Moreau ' ' Description : System.Windows.Forms.TextBox Extender ' ' Utilisation : 1. Add a regular TextBox to your form ' 2. In the " Windows Form Designer generated code " section, ' replace System.Windows.Forms.TextBox by NumericTextBox (2 places) ' ' Historique : ' Auteur Date Intervention ' ----------------- --------------- ------------------------------------------------- ' Eric Moreau 2003/10/25 Création '===================================================================================== Imports System.ComponentModel Public Class NumericTextBox Inherits System.Windows.Forms.TextBox #Region " Windows Form Designer generated code " Public Sub New() MyBase.New() 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call CustomInitialization() End Sub 'UserControl overrides dispose to clean up the component list. Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub 'Required by the Windows Form Designer Private components As System.ComponentModel.IContainer 'NOTE: The following procedure is required by the Windows Form Designer 'It can be modified using the Windows Form Designer. 'Do not modify it using the code editor. Friend WithEvents ErrorProvider1 As System.Windows.Forms.ErrorProvider <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() Me.ErrorProvider1 = New System.Windows.Forms.ErrorProvider ' 'S2iNumericTextBox ' End Sub #End Region #Region " Members " Private mblnErrorProviderVisible As Boolean Private mbytNumberDecimals As Byte Private mbytNumberDigits As Byte Private mdecDefaultValue As Decimal Private mdecValueMax As Decimal Private mdecValueMin As Decimal Private mintErrorLanguage As enuLangueMessage Private mstrDisplayFormat As String Public Event ValidationError(ByVal sender As Object, ByVal e As NumericTBEventArgs) Enum enuErrors ErrNone ErrTooManyMinusSign ErrMinusSignMisplaced ErrNegativeNotAllowed ErrTooManyDot ErrDotNotAllowed ErrLessThenMin ErrGreaterThenMax ErrInvalidValue ErrInvalidChar ErrEmptyValue ErrTooManyDigits ErrTooManyDecimals ErrCannotApplyFormat ErrPasteCanceled End Enum Enum enuLangueMessage English Francais End Enum #End Region '-- Members #Region " Methods " Protected Overrides Sub CreateHandle() Me.Text = mdecDefaultValue.ToString MyBase.CreateHandle() End Sub #Region " Public methods " '=========================================================================================================== '=== Returns wheter the textbox content is valid or not '=========================================================================================================== Public Function IsValid() As Boolean Return Me.ValidateFieldContent(Decimal.Parse(Me.Text, Globalization.NumberStyles.Any).ToString) End Function #End Region '-- Public methods #Region " Protected methods " <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _ Protected Overrides Sub WndProc(ByRef m As Message) Const WM_PASTE As Integer = &H302 ' Listen for operating system messages Select Case m.Msg Case WM_PASTE Dim iData As IDataObject = Clipboard.GetDataObject() Dim s As String = CType(iData.GetData(DataFormats.Text), String) If Not s = Nothing Then Dim strNewResult As String Dim strBeginning As String = Me.Text.Substring(0, Me.SelectionStart) Dim strEnding As String = Me.Text.Substring(Me.SelectionStart + Me.SelectionLength) strNewResult = strBeginning & s & strEnding If Me.ValidateFieldContent(strNewResult) Then 'OK - Resulting field will be OK MyBase.WndProc(m) Else 'The result of the paste would cause and invalid value so stop it SetError(enuErrors.ErrPasteCanceled, strNewResult) End If End If Case Else MyBase.WndProc(m) End Select End Sub #End Region '-- Protected methods #Region " Privates methods " '=========================================================================================================== '=== Calculates the number of a given character into a string '=========================================================================================================== Private Function CountChar(ByVal pstrSource As String, ByVal pstrChar As String) As Integer Return pstrSource.Length - pstrSource.Replace(pstrChar, "").Length End Function '=========================================================================================================== '=== Custom initialization for the component '=========================================================================================================== Private Sub CustomInitialization() Me.DisplayFormat = "###,##0.00" Me.ErrorMessageLanguage = enuLangueMessage.Francais Me.NumberDecimals = 2 Me.NumberDigits = 6 Me.ValueMax = 999999.99D Me.ValueMin = -999999.99D End Sub '=========================================================================================================== '=== A centralized method to set the ErrorProvider control and to raise a custom event '=========================================================================================================== Private Sub SetError(ByVal pError As enuErrors, Optional ByVal pstrValue As String = "") Dim strMessageE As String Dim strMessageF As String Select Case pError Case enuErrors.ErrNone strMessageE = "" strMessageF = "" Case enuErrors.ErrEmptyValue strMessageE = "Field cannot be left empty!" strMessageF = "La valeur ne peut pas être laissé vide!" Case enuErrors.ErrTooManyMinusSign strMessageE = "Only one minus sign is allowed!" strMessageF = "Un seul symbole négatif est permis!" Case enuErrors.ErrMinusSignMisplaced strMessageE = "The minus sign must be placed at the beginning!" strMessageF = "Le symbole négatif doit être placé au début!" Case enuErrors.ErrTooManyDot strMessageE = "Only one decimal separator is allowed!" strMessageF = "Un seul séparateur de décimales est permis!" Case enuErrors.ErrDotNotAllowed strMessageE = "Decimal separator is not allowed!" strMessageF = "Le séparateur de décimal n'est pas permis!" Case enuErrors.ErrTooManyDigits strMessageE = "Too many digits! Only " & Me.NumberDigits.ToString & " allowed." strMessageF = "Trop de chiffres! Seulement " & Me.NumberDigits.ToString & " sont permis." Case enuErrors.ErrTooManyDecimals strMessageE = "Too many decimals! Only " & Me.NumberDecimals.ToString & " allowed." strMessageF = "Trop de décimales! Seulement " & Me.NumberDecimals.ToString & " sont permis." Case enuErrors.ErrLessThenMin strMessageE = "Value is less then the minimum allowed (" & Me.ValueMin.ToString & ")!" strMessageF = "La valeur est inférieure au minimum permis (" & Me.ValueMin.ToString & ")!" Case enuErrors.ErrGreaterThenMax strMessageE = "Value is greater then the maximum allowed (" & Me.ValueMax.ToString & ")!" strMessageF = "La valeur est supérieure au maximum permis (" & Me.ValueMax.ToString & ")!" Case enuErrors.ErrNegativeNotAllowed strMessageE = "Negative Values not allowed!" strMessageF = "Les valeurs négatives ne sont pas permises!" Case enuErrors.ErrInvalidValue strMessageE = "The field does not allow this value! (" & pstrValue & ")" strMessageF = "Valeur invalide! (" & pstrValue & ")" Case enuErrors.ErrInvalidChar strMessageE = "Character not allowed!" strMessageF = "Caractère invalide!" Case enuErrors.ErrCannotApplyFormat strMessageE = "Cannot apply the display format!" strMessageF = "Impossible d'appliquer la propriété DisplayFormat!" Case enuErrors.ErrPasteCanceled strMessageE = "The paste operation was canceled because it would result in an invalid value (" & pstrValue & ")!" strMessageF = "L'opération >Coller< a été annulé parce qu'elle causerait une valeur invalide (" & pstrValue & ")!" Case Else strMessageE = "Unkown error!" strMessageF = "Erreur inconnue!" End Select If ErrorMessageLanguage = enuLangueMessage.English Then If mblnErrorProviderVisible Then ErrorProvider1.SetError(Me, strMessageE) If strMessageE.Length > 0 Then RaiseEvent ValidationError(Me, New NumericTBEventArgs(strMessageE, Me.Text)) End If Else If mblnErrorProviderVisible Then ErrorProvider1.SetError(Me, strMessageF) If strMessageF.Length > 0 Then RaiseEvent ValidationError(Me, New NumericTBEventArgs(strMessageF, Me.Text)) End If End If End Sub '=========================================================================================================== '=== Validates many patters to be sure that the content of the field is valid. '=== Many of these validations cannot be done on the KeyPress event. '=== Others are also placed here to insure that paste or assignement is OK. '=========================================================================================================== Private Function ValidateFieldContent(ByVal pstrText As String) As Boolean SetError(enuErrors.ErrNone) 'Empty string not allowed If pstrText.Length = 0 Then SetError(enuErrors.ErrEmptyValue) Return False End If 'Invalid string If Not Microsoft.VisualBasic.IsNumeric(pstrText) Then 'TODO replace the IsNumeric SetError(enuErrors.ErrInvalidValue) Return False Else 'E.Moreau 2003/10/26 'Quand bindé sur un DECIMAL il retourne une valeur du style 123456.1200 ce qui cause une erreur si le champ ne peut contenir que 2 décimales! pstrText = Microsoft.VisualBasic.Val(pstrText).ToString 'TODO replace the VAL End If 'Check to see the number of minus sign(-) and its position Select Case CountChar(pstrText, "-") Case 0 'OK No problems Case 1 If Not pstrText.StartsWith("-") Then SetError(enuErrors.ErrMinusSignMisplaced) Return False End If Case Else SetError(enuErrors.ErrTooManyMinusSign) Return False End Select 'Check to see if there is only one decimal separator Select Case CountChar(pstrText, System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator) Case 0 'OK, No problems Case 1 Dim intPos As Integer = pstrText.Replace("-", "").IndexOf(System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator) 'Check if the number of digits to the left of the decimal separator is OK If intPos > Me.NumberDigits Then SetError(enuErrors.ErrTooManyDigits) Return False End If 'Check if the number of digits to the right of the decimal separator is OK If pstrText.Replace("-", "").Length - (intPos + 1) > Me.NumberDecimals Then SetError(enuErrors.ErrTooManyDecimals) Return False End If Case Else SetError(enuErrors.ErrTooManyDot) Return False End Select 'Check min and max values If Me.ValueMin <> 0 Or Me.ValueMax <> 0 Then If Microsoft.VisualBasic.Val(pstrText) < Me.ValueMin Then 'TODO replace the VAL SetError(enuErrors.ErrLessThenMin) Return False End If If Microsoft.VisualBasic.Val(pstrText) > Me.ValueMax Then 'TODO replace the VAL SetError(enuErrors.ErrGreaterThenMax) Return False End If End If 'If we get through here, the value should be OK!!! Return True End Function '=========================================================================================================== '=== Validates a single character (called by the KeyPress event). '=========================================================================================================== Private Function ValidateKey() As Boolean Dim intPos As Integer = Me.Text.IndexOf(System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator) If intPos = -1 Then If Me.Text.Replace("-", "").Length = Me.NumberDigits Then SetError(enuErrors.ErrTooManyDigits) Return False End If Else If Me.SelectionStart > intPos Then 'Check if the number of digits to the right of the decimal separator is OK If Me.Text.Length - (intPos + 1) >= Me.NumberDecimals Then SetError(enuErrors.ErrTooManyDecimals) Return False End If Else 'Check if the number of digits to the left of the decimal separator is OK If Me.Text.IndexOf("-") = -1 Then If intPos >= Me.NumberDigits Then SetError(enuErrors.ErrTooManyDigits) Return False End If Else If intPos - 1 > Me.NumberDigits Then SetError(enuErrors.ErrTooManyDigits) Return False End If End If End If End If 'If we get through here, the value should be OK!!! Return True End Function #End Region '-- Privates methods #End Region '-- Methods #Region " Subclassed events " Private Sub SCEnter(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Enter Try 'Remove formatting characters MyBase.Text = Decimal.Parse(Me.Text, Globalization.NumberStyles.Any).ToString Catch 'Nothing, just don't do it!!! End Try Me.SelectAll() End Sub Private Sub SCKeyPress(ByVal eventSender As System.Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress Dim KeyAscii As Integer = Convert.ToInt32(e.KeyChar) If KeyAscii = 8 Then '============================================================================================== '=== BackSpace '============================================================================================== 'Let it do its job ElseIf KeyAscii = 3 Then '============================================================================================== '=== CTRL-C '============================================================================================== 'Let it pass, validation will be done into the TextChanged event ElseIf KeyAscii = 22 Then '============================================================================================== '=== CTRL-V '============================================================================================== 'Let it pass, validation will be done into the WndProc method ElseIf KeyAscii = 26 Then '============================================================================================== '=== CTRL-Z '============================================================================================== 'Let it pass ElseIf e.KeyChar = System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator Then '============================================================================================== '=== Validation for . '============================================================================================== 'E.Moreau 2003.11.02 - remove the selected text from the validation Dim strText As String If Me.SelectionLength > 0 Then strtext = Me.Text.Replace(Me.SelectedText, "") Else strtext = Me.Text End If ' if we already have a period, throw it away If strText.IndexOf(".") >= 0 Then SetError(enuErrors.ErrTooManyDot) KeyAscii = 0 End If ' if we don't have decimals, throw it away If Me.NumberDecimals = 0 Then SetError(enuErrors.ErrDotNotAllowed) KeyAscii = 0 End If ElseIf e.KeyChar = "-" Then '============================================================================================== '=== Validation for - '============================================================================================== 'E.Moreau 2003.11.02 - remove the selected text from the validation Dim strText As String If Me.SelectionLength > 0 Then strtext = Me.Text.Replace(Me.SelectedText, "") Else strtext = Me.Text End If ' The number can only have one minus sign, so ' if we already have one, throw this one away If strText.IndexOf("-") >= 0 Then SetError(enuErrors.ErrTooManyMinusSign) KeyAscii = 0 End If ' if the insertion point is not sitting at zero ' (which is the beginning of the field), ' throw away the minus sign (because it's not ' valid except in first position) If Me.SelectionStart <> 0 Then SetError(enuErrors.ErrMinusSignMisplaced) KeyAscii = 0 End If ' do we allow negative numbers? If Me.ValueMin >= 0 Then SetError(enuErrors.ErrNegativeNotAllowed) KeyAscii = 0 End If ElseIf e.KeyChar >= "0" And e.KeyChar <= "9" Then '============================================================================================== '=== Validation for numbers '============================================================================================== If Not ValidateKey() Then 'SetError(enuErrors.ErrInvalidChar) KeyAscii = 0 End If Else SetError(enuErrors.ErrInvalidChar) KeyAscii = 0 End If If KeyAscii = 0 Then e.Handled = True End If End Sub Private Sub SCLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Leave Try Me.Text = CType(Me.Text, Decimal).ToString(Me.DisplayFormat, Nothing) Catch SetError(enuErrors.ErrCannotApplyFormat) Finally If Me.Text.Length = 0 Then Me.Text = mdecDefaultValue.ToString(Me.DisplayFormat, Nothing) End If End Try End Sub #End Region '-- Subclassed events #Region " Exposed Properties " #Region " Overrides " Public Overrides Property Text() As String Get Return MyBase.Text End Get Set(ByVal Value As String) If Value Is Nothing Then Value = mdecDefaultValue.ToString If Me.ValidateFieldContent(Decimal.Parse(Value, Globalization.NumberStyles.Any).ToString) Then 'MyBase.Text = Value 'E.Moreau 2003/10/26 'Formater quand une valeur est donnée à la propriété TEXT MyBase.Text = CType(Value, Decimal).ToString(Me.DisplayFormat, Nothing) Else SetError(enuErrors.ErrInvalidValue, Value) End If End Set End Property #End Region '-- Overrides #Region " Custom " 'This property contains the display format <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("The default value (also the value when the control is left empty).")> _ Public Property DefaultValue() As Decimal Get Return mdecDefaultValue End Get Set(ByVal Value As Decimal) mdecDefaultValue = Value End Set End Property 'This property contains the display format <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("The display format (passed to Decimal.ToString)")> _ Public Property DisplayFormat() As String Get Return mstrDisplayFormat End Get Set(ByVal Value As String) mstrDisplayFormat = Value End Set End Property 'This property dictates the language of error messages <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("The language of error messages")> _ Public Property ErrorMessageLanguage() As enuLangueMessage Get Return mintErrorLanguage End Get Set(ByVal Value As enuLangueMessage) mintErrorLanguage = Value End Set End Property 'This property contains the number of digits (to the right of the decimal point) <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("The number of digits (to the right of the decimal point)")> _ Public Property NumberDecimals() As Byte Get Return mbytNumberDecimals End Get Set(ByVal Value As Byte) mbytNumberDecimals = Value End Set End Property 'This property contains the maximum number of digits (decimals included) <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("The maximum number of digits (decimals excluded)")> _ Public Property NumberDigits() As Byte Get Return mbytNumberDigits End Get Set(ByVal Value As Byte) mbytNumberDigits = Value End Set End Property <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("The maximum value the textbox can contain")> _ Public Property ValueMax() As Decimal Get Return mdecValueMax End Get Set(ByVal Value As Decimal) mdecValueMax = Value End Set End Property <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("The minimum value the textbox can contain")> _ Public Property ValueMin() As Decimal Get Return mdecValueMin End Get Set(ByVal Value As Decimal) mdecValueMin = Value End Set End Property #End Region '-- Custom #Region " Error Provider " <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("BlinkRate property of the Provider control.")> _ Public Property ErrProviderBlinkRate() As Integer Get Return ErrorProvider1.BlinkRate End Get Set(ByVal Value As Integer) ErrorProvider1.BlinkRate = Value End Set End Property <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("BlinkStyle property of the Provider control.")> _ Public Property ErrProviderBlinkStyle() As ErrorBlinkStyle Get Return ErrorProvider1.BlinkStyle End Get Set(ByVal Value As ErrorBlinkStyle) ErrorProvider1.BlinkStyle = Value End Set End Property <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("Icon property of the Provider control.")> _ Public Property ErrProviderIcon() As Icon Get Return ErrorProvider1.Icon End Get Set(ByVal Value As Icon) ErrorProvider1.Icon = Value End Set End Property <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("IconAlignment property of the Provider control.")> _ Public Property ErrProviderIconAlignment() As ErrorIconAlignment Get Return ErrorProvider1.GetIconAlignment(Me) End Get Set(ByVal Value As ErrorIconAlignment) ErrorProvider1.SetIconAlignment(Me, Value) End Set End Property <CategoryAttribute("S2iNumericTextBox Properties"), _ DescriptionAttribute("Will the Error Provider control be visible?")> _ Public Property ErrProviderVisible() As Boolean Get Return mblnErrorProviderVisible End Get Set(ByVal Value As Boolean) mblnErrorProviderVisible = Value End Set End Property #End Region '-- Error Provider #End Region '-- Exposed Properties End Class ' NumericTBEventArgs: a custom event inherited from EventArgs. Public Class NumericTBEventArgs Inherits EventArgs Public Sub New(ByVal pMessage As String, ByVal pValue As String) Me.Message = pMessage Me.Value = pValue End Sub Public Message As String Public Value As String End Class 'end of class NumericTBEventArgs>Does Ctrl+V count as one keypress or two keypresses?
>protected override void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e) >{ > switch(e.KeyChar) > { > case '\b': > case '0': > case '1': > case '2': > case '3': > case '4': > case '5': > case '6': > case '7': > case '8': > case '9': > { > e.Handled = false; > }break; >// case System.Windows.Forms.Keys.ControlKey + System.Windows.Forms.Keys.V: >// { >// System.Windows.Forms.MessageBox.Show("ctrl+v"); >// e.Handled = false; >// }break; > default: > { > e.Handled = true; > }break; > } >} >>I have commented out some of the code above, because it doesn't compile. Any hints regarding how I can allow paste would be appreciated.