Comment créer une TextBox numérique ?
Plusieurs solutions existent pour n'autoriser que la saisie de nombres dans un TextBox. La première consiste à vérifier à la validation (c'est par exemple le cas lorsque le focus quitte le contrôle) si la valeur entrée est exacte. Voici un bout de code l'illustrant : Private Sub TextBox_Validate(Cancel As Boolean)
If Not IsNumeric(TextBox.Text) Then Cancel = True MsgBox "Veuillez entrer un nombre !" End If
End Sub Cette méthode permet d'ailleurs de vérifier la validité de n'importe quel contenu, numérique ou pas. Une seconde solution consiste à intercepter les frappes au clavier et d'accepter ou non le caractère qui va être entré dans la zone de texte : Private Sub TextBox_KeyPress(KeyAscii As Integer)
If Not ((KeyAscii >= 48 And KeyAscii <= 57) Or KeyAscii = 8) Then KeyAscii = 0 End If
End Sub Cette seconde technique s'avère pratique pour n'autoriser que la saisie de nombres entiers. Cependant, lorsque la validation est moins simple, sa mise en oeuvre peut facilement devenir complexe. Par exemple, dans le cas de nombres décimaux, lorsque l'utilisateur entre une virgule, il faut s'assurer que celle-ci n'a pas déjà été entrée. De plus, l'utilisateur a toujours la possibilité de "coller" du texte sans qu'une seule vérification ne soit réalisée sur le contenu. Pour éviter cela, on peut "sous-classer" le contrôle afin d'intercepter les opérations du presse-papier. Voici le code à employer permettant de sous-classer autant de contrôles que l'on veut : Code à transcrire dans un module : Option Explicit
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _ ByVal Hwnd As Long, _ ByVal nIndex As Long, _ ByVal dwNewLong As Long) As Long Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" ( _ ByVal lpPrevWndFunc As Long, _ ByVal Hwnd As Long, _ ByVal Msg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long) As Long
Private Const GWL_WNDPROC = (-4) Private Const WM_PASTE = &H302
Private colScControls As New Collection
Public Sub Subclass(hWindow As Long)
Dim OldWndProc As Long
If IsMemberInCollection("hwnd" & hWindow, colScControls) = False Then OldWndProc = SetWindowLong(hWindow, GWL_WNDPROC, AddressOf WndProc) colScControls.Add OldWndProc, "hwnd" & hWindow End If
End Sub
Private Function WndProc(ByVal Hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Dim bReturnOld As Boolean
bReturnOld = True
Select Case uMsg Case WM_PASTE If Not IsNumeric(Clipboard.GetText) Then WndProc = 0 bReturnOld = False End If End Select
If bReturnOld Then WndProc = CallWindowProc(colScControls("hwnd" & Hwnd), Hwnd, uMsg, wParam, lParam)
End Function
Public Sub UnSubclass(hWindow As Long)
If IsMemberInCollection("hwnd" & hWindow, colScControls) Then SetWindowLong hWindow, GWL_WNDPROC, colScControls("hwnd" & hWindow) colScControls.Remove "hwnd" & hWindow End If
End Sub
Private Function IsMemberInCollection(Member, Collection As Collection) As Boolean
Dim TempVal As Variant
On Error Resume Next TempVal = Collection(Member) IsMemberInCollection = (Err.Number = 0) Err.Clear
End Function
Code à transcrire dans une feuille : Option Explicit
Private Sub Form_Load()
Subclass Text1.hWnd
End Sub
Private Sub Form_Unload()
UnSubclass Text1.hWnd
End Sub
Vous trouverez aussi un exemple de sous-classement destiné à intercepter les messages WM_PASTE sur vbVision : LimitEdit Demo. Attention, si vous terminez votre programme en cliquant sur le bouton "End" (Fin), vous ferez planter VB (à moins de disposer d'une DLL de gestion du sous-classement idoine). Quand on sous-classe un contrôle, il faut toujours terminer son programme en cliquant sur la croix du formulaire principal. Une troisième solution consiste à vérifier, après la frappe, le texte entré : Private Sub Text1_Change()
Dim OldSelStart As Long
If Not IsNumeric(Text1.Text) Then OldSelStart = Text1.SelStart Text1.Text = CStr(Val(Text1.Text)) Text1.SelStart = OldSelStart End If
End Sub Cette méthode présente de nombreux désavantages. Notamment, si l'utilisateur appuie par erreur sur la mauvaise touche, il se peut qu'une partie de la valeur introduite soit perdue, le forçant à recommencer la saisie du nombre. De plus, la remise à 0 du texte déclenche à nouveau l'événement Change, ce qui doit être pris en considération si pour des traitements plus complexes, afin d'éviter la répétition du code. Une quatrième méthode consiste à changer le style du contrôle via une API. Il existe en effet un style (ES_NUMBER) qui n'autorise que la saisie de nombres pour une zone de texte : Option Explicit
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" ( _ ByVal hWnd As Long, _ ByVal nIndex As Long) As Long Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _ ByVal hWnd As Long, _ ByVal nIndex As Long, _ ByVal dwNewLong As Long) As Long
Private Const GWL_STYLE = (-16) Private Const ES_NUMBER = &H2000&
Private Sub SetNumeric(txtTextBox As textbox, bNumeric As Boolean)
Dim CurStyle As Long CurStyle = GetWindowLong(txtTextBox.hWnd, GWL_STYLE)
If bNumeric Then SetWindowLong txtTextBox.hWnd, GWL_STYLE, CurStyle Or ES_NUMBER Else SetWindowLong txtTextBox.hWnd, GWL_STYLE, CurStyle And (Not ES_NUMBER) End If
End Sub
Private Sub Command1_Click()
SetNumeric Text1, True
End Sub Malheureusement, ce style n'accepte que les nombres entiers et n'empêche pas l'utilisateur a de "coller" du texte sans vérification. Il faudra donc mettre en place la technique de sous-classement évoquée ci-dessus. Enfin une dernière méthode serait d'employer un contrôle "MaskedEditBox" qui permet de spécifier un masque de saisie pour les données à entrer. Mais ce contrôle n'est pas toujours facile à manipuler et est assez buggé. Cette méthode est donc à déconseiller. Pour conclure, quelle que soit la méthode choisie, sachez qu'il vaut toujours mieux vérifier une dernière fois les données avant de les sauvegarder définitivement. Voir aussi : |