VBA : Débogage, les bonnes pratiques (1)

Dans cette série de billets nous allons délivrer des conseils, détailler des techniques et mettre en pratique le débogage au travers d’exemples simples.

Introduction

Le débogage est l’action de suivre le déroulement du code pour en observer le comportement et corriger des problèmes ou bugs. Tout développement passe par cette phase dont la durée varie suivant la maîtrise du langage.
La facilité de débogage est proportionnelle à la simplicité du code. Faites un code simple, efficace et vous aurez d’autant plus de facilité à le déboguer.

Qu’est-ce que VBE ?

VBE est l’acronyme de Visual Basic Editor, soit l’éditeur Visual Basic Application commun à tous les produits de la suite Office et autre produit intégrant le langage de programmation VBA. C’est avec lui que nous allons travailler tout au long de ces billets.

On y accède au choix après l’ouverture d’un fichier Access :
·         Avec la combinaison de touches Alt + F11,
·         En ouvrant un nouveau module ou un existant,
·         En ouvrant ou créant un événement du type [Procédure Evènementielle] d’un formulaire/état.
·         Pour les versions récentes via le ruban Créer/ Autre /Menu Macro/ Module ou Module de classe.

Cet éditeur comporte un menu, des barres d’outils ainsi que plusieurs fenêtres.

Nous allons aborder tour à tour chacune d’elles au fur et à mesure de notre besoin.

Premiers réglages : Réduire les risques d’erreurs

Le débogage commence avant l’écriture de la moindre ligne de code. En effet, au-delà de la bonne connaissance du produit il existe quelques précautions à prendre pour limiter le risque d’erreurs de codage. Il faut en premier lieu utiliser les meilleurs réglages de l’éditeur VB (Visual Basic Editeur, VBE).

Les réglages de VBE

Avant de commencer l’écriture de la moindre ligne de code vous devez vous assurer des réglages des options de l’éditeur VB.

Vérification automatique de syntaxe

Cette option est intéressante lorsque vous débutez car elle vous signale les erreurs lors du changement de ligne et vous propose un accès à la rubrique d’aide la plus adéquate pour résoudre le problème.
Dans le cas d’une erreur sur la ligne, la ligne apparait en rouge. Si la vérification automatique de syntaxe est active, le mot clef posant problème est signalé et un message d’erreur s’affiche.

Avec la vérification automatique de syntaxe :

Lorsque le curseur n’est plus sur la ligne comportant l’erreur.

Sans la vérification :

Activée ou non la ligne apparaitra en rouge si elle comporte une erreur, la différence est uniquement sur l’affichage ou non du message.

Lorsque vous faites de la concaténation de texte, comme des requêtes SQL dans le code (les plus intégristes me traiteront d’hérétique), ou de la copie de code cette option peut vite devenir agaçante. Personnellement je désactive cette option.

Déclaration des variables obligatoires

Vivement conseillée en toute circonstance, cette option permet d’ajouter sur tous les nouveaux modules, l’instruction Option Explicit en en-tête. Cela oblige le développeur à déclarer chaque variable avant son utilisation. Vous réduisez ainsi les erreurs liées aux mauvais noms de variables souvent causées par des fautes de frappe.
Très fréquente, cette erreur conduit à un code qui ne plante pas mais qui ne fait pas ce qu’on lui demande. C’est souvent le plus difficile à déboguer.
Si vous avez déjà commencé à coder lorsque vous lisez ces lignes, il n’est pas trop tard pour ajouter cette option dans tous vos modules y compris ceux des formulaires et des états, cependant vous devrez le faire manuellement.
Cette instruction vient immédiatement après Option Compare.

Option Compare Database 
Option Explicit

Notez qu’en l’absence de déclaration, une variable est de type Variant comme une variable déclaré sans type. Le type Variant est  très gourmand en mémoire.

Complément automatique des instructions

Cette option ajoute un confort et une rapidité en mettant à disposition la complétion de code. La complétion, souvent désignée sous le terme d’auto-complétion ou intellisense, se comporte de différentes manières.

1er cas d’utilisation

Lorsque vous saisissez un début d’instruction et que vous appuyez simultanément sur CTRL+Espace  plusieurs cas de figure peuvent se produire.
INFORMATION : Les cas sont ceux rencontrés avec les références standards.

Une seule correspondance

Il n’existe qu’une instruction possible, votre saisie est automatique complétée.
Entrez Doc et CTRL+Espace, le mot clé DoCmd s’affiche.

Plusieurs correspondances possibles

Il existe plusieurs instructions qui répondent à cette saisie. Une liste déroulante s’affiche, l’instruction choisie est celle qui correspond à la saisie.
Entrez Do et CTRL+Espace et la liste s’affiche.

Utilisez les touches de déplacement et la tabulation pour valider ou la souri ou encore compléter le mot clef.
Pour annuler utilisez la touche Echap.

2ème cas d’utilisation

Lorsque vous utilisez le nom d’un objet ou d’une classe et que vous saisissez le point, la même liste déroulante s’ouvre avec les choix possibles pour cet objet.

3ème cas d’utilisation

Dans certains cas, comme la déclaration de variables typées, la complétion entre en action après avoir entré le mot clé AS.

On rencontre aussi ce fonctionnement lorsque l’instruction saisie nécessite un paramètre Enum, La méthode « OpenRecordset » de DAO en est un parfait exemple. Voir le tutoriel sur les déclarations Enum ici.

Conclusion
Avec la complétion vous n’aurez plus d’excuses pour avoir inventé des noms de méthodes ou propriétés farfelues. Si vous êtes un peu curieux vous pourrez explorer les composants d’un objet, en action avec l’aide vous découvrirez ainsi de nouvelles méthodes et propriétés.

La complétion fonctionne également avec vos bibliothèques de fonctions, procédures et classes ainsi qu’avec les contrôles, propriétés et méthodes des formulaires et états.
La complétion ne fonctionne pas dans les cas suivants :
• Quand il y a une erreur identifiée (ligne en rouge) non corrigée.
• L’objet que vous utilisez n’existe pas (bibliothèque non référencée, nom farfelu…)
• L’objet n’est pas typé précisément (Late Binding) c’est le cas pour Variant et Object par exemple.

Notez que dans ce dernier cas vous pouvez utilisez la méthode « Early Binding » durant la conception, puis le transformer en « Late Binding » par la suite. Cette technique est décrite dans le billet traitant du Binding.

La complétion est donc essentielle pour éviter les fautes de frappe et l’utilisation de propriétés ou méthodes inexistantes mais également dans le perfectionnement de votre apprentissage d’une bibliothèque.

Bon utilisation !

ACCESS : Tri d’enregistrements sur le contenu d’une zone de liste déroulante

Il est fréquent et même commun d’utiliser des listes déroulantes dans des formulaires.

Dans le cas où le contenu affiché n’est pas présent dans la source de données, il est difficile de faire un tri sur cette donnée.

Je vous livre ici une astuce qui va vous permettre de faire un tri de la source du formulaire à partir des données affichées dans la liste.

Admettons une liste nommée LmAdherent dont le champ affiché est nommé Nom_Adherent et provient d’une source externe au formulaire.

Dans ce cas sur l’évènement Sur Ouverture indiqué cette syntaxe :

Me.OrderBy = "[Lookup_LmAdherent].[Nom_Adherent]" Me.OrderByOn = True

On concatène le mot clef « Lookup_ » au nom de la liste déroulante LmAdherent puis un point et le nom de la colonne à trier.

Chaque fois que vous ouvrirez le formulaire, il apparaitra trié sur le contenu Nom_Adherent alors que cette données n’est pas contenu dans la source du formulaire.

Vous pouvez découvrir d’autre syntaxe en observant ce qui se passe dans la propriété OrderBy du formulaire lorsque vous utilisez le tri du menu contextuel.

Bonne utilisation !

VBA : syntaxe avec ! ou .

Lorsque vous parcourez du code VBA, vous pouvez constater que suivant les développeurs, les uns emploieront le point d’exclamation, d’autres le point, ou plus rarement un mélange de points d’exclamation et de points.

Origine du !

Le ! est l’héritage de Access Basic (Access 1.1). Dans VBA on peut encore utiliser quelques instructions Access Basic, uniquement avec les objets survivants de cette époque. Les formulaires et états en sont le plus bel exemple.

L’exemple le plus courant étant celui-ci :

forms!monformulaire

Comportement du !

Ce que l’on sait moins c’est le que le point d’exclamation remplace le membre par défaut. On peut facilement le vérifier au travers de quelques instructions habituelles.

forms!monformulaire!prenom

dont l’équivalent et la signification n’est pas :

forms.monformulaire.controls("prenom")

mais plutôt :

forms.monformulaire.recordset.fields("prenom")

Enfin beaucoup de classes de bibliothèques historiques peuvent utiliser le point d’exclamation comme dans l’exemple suivant avec DAO :

Dim rst as Dao.Recordset 
Set rst = currentDb.OpenRecordset("SELECT monchamp, monchamp1 FROM table;", dbopensnapshot) 
rst!monchamp 

Que l’on traduira par :

rst.fields("monchamp")

Il faut donc être extrêmement vigilant avec l’utilisation de point d’exclamation. Ms ACCESS a suffisamment de faux amis pour ne pas en provoquer.

Le point et ses avantages

Même si utiliser le point implique un code plus verbeux, ce qui n’est pas forcément une mauvaise chose, le point a l’avantage d’être précis et surtout de mettre l’auto complétion à disposition du développeur.

Rien que cet avantage devrait encourager les néophytes à l’employer.

Si vous êtes un mordu du « ! » ou que votre touche « point » n’est pas disponible, vous pouvez toujours regarder dans l‘Explorateur d’objets de VBE (touche F2) pour connaître le membre par défaut d’un objet ou d’une classe. Il est symbolisé par une pastille bleu et sa description est suffisamment claire.

Conclusion

Le point doit être privilégier dans la majorité des cas, cela peut devenir verbeux mais lors d’une relecture du code il apparait être plus clair.

Bonne utilisation !

ACCESS : Liste à choix multiple et instruction sql IN()

Transmettre un paramètre à une requête se fait assez simplement via plusieurs méthodes. Cela reste à la discrétion du développeur avec cependant 2 règles fondamentales à retenir :

  1. L’ihm doit rester pratique pour l’utilisateur, le développeur s’adapte.
  2. Le code doit être le plus simple possible, pensez aux autres qui devront le lire.

Concernant une liste In() le problème de passation de paramètres est un peu plus ardu. Voici comment je procède habituellement, bien évidemment c’est encore à la discrétion du développeur, à chacun sa méthode et ses habitudes.

D’abord concernant la captation des choix de la liste et de leur valeur. La méthode indiquée dans mon tutoriel http://loufab.developpez.com/tutoriels/access/operateur-in/ reste valable à un détail ; il ne traite que des valeurs numériques. Pour du texte voici ce qu’il faut modifier, ce qui, bien que pas très propre, fonctionnera aussi pour du numérique. Notez également que je factorise sous forme d’une fonction pour pouvoir récupérer la liste où bon me semble dans l’application.

Public Function fGetListe(zliste As ListBox) As String 
Dim itm As Variant ' l'item choisi 
Dim lstval As String ' la liste des id sélectionnés 

For Each itm In zliste.ItemsSelected ' parcourt les items 
   'ICI LA LIGNE MODIFIEE 
   lstval = lstval & """" & zliste.ItemData(itm) & """," ' insère chaque valeur TEXTE Next 
fGetListe = Left(lstval, Len(lstval) - 1) 

End Function

Donc l’appel est simple :

... = fGetListe(me.malistederoulante) 
'ou encore ..." & fGetListe(me.malistederoulante) & "...

Pour l’inclusion dans une requête voici comment procéder (extrait tiré d’une de mes applications) :

Dim strChoix As String 
Dim strSql As String 
strSql = "SELECT [Nom] & "" "" & [Prenom] AS Adhérent, " & 
       _ " tAdherent.FonctionAmap, tAdherent.Email, " 
strSql = strSql & " tAdherent.Selection, " & _ 
         "tAdherent.ID_adherent, tAdherent.Nom FROM tAdherent " 
strChoix = fGetListe(Me.lstFonctionAmap) 
If Len(strChoix) > 0 Then 
   strSql = strSql & " WHERE FonctionAmap " & _ 
   " in(" & strChoix & ")" 
End If 
strSql = strSql & " ORDER BY tAdherent.Nom;" 
  • strSql contient la requête
  • strChoix la liste des valeurs

Une fois strSql renseigné on peut imaginer l’utiliser tel quel ou bien l’enregistrer dans un objet requête comme ceci :

Dim Qrd As DAO.QueryDef 'Créer un objet requête 
Set Qrd = CurrentDb.CreateQueryDef("MaNouvelleRequete", strSql) 
'ou modifier un objet requête existant 

'dans ce dernier cas le code SQL existant est simplement remplacé par le nouveau 
Set Qrd = CurrentDb.QueryDefs("UneRequeteExistante") 
Qrd.SQL = strSql 


currentdb.QueryDefs.Refresh 'on force le rafraichissement.

La manipulation des objets requête ne dispose pas de méthode Save pour la sauvegarde car la sauvegarde est implicite.

La liste à choix multiple est souvent boudée parce que moins pratique à poser dans une IHM et plus complexe à exploiter aussi bien pour l’utilisateur (ctrl et shift) que pour le développeur néophyte cependant elle est incontournable pour du choix multiple.

Bonne utilisation !

VBA : Classe et gain de temps

Le concept de classe en VBA a certes des lacunes mais il ne doit pas être négligés pour autant.

En effet par l’utilisation des classes ont peut gagner un temps précieux. Voici un petit exemple fonctionnel d’une classe qui gère les évènements DblClick et NotInList d’une zone de liste déroulante. Le DblClick permet d’ouvrir un formulaire de saisie/édition avec l’item de la liste, tandis que NotInList permet de saisir l’item.

Créer le module de classe

Dans VBE faites Insertion/Module de classe. En bas à gauche dans la fenêtre des propriétés vous devez renseigner le nom de l’instance. cComboBox.

Comment changer un module standard en module de classe.

Instancing permet de définir si la classe n’est visible que dans le fichier courant ou si elle est visible depuis l’extérieur. Laissez 1 – Private.

Déclaration

Sur la ligne qui suit les options, nous allons déclarer les variables nécessaires. Tout d’abord la plus importante, celle qui contiendra l’objet Combobox.

Private WithEvents LmCombo As Access.ComboBox

WithEvents indique que cet objet pourra lever des évènements.

Access.ComboBox est le type Liste déroulante.

Ensuite nous aurons besoin de variables de type String pour stocker différents éléments nécessaires à l’ouverture du formulaire.

'nom du formulaire qui est appelé lors du notinlist 
Private strFormNameLie As String 

'nom du champ id de la table recevant la nouvelle valeur 
Private strIdFieldName As String 

'nom du champ recevant la valeur saisie dans la combo 
Private strControlName As String 

'nom de la table recevant la nouvelle valeur 
Private strTableFormLie As String

En suivant nous écrivons deux méthodes habituelles dans les classes.

Private Sub Class_Initialize() 
' Initialise 
End Sub

La Class_Initialize est exécutée au démarrage de la classe. Elle ne contient rien dans cet exemple mais peu permettre d’initialiser des propriétés ou des variables, exécuter des méthodes …

Private Sub Class_Terminate() 
' libère les variables 
On Error Resume Next 

Set LmCombo = Nothing
 
End Sub

Tandis que Class_Terminate est exécuté lorsqu’on libère la classe, qu’on la décharge. Celle-ci est importante car c’est là qu’on décharge également toutes nos variables.

Pour charger la combo depuis le formulaire il faut utiliser un Setteur. Le voici !

Public Property Set objComboBox(objCombo As Access.ComboBox) 
   Set LmCombo = objCombo 
   LmCombo.OnDblClick = "[Event Procedure]" 
   LmCombo.OnNotInList = "[Event Procedure]" 
End Property

On voit qu’on lui passe un objet combobox et qu’il est chargé dans la variable définie précédemment. Les 2 lignes suivantes permettent d’activer l’écoute des évènements dont nous avons besoin : DblClick et NotInList.

Les autres variables ou propriétés de la classe doivent également être valorisées toujours à l’aide de Setteurs.

Public Property Let frmNameLie(strNomFormulaireLie As String)
   strFormNameLie = strNomFormulaireLie 
End Property
Public Property Let idFieldName(strNomIdFieldFormulaireLie As String) 
   strIdFieldName = strNomIdFieldFormulaireLie 
End Property
Public Property Let controlNameLie(strNomControleFormulaireLie As String)  
   strControlName = strNomControleFormulaireLie 
End Property
Public Property Let tableNameLie(strNomTableFormulaireLie As String) 
   strTableFormLie = strNomTableFormulaireLie 
End Property

Rien de bien particulier à part qu’on utilise un Let réservé aux objets au lieu du Set.

Nos variables sont devenus des propriétés de la classe.

Méthode privée

Une méthode privée permettra d’attendre la fermeture du formulaire de saisie avant de poursuivre l’exécution du code. Nous d’obtiendrons un déroulement synchrone du processus.

Private Sub pAttendreFermeture(vlFrmRprt As Object) 
'----------------------------------------------------------- 
' Procedure : pAttendreFermeture 
' Author : Fabrice CONSTANS 
' Date : 20/10/2011 
' Description : Attend la fermeture de l'objet pour rendre la main 
' Paramètres : vlFr est l'objet à controler 
'----------------------------------------------------------- 
On Error GoTo 
pgAttenteFermeture_Error 

Do 
   DoEvents 
Loop While vlFrmRprt.Visible 

pgAttenteFermeture_Error: 
On Error GoTo 0 

Exit Sub 
End Sub

Les évènements

Les évènements vont être gérés à l’aide de procédure qui deviendront de fait des méthodes de la classe. La différence c’est qu’à aucun moment nous ne devront les appeler pour qu’elles fonctionnent. Ceci grâce au WithEvents de la déclaration et à l’activation des écouteurs.

Le Notinlist

On remarque que la signature de la procédure est strictement identique à celle qui pourrait être générée dans un formulaire. Je vous conseille d’ailleurs d’utiliser des signature générée et de les copier dans la classe.

Public Sub LmCombo_NotInList(NewData As String, Response As Integer) 
'-------------------------------------------------------------------- 
' Procedure : LmContact_NotInList 
' Author : Fabrice CONSTANS (MVP) 
' Date : 21/01/2016 
' Purpose : Nouveau contact, on appelle le formulaire de saisie. 
'------------------------------------------------------------------ 
Dim strNouveauNumero As String 
On Error GoTo Errsub 
If vbYes = MsgBox("Cette valeur n'existe pas. " & _
    "Souhaitez-vous la créer ?", vbInformation + vbYesNo, _ 
    "classe ccombo") Then 

   'ouverture du formulaire en mode Ajout 
   DoCmd.OpenForm strFormNameLie, acNormal, , , acFormAdd 

   'on gèle le contrôle reception de la valeur saisie      
   Forms(strFormNameLie).Controls(strControlName).Enabled = False 

   'on ajoute la valeur saisie dans la liste    
   Forms(strFormNameLie).Controls(strControlName).Value = NewData 

   'traitement synchrone 
   pAttendreFermeture Forms(strFormNameLie) 

   'Vérification que le valeur existe (peut-être que l'utilisateur a 
   ' changé d'avis et a annulé la création 

   If dLookUp("nz(" & strIdFieldName & ",-1)" _ 
      , strTableFormLie, strControlName & "=""" & NewData & """") > 0 Then 

      'la valeur a été saisi 
      Response = acDataErrAdded 
   Else 
      'la valeur n'a pas été saisi (annulation) 
      Response = acDataErrContinue 
      LmCombo.Parent.Undo 
   End If 
Else 
   'il ne souhaite pas la création 
    Response = acDataErrContinue 
    LmCombo.Parent.Undo 
End If 

Exitsub: 
On Error GoTo 0 
Exit Sub 

Errsub: 
msgbox "cComboBox.LmCombo_NotInList", Err, Erl, Err.Description 

End Sub

Vous pouvez constater que nous utilisons bien les constantes habituelles pour ce type d’évènement : NewData, acDataErrContinue, acDataErrAdded

Le DblClick

Cet évènement est plus simple que le précédent puisqu’il ne fait qu’ouvrir le formulaire en mode consultation/modification.
A la sortie on pense à rafraichir le contenu de la combo au cas ou l’utilisateur a modifié une valeur.
Il va de soit que pour cette dernière le traitement doit également être synchrone.

Public Sub LmCombo_DblClick(Cancel As Integer) 
'------------------------------------------------------------------ 
' Procedure : btnEditContact_Click 
' Auteur : Fabrice CONSTANS (MVP) 
' Date : 16/02/2016 
' Purpose : edit et raffraichit la liste au retour 
'------------------------------------------------------------------ ' 
Dim Id As Long 
On Error GoTo Errsub 

If IsNull(LmCombo.Column(0)) Then 
   MsgBox "Pour créer un item vous devez entrer sa valeur.", _ 
          vbInformation + vbOKOnly, "classe ccombo" 
   Exit Sub 
End If 

Id = LmCombo.Column(0) 
DoCmd.OpenForm strFormNameLie, acNormal, , _ 
      strIdFieldName & "=" & Id, acFormEdit 

pAttendreFermeture Forms(strFormNameLie) 
LmCombo.Undo 
LmCombo.Requery 
LmCombo.value = Id 

Exitsub: On Error GoTo 0 
Exit Sub 
Errsub: msgbox ("cComboBox.LmCombo_DblClick", Err, Erl, Err.Description) 

End Sub

Voilà la Classe cCombo est prête à être utilisée. Voyons cela dans les faits.

Utiliser la classe dans un formulaire

En premier lieu il faut indiquer au formulaire que nous allons utiliser cette classe. Pour cela nous devons utiliser autant de variables qu’il y a de zone de liste à implémenter. Ces variables doivent être globale, donc déclarer immédiatement après les 2 lignes d’Options du module de classe du formulaire.

Dim cComboClient As cComboBox

Dans l’événement Sur Ouverture du formulaire on poursuit la déclaration.

Set cComboClient = New cComboBox 
cComboClient.controlNameLie = "RaisonSociale" 
cComboClient.frmNameLie = "fClient" 
cComboClient.idFieldName = "id_Client" 
Set cComboClient.objComboBox = Me.Controls("lmClient") 
cComboClient.tableNameLie = "tClient"

New permet de créer l’instance de la classe et ainsi pouvoir accéder aux propriétés.

Dans cet exemple la liste déroulante se nomme lmClient. Vous pouvez adapter ce code déclaratif à votre propre cas. Vous ne devez jamais modifier la classe pour y intégrer des noms propres à l’application. Une classe doit rester générique sous peine de ne plus être portable.

Il va de soit qu’à chaque nouvelle liste déroulante à instancier, un code similaire devra être ajouté.

Une fois sauvegardé, vous pouvez utiliser le formulaire.

Les tests de comportement à faire :

  • Entrer une nouvelle valeur.
  • Double cliquer sur la zone de liste.

Classe en VBA, quels avantages ?

Le poids de l’application : On remplace des centaines de lignes par une partie déclarative.

L’uniformité du comportement : Comme on utilise le même code pour tous les contrôles nous sommes sûr que le comportement sera la même.

Maintenance et évolution : Si un bug est constaté ou que l’on souhaite faire évoluer le comportement, une seule intervention est nécessaire. Le recopie de code ne sont plus nécessaires avec leur lots d’erreurs potentielles.

Rapidité de conception : Une simple déclaration permet d’exploiter la classe.

Le revers de la médaille : En cas d’erreur non traitée, il peut se produire un déchargement des classes. Il faudra alors exécuter les déclarations une nouvelle fois.

La mise au point peut s’avérer plus complexe qu’avec un code standard, c’est pour cela qu’il est conseillé lorsqu’on débute, de concevoir les traitements dans un formulaire pour ensuite le transformer en classe.

Conclusion

J’espère que ce tuto vous aura plu et qu’il vous donnera des idées d’implémentations pour vos développements.

Bonne utilisation !

VBA : La recherche et les accents en SQL et VBA.

Rechercher dans une base de données avec l’opérateur Like est assez trivial. Avec quelques jokers on arrive à retrouver ce que l’on souhaite. Cependant lorsque on a affaire à des contenus ayant des caractères accentués il est difficile de récupérer à la fois ceux qui en comporte et ceux qui n’en ont pas.

L’objet de ce billet est d’utiliser VBA pour contourner ce problème.

La première chose à faire est de créer une fonction qui va traiter le mot recherché.

Function ConvertAccForLike(strValue As String) As String 
' Fabrice Constans (MVP ACCESS) mars 2016 

If InStr(1, strValue, "a") > 0 Then 
   strValue = Replace(strValue, "a", "[aàâä]") 
End If 
If InStr(1, strValue, "e") > 0 Then 
   strValue = Replace(strValue, "e", "[eéèêë]") 
End If 
If InStr(1, strValue, "i") > 0 Then 
   strValue = Replace(strValue, "i", "[iîï]") 
End If 
If InStr(1, strValue, "o") > 0 Then 
   strValue = Replace(strValue, "o", "[oôö]") 
End If 
If InStr(1, strValue, "u") > 0 Then 
   strValue = Replace(strValue, "u", "[uùûü]") 
End If 
ConvertAccForLike = strValue 

End Function

Dans cette fonction, on recherche la présence des voyelles, une à une, en commençant par le a, ensuite le e, puis le i, etc. Chaque fois que la voyelle est détectée on la remplace par la syntaxe Contient de l’opérateur Like, soit [...]

Pour le mot « eleve », la valeur renvoyée sera :

"[eéèêë]l[eéèêë]v[eéèêë]"

Les mots trouvés seront :

eleve, éleve, élève, élevé…

Vous pouvez l’utiliser directement dans objet requête, une source de formulaire, une clause Where d’un OpenForm ou OpenReport comme dans une requête en VBA.

Voici son utilisation :

SELECT * FROM matable WHERE champ1 Like convertAccForLike("eleve");

Evidemment vous pouvez compléter avec les autres jokers.

SELECT * FROM matable WHERE champ1 Like convertAccForLike("eleve?");

Bonne utilisation !

ACCESS : Champ mémo tronqué lors d’un Export Excel

Si vous avez un jour tenté de lier un source de données MS Access, requête ou table, à Excel, vous avez sûrement été confronté à l’interprétation aléatoire des champs mémo.

Aléatoire ? Pas tant que ça. Il faut savoir que pour réaliser un export à partir du menu DONNEES EXTERNES / Exporter Excel, MS Access ne se base non pas sur la structure du champ mais sur le contenu des premières lignes. Si dans ces premières lignes il rencontre un contenu de plus de 255 caractères il considère ce champ comme mémo (texte long)… et là tout va bien.

Dans le cas contraire, il tronquera tout le reste à 255 caractères qui est la limite du format Texte Court, anciennement Texte. Adieu vos 32 000 caractères suivant !

Info : Un champ Texte Long, anciennement  Mémo, de la base de données 
Jet (Ms ACCESS) et d'une capacité de 65 535 caractères exactement.
Une cellule MS EXCEL en contient tout au plus 32 767.
Il en manquera toujours un peu.

On peut donc oublier l’export CSV ou depuis la commande MS Access, à part faire un tri sur le nombre de caractères de votre champ mémo, si techniquement cela ne pose aucun problème fonctionnellement cela peut ne pas convenir. On ne sera jamais sûr d’avoir la totalité du contenu.

Heureusement, il existe un méthode, certes un peu plus complexe mais également plus efficace. Il s’agit de l’automation. Autrement dit, piloter Excel depuis Access. Il existe une méthode de copie de recordset disponible avec VBA Excel.

CopyFromRecordset

Cette méthode est disponible dans Excel sans ajout de bibliothèque (Références) car il faut le savoir, Excel est un consommateur de Recordset à ses heures.

Voici le code commenté :

Function fInsertInSheet(ByVal strPath As String, ByVal strFeuille As String, rst As Recordset2) As Boolean 
'----------------------------------------------------------------- 
' Procedure : fInsertInSheet 
' Author : Fabrice CONSTANS (MVP) 
' Date : 21/01/2014 
' Purpose : Insère un ou plusieurs enregistrements issu de RST 
' dans la feuille excel strFeuille du fichier strPath 
' utilise fFieldFormated() et ADODB 
' Parameters: strPath = chemin+nom du fichier Xls au format 12.0 
' strFeuille = la feuille dans laquelle insérer l'enrg 
' rst = le recordset contenant les données à insérer 
' Return : Boolean renvoi vrai si insertion réussie 
'----------------------------------------------------------------- 

'Ecrit le recordset transmis dans la feuille indiquée 

Dim strSql As String 
Dim i As Long 
Dim l As Long
 
'Late Binding 
Dim oExcel As Object ' Excel application 
Dim oFeuille As Object ' la feuille 
Dim oWork As Object ' le workbook 
Dim boolStateDisplayAlerts As Boolean 
Dim boolStateAskToUpdateLinks As Boolean 

On Error GoTo Errsub 
Set oExcel = CreateObject("Excel.Application") 
oExcel.Visible = False 
'enregistre l'état 
boolStateDisplayAlerts = oExcel.DisplayAlerts 
boolStateAskToUpdateLinks = oExcel.AskToUpdateLinks 
'met en mode silentieux 
oExcel.AskToUpdateLinks = False
oExcel.DisplayAlerts = False 

Set oWork = oExcel.Workbooks.Open(strPath) ' ouvre le classeur 
Set oFeuille = oWork.Sheets(strFeuille) ' active la feuille 
'xlByRows, xlPrevious l = 1 

'insertion en ligne 1 
oFeuille.Cells(l, 1).CopyFromRecordset rst 'copie recordset 
oExcel.Windows(1).Visible = True 
oWork.Save 'on le sauve 

'remet à l'état d'origine 
oExcel.DisplayAlerts = boolStateDisplayAlerts 
oExcel.AskToUpdateLinks = boolStateAskToUpdateLinks 
oExcel.Visible = True 
oExcel.Quit 

Set oFeuille = Nothing ' vide les objets xls 
Set oWork = Nothing 
Set oExcel = Nothing 
fInsertInSheet = True 'ça c'est bien passé ! 

Exitsub: 
On Error GoTo 0 Exit Function 

Errsub: 
fInsertInSheet = False 'il y a un problème 
'mettre ici une gestion d'erreur ou un msg 
End Function

Conclusion

Voilà un code pas si mystérieux où l’on ouvre un recordset coté Access pour le copier dans la feuille Excel. Plus d’informations sur le LateBinding.

Bonne utilisation !

ACCESS : Répéter des données dans un état

La répétition de données dans un état peut être une nécessité, c’est souvent le cas pour l’édition d’étiquettes, de formulaires en plusieurs exemplaires ou tout simplement pour des codes barres.

Plusieurs solutions

La première, qui fait plus office de bricolage consiste à dupliquer les enregistrements à la source. Cependant cela nécessite d’avoir une table source dévouée à l’impression, de préparer l’impression en amont et bien sûr de consacrer du temps SGBD et serveur pour cette tâche.

La seconde, celle que nous allons mettre en pratique consiste à utiliser le moteur d’impression des états d’ACCESS pour simuler cette duplication.

Comment procéder ?

Admettons une table composé des 2 colonnes suivantes :

  • ValeurAImprimer qui contient les données à imprimer. (Texte 255)
  • FrequenceImpression un code qui détermine la fréquence d’impression. (Texte 50)

On commence par créer un état simplement basé sur cette table, on cache la zone de texte FréquenceImpression en mettant sa propriété Visible à Non.

Le décors est planté, il n’y a plus qu’à insérer le code VBA correspondant.

Le code

Le code exploite les évènements Sur Ouverture et Sur Formatage, une variable globale, une petite fonction qui peut être facultative comme nous le verrons plus tard et utilise la propriété NextRecord propre aux états ACCESS.

La variable globale va permettre de compter les occurrences imprimées.

Option Compare Database 
Option Explicit 

Dim cpt As Long

Lors de l’ouverture de l’état la variable est initialisée à 1.

Private Sub Report_Open(Cancel As Integer) 
   cpt = 1 
End Sub

A chaque préparation de l’impression de la zone détail on analyse ce qui doit être effectué.

Private Sub Détail_Format(Cancel As Integer, FormatCount As Integer) 
   cpt = cpt + 1 
   If cpt <= nbrRepeat(Me.FrequenceImpression) Then 
      Me.NextRecord = False 
   Else 
      cpt = 1 
   End If 
End Sub

Si le compteur (cpt) n’a pas atteint le nombre de répétition souhaité on réimprime le même enregistrement.
Ce tour de force est effectué grâce à la propriété NextRecord qui, si elle est False, ne charge pas l’enregistrement suivant.

Le nombre de répétition est déterminé par la valeur contenue dans FrequenceImpression.
Dans cet exemple on veut imprimer suivant une périodicité une quantité déterminée d’informations.

Function nbrRepeat(period As String) 
    Select Case period 
       Case "trimestriel" 
          nbrRepeat = 3 
       Case "annuel" 
          nbrRepeat = 1 
       Case "semestriel" 
          nbrRepeat = 2 
    End Select 
End Function

Par exemple si FrequenceImpression contient « trimestriel » l’enregistrement sera imprimé 3 fois.

Notez que cette dernière fonction peut être paramétrée à votre guise, ne pas exister du tout si vous indiquez directement le nombre dans la colonne FrequenceImpression ou si le nombre d’impression est fixe.

Bonne utilisation !

ACCESS : Se positionner sur l’item d’une liste déroulante en entrant une partie du texte.

Dernièrement, un internaute me demandait s’il était possible de modifier le fonctionnement de l’auto complétion dans les listes déroulante. S’agissant d’une fonctionnalité interne d’ACCESS, il est évident que ce n’est pas possible nativement.

Par contre on peut toujours contourner le problème à l’aide du code VBA. Voici cette astuce basée sur l’utilisation de l’évènement KeyPress (touche pressée) de la liste.

On commence par créer une variable globale dans le formulaire pour stocker les touches pressées.

Option Compare Database Option Explicit Dim strList As String 'stocke les keycode

A chaque touche tapée par l’utilisateur, on parcourt la liste à la recherche du premier item correspondant aux caractères saisis.

Private Sub Modifiable1_KeyPress(KeyAscii As Integer) 
   Dim i As Long 'contrôle les touches 
   If KeyAscii = 27 Then strList = ""  'Sur Echap vide le contenu 
   If Not (KeyAscii > 64 And KeyAscii < 123) Then 
      Exit Sub 'ce n'est pas un caractère A-Z a-z (à affiner) 
   End if
   strList = strList & Chr(KeyAscii) 'ajoute la touche pressée 
   For i = 0 To Me.Modifiable1.ListCount - 1 'parcours les items 
      If Me.Modifiable1.Column(1, i) Like "*" & strList & "*" Then 
         'l'item correspond 
         'pour un liste  Me.Modifiable1.ListIndex = i 'on s'y positionne 
         'pour une liste déroulante 
         Me.Modifiable1 = Me.Modifiable1.Column(0, i) 
         Exit For 'et on sort 
      End If 
   Next 
End Sub

La méthode suivante donne la valeur de la colonne 2 de la ligne i.

Column(1, i)

Like est un opérateur logique commun à SQL et VBA.

Like "*" & strList & "*"

Par sécurité on vide les caractères saisis sur la prise et la perte du focus.

Private Sub Modifiable1_GotFocus() 
   strList = "" 'perte du focus on vide la liste de touche 
End Sub 

Private Sub Modifiable1_LostFocus() 
   strList = "" 'perte du focus on vide les keycode 
End Sub

Comme vous le voyez, rien de complexe, on utilise juste les nombreuses possibilités d’Access pour contourner le problème. Il va sans dire que l’auto complétion classique de la liste fonctionne toujours.

N’hésitez pas à affiner le contrôle des touches tapées, en effet, dans cet exemple seul l’alphabet classique est pris en compte, il manque les caractères accentués, les chiffres …

Avertissement

L’utilisation de like successif, 1 like par touche pressée, est très consommatrice pour la base de données. Il peut y avoir de très fort ralentissement sur de gros volumes de données. A vous de juger si il est vraiment opportun d’utiliser cette méthode.

Bonne utilisation !

VBA : Arguments multiples pour un même paramètre par l’exemple

Certaines syntaxes permettent de passer plusieurs arguments dans le paramètre. C’est le cas de la commande MsgBox par exemple, où le paramètre Buttons accepte plusieurs valeurs de l’énumérateur vbMsgBoxStyle.

Il est assez simple d’utiliser cette technique dans vos propres commandes ou fonctions. Voici comment procéder :

L’énumérateur VBA

Commencez par créer un module standard pour accueillir la fonction. Dans l’en-tête, après la ligne Option Explicit, nous allons définir l’énumérateur. L’énumérateur est une simple structure de données de variable.





Option Compare Database
Option Explicit 

Public Enum efFichierExt 
       Unite = 8 
       Chemin = 16 
       Fichier = 32 
       Extension = 64 
End Enum

Pour en savoir plus sur l’énumération en VBA, son potentiel et ses bienfaits dans votre code, consultez mon tutoriel : http://loufab.developpez.com/tutoriels/access/enumVBA/

Il est très important que les valeurs de l’enum soit en base 8, comme vous le verrez par la suite.

La fonction

L’écriture de la fonction est assez triviale. La signature de la fonction fait référence à l’enum comme un type classique.

Public Function fFichierExt(strCheminFichier As String, _
iType As efFichierExt) As String


Le paramètre iType est donc de type efFichierExt, notre enum.

Maintenant, pour tester la présence des différentes valeurs dans iType, il ne faut pas utiliser les opérateurs logiques (=, <>…) habituels mais AND.

Voici comme on procède :

If iType And Unite Then ' l'unité 
   vRetour = Left(strCheminFichier, InStr(strCheminFichier, ":")) 
End If

iType And Unite est l’équivalent de « Est-ce que iType contient Unite ? »

Voici la fonction complète qui permet d’extraire de parties du chemin d’un fichier.

Public Function fFichierExt(strCheminFichier As String, _
                            iType As efFichierExt) As String 
'-------------------------------------------------------------- 
' Procedure : fFichierExt 
' Author : Fabrice CONSTANS (MVP) 
' Date : 13/03/2013 
' Purpose : Retourne l'un des éléments suivant le chemin/fichier passé 
' Parametres: 
' strCheminFichier contient le chemin et fichier 
' strType = enum eTypeFichierExt 
' 64 renvoi l'extension du fichier sans le point 
' 32 renvoi le nom du fichier sans son extension 
' 16 renvoi le chemin sans le nom ni l'extension 
' 8 renvoi l'unité 
'-------------------------------------------------------------- 
On Error GoTo Errsub 
Dim vRetour As String 
If iType And Unite Then ' l'unité 
   vRetour = Left(strCheminFichier, InStr(strCheminFichier, ":")) 
End If 
If iType And Chemin Then ' le chemin sans l'unité logique
   vRetour = vRetour & Mid(strCheminFichier, 3, InStrRev(strCheminFichier, "\") - 2) 
End If 
If iType And Fichier Then 
   Dim tmpFic As String 
   If strCheminFichier Like "*.*" Then 
      tmpFic = Right(strCheminFichier, 
             _ Len(strCheminFichier) - InStrRev(strCheminFichier, "\")) 
      vRetour = vRetour & Left(tmpFic, InStrRev(tmpFic, ".") - 1) 
   Else 
      vRetour = strCheminFichier 
   End If
End If 
If iType And Extension Then ' renvoi l'extension 
   If iType And Fichier Then 
      vRetour = vRetour & "." vRetour = vRetour & Right(strCheminFichier, _                    
                Len(strCheminFichier) - InStrRev(strCheminFichier, ".")) 
   End If 
fFichierExt = vRetour 
Exit Function 

Errsub: 'ici utiliser votre propre traitement d'erreur 
Exit Function 

End Function

Comme vous le voyez, iType est analysé plusieurs fois. A chaque analyse on concatène ou non l’élément souhaité.

La première analyse détermine si l’on doit renvoyer l’unité, la deuxième le chemin etc.

L’appel se fait de la manière suivante :

? fFichierExt("c:\windows\temp\monfichier.tmp", Fichier + Extension)

On remarque que, comme pour MsgBox, les paramètres multiples sont additionnés.

Dans cet exemple le résultat sera :

monfichier.tmp

On peut donc renvoyer tout ou partie de la valeur passée. Par exemple :

? fFichierExt("c:\windows\temp\monfichier.tmp", Unite + Chemin + 
             _ Fichier + Extension)

Evidemment, vous pouvez faire l’addition dans l’ordre que vous souhaitez, le résultat sera toujours le même.

? fFichierExt("c:\windows\temp\monfichier.tmp", Extension + Fichier)

Conclusion

L’utilisation d’un enum en base 8 et de l’opérateur logique AND permet de passer autant de paramètres que l’on souhaite. Encore faut-il en avoir l’utilité…

Vous pouvez utiliser la fonction ci-dessus dans vos applications dans la mesure où vous ne modifiez pas le nom de l’auteur.

Bonne utilisation !