← Retourner à la page principale



Astuces, conseils et optimisations en WLangage® *

* Les marques "PC SOFT®" et "WINDEV®" sont déposées par la société PC SOFT®.

Les astuces classiques
 - Effectuer un tri numérique sur une colonne 'chaîne' d'une table
 - Les masques des champs de type 'numérique'
 - L'affectation multiple s'applique aux structures
 - Optimiser les traitements sur un champ avec ..AffichageActif
 - Remplir une chaîne avec un séparateur
 - Transformer un tableau vers une chaîne avec un séparateur
 - Utiliser TableSelect() dans l'événement clic sur colonne 'image'
 - Renseigner un champ d'un haut de rupture avec une valeur d'une colonne
 - Ne pas afficher la fenêtre d'abandon à l'impression
 - Synchroniser le déplacement de deux tables
 - Blocage sur TâcheParallèleAttendToutes() ou ThreadAttend()
 - Empêcher l'enroulé / déroulé sur les nœuds d'un arbre
 - Récupérer le nom de l'élément appelant
 - Corriger l'identifiant automatique en base de données
 - Comprendre l'opérateur d'échange (<=>)
 - Forcer le séparateur des milliers

Les astuces
 - Tester si un fichier existe sans le télécharger
 - Télécharger un fichier sur le web
 - Réduire la taille d'une image
 - Effectuer une requête HTTP de type GET avec autorisation via socket
 - Analyse de la fonction LanceAppliAssociée()
 - Intercepter les frappes au clavier
 - Initialiser plusieurs tableaux avec les mêmes valeurs
 - Contrer la limitation des 2100 paramètres en MSSQL Server
 - Sauver l'état d'un champ table
 - Obtenir le dernier jour d'un mois
 - Ajouter un nombre décimal à une dateheure (année, mois, jour, ...)
 - Extraire les initiales d'un nom ou prénom
 - Effectuer une capture d'écran d'un champ
 - Libérer les ressources
 - Surcharger la fonction HCréationSiInexistant()
 - Surcharger la fonction PDFFusionne()
 - Obtenir le nombre de jours ouvrés ou ouvrants pour une période
 - Rechercher et remplacer dans un document
 - Savoir si un nombre est contenu dans une chaîne ?
 - Vérifier un calcul mathématique

Les optimisations

Les conseils
 - Opérateur _OU_ et Null
 - Faire un HDésactiveFiltre avant et après un POUR TOUT
 - Libérer en mémoire les requêtes utilisées
 - Utiliser les requêtes préparées
 - Optimisation de l'accès aux données en HFSQL Classic
 - Code à l'initialisation du projet


Les astuces classiques


Comment effectuer un tri numérique sur une colonne de type chaîne d'un champ table ?

NomChampTable.NomColonne..OptionTri = ccRespecteNumérique
TableTrie(NomChampTable, "+"+NomChampTable.NomColonne..NomComplet)

Les masques des champs de type numérique ou monétaire (un champ de saisie, une colonne dans un champ table) :

- Le masque "099,99" affiche un résultat de la forme "012.34" et pas "12.34".
- Le masque "999,00" affiche un résultat de la forme "999" et pas "999.00".
- Le masque "+999;-999" affiche un résultat de la forme "+999" et pas "999".
- Le masque "999,99%" affiche "12.3%" et retourne un résultat de la forme "0.123".
- Le masque "999,99%%" affiche "12.3%" et retourne un résultat de la forme "12.3".


L'affectation multiple s'applique aux structures

ST_Point3D est une Structure
    nX est un entier
    nY est un entier
    nZ est un entier
FIN
stUnPoint3D est un ST_Point3D = [0, 0, 50]

Optimiser le temps d'exécution pour les traitements dans lesquels un champ doit être manipulé grâce à la propriété ..AffichageActif

L'affichage du champ ou de la fenêtre n'est demandé qu'après le traitement.

NomChampTable..AffichageActif = Faux
POUR i = 1 À 1000
    TableAjouteLigne(NomChampTable, TraitementPourLaLigne(i))
FIN
NomChampTable..AffichageActif = Vrai

Remplissage d'une chaîne avec des éléments séparés par un séparateur

Le séparateur n'est pas concaténé si la chaîne de départ est une chaîne vide.

sUneChaîne est une chaîne
POUR i = 1 À 10
    sUneChaîne += [":"] + NumériqueVersChaîne(i)
FIN 
// La variable sUneChaîne vaut "1:2:3:4:5:6:7:8:9:10"

Comment convertir un tableau vers une chaîne de caractères avec un séparateur particulier ?

tab1 est un tableau de chaîne = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi"]
sUneChaîne est une chaîne = TableauVersChaîne(tab1, ";")
// La variable sUneChaîne vaut "Lundi;Mardi;Mercredi;Jeudi;Vendredi"

Inversement, pour transformer une chaîne en tableau avec un séparateur connu :

sUneChaîne est une chaîne = "5,4,3,2,1"
tab1 est un tableau de chaîne
ChaîneVersTableau(sUneChaîne, tab1, ",")
// La variable tab1 vaut ["5", "4", "3", "2", "1"]

Comment récupérer une valeur cohérente avec la fonction TableSelect() utilisée dans le code de l'événement clic sur une colonne de type image d'un champ table ?

// A placer dans le code de l'événement clic sur une colonne de type image d'un champ table
TableSelectPlus(NomChampTable, NomChampTable)
Trace( TableSelect(NomChampTable) )

Comment renseigner par programmation un champ dans un haut de rupture avec une valeur d'une colonne d'un champ table ?

// A placer dans le code de l'affichage d'une ligne du haut de rupture dans un champ table
NomChampTable[TableIndiceRupture(NomHautDeRupture)].NomChamp = NomChampTable.NomColonne

Comment ne pas afficher la fenêtre d'abandon dans le visualisateur de rapports (aperçu avant impression) d'un état ?

Cette fonction doit être appelée avant le début d'une impression (exemple : avant la fonction iImprime() ou iImprimeEtat(), ...).

iFenêtreAbandon(Faux)

Comment synchroniser le déplacement de deux champs tables ?

Voici comment intercepter le déplacement de l'ascenseur dans la table 1 pour le transmettre à la table 2 :
Dans le code d'initialisation du champ Table1 :

// On intercepte le déplacement de l'ascenseur
Evénement("DeplaceTable1", NomChampTable1..Nom, 276) // WM_HSCROLL=276

Dans une nouvelle procédure locale nommée "DeplaceTable1" :

// On transmet ce déplacement à une seconde table
PROCÉDURE DeplaceTable1(nMessage, WPARAM , LPARAM)
SendMessage(Handle(NomChampTable2..Nom), nMessage, WPARAM, LPARAM)

Depuis la version 19 de WINDEV® et grâce à la nouvelle fonction AscenseurPosition() :

AscenseurPosition(NomChampTable2, ascHorz, AscenseurPosition(NomChampTable1, ascHorz))

Les fonctions TâcheParallèleAttendToutes() et ThreadAttend() ne fonctionnent pas ?

S'il y a une utilisation de l'IHM (thread principal) dans une des tâches parallèles alors la fonction TâcheParallèleAttendToutes() ne fonctionnera pas. Il faut alors utiliser le code ci-dessous.

tabTP est un tableau de TâcheParallèle

// La ligne ci-dessous ne fonctionne pas
//TâcheParallèleAttendToutes(tabTP)

nIndice est un entier = 1
TANTQUE (nIndice = 1 À tabTP..Occurrence)
    SI tabTP[nIndice]..Terminée ALORS
        nIndice++
    SINON
        Temporisation(25, tempoSourisEtClavier)
    FIN
FIN

S'il y a une utilisation de l'IHM (thread principal) dans le thread secondaire alors la fonction ThreadAttend() sera bloquante. Il faut alors utiliser le code ci-dessous.

// La ligne ci-dessous ne fonctionne pas 
//ThreadAttend(sThreadIdentifiant, 100)

nChrono est un entier = DonneIdentifiant()
ChronoDébut(nChrono)
// Si le thread secondaire n'existe plus ou que la durée d'attente est supérieure à 1 seconde alors sortir de la boucle
TANTQUE (ThreadEtat(sThreadIdentifiant) <> threadInexistant) _ET_ (ChronoValeur(nChrono) < 100)
    Temporisation(5, tempoSourisEtClavier)
FIN
ChronoFin(nChrono)

L’exécution d'une fonction d'attente (ThreadPause / Multitâche / SignalAttend / ...) provoque un interblocage (deadlock).


Comment empêcher l'utilisateur d'enrouler et de dérouler les branches/nœuds d'un champ arbre ?

Dans le code de l'événement "Enroulé, déroulé d'un nœud" d'un champ arbre, ajouter le code suivant :

 RENVOYER Faux

Comment récupérer le nom complet de l'élément appelant ?

Trace( dbgInfo(dbgElément, dbgTraitementAppelant) )

Que faire si l’identifiant automatique en base de données est erroné ?

HLitDernier(NomTableBDD)
SI PAS HEnDehors(NomTableBDD) ALORS
     // L’identifiant automatique est fixé au dernier enregistrement
     HModifie(NomTableBDD, hNumEnrEnCours, hFixeIdAuto)
FIN

Comprendre l'opérateur d'échange (<=>)

L'opérateur d'échange permet d'échanger le contenu de deux éléments.

// Affecte la valeur 5 à la variable i et la valeur 6 à la variable j
i, j est un entier = (5, 6) 

// Le résultat de la trace vaut "i=5 j=6"
Trace("i="+i, "j="+j)

i <=> j

// Le résultat de la trace vaut "i=6 j=5"
Trace("i="+i, "j="+j)

Comment forcer le séparateur des milliers dans les masques des champs de type numérique ?

Le séparateur des miliers peut être un séparateur insécable, ce qui peut fausser l'affichage dans les états ou dans les champs.

// Si le séparateur insécable - Caract(160) [Windows 11] - est le séparateur des milliers alors ...
// ... forcer le séparateur des milliers par un Espace - caract(32) [Windows 10] -
SI (Milieu(NumériqueVersChaîne(1000, "4S"), 2, 1) = Caract(160)) ALORS 
     ChangeSéparateur(sepMILLIER, Caract(32)) 
FIN


Les astuces


Comment tester l'existence d'un fichier sur le web sans le télécharger ?

sUrl_à_tester est une chaîne = "<URL>"
sUrl_urlDomaine est une chaîne = URLExtraitChemin(sUrl_à_tester, urlDomaine)
sUrl_urlCheminComplet est une chaîne = URLExtraitChemin(sUrl_à_tester, urlCheminRessource+urlNomRessource+urlExtensionRessource)
sMessage est une chaîne = [
HEAD /%1 HTTP/1.1
Host: %2
Connection: Close
] // Message à transmettre
sMessage = ChaîneConstruit(sMessage, sUrl_urlCheminComplet, sUrl_urlDomaine) +RC+RC
SI SocketExiste("MaSocketHTTP") ALORS 
    SocketFerme("MaSocketHTTP")
FIN
SI SocketConnecte("MaSocketHTTP", 80, sUrl_urlDomaine, 5000) ALORS
    // Le marqueur de fin est la chaîne de caractères "<EOF>"
    SocketChangeTransmissionMode("MaSocketHTTP", SocketMarqueurFin)
    SI SocketEcrit("MaSocketHTTP", sMessage) ALORS
        Trace( ExtraitChaîne(SocketLit("MaSocketHTTP"), 2, " ") )
    FIN
    SocketFerme("MaSocketHTTP")
FIN

Depuis la version 20 de WINDEV® et grâce aux nouvelles variables httpRequête et httpRéponse :

cMaRequête est un httpRequête
cMaRequête..URL = "<URL>"
cMaRequête..Méthode = httpHead

cMaRéponse est un httpRéponse = HTTPEnvoie(cMaRequête)
SI PAS ErreurDétectée() _ET_ (cMaRéponse..CodeEtat = 200) ALORS
    Trace( cMaRéponse..CodeEtat )
FIN

Comment télécharger un fichier depuis le web ?

cMaRequête est un httpRequête
cMaRequête..URL = "<URL>" // Lien vers un fichier sur le web

cMaRéponse est un httpRéponse = HTTPEnvoie(cMaRequête)
SI PAS ErreurDétectée() _ET_ (cMaRéponse..CodeEtat = 200) ALORS
    
    sTempFichier est une chaîne = fRepTemp() +[fSep]+ fExtraitChemin(cMaRequête.URL, fFichier+fExtension)
    SI fSauveTexte(sTempFichier, cMaRéponse..Contenu) ALORS
        Trace(sTempFichier)
    FIN
    
FIN

Comment réduire la taille (poids) et les dimensions (largeur et hauteur) d'une image ?

sImageOrigine est une chaîne = "C:\logo.jpg" // lien vers un fichier image
sTempFichier est une chaîne = fRepTemp() +[fSep]+ fExtraitChemin(sImageOrigine, fFichier+fExtension)

SI fEstUneImage(sTempFichier) ALORS
    img est une Image = dChargeImage(sImageOrigine) // Charge le fichier dans une variable de type Image
    dRedimensionne(img, img..Largeur/2, img..Hauteur/2, drHauteQualite) // Permet de réduire les dimensions de l'image
    dSauveImageJPEG(img, sTempFichier, 60) // Taux de compression de 40 %
    Trace(sTempFichier)
FIN

Comment effectuer une requête HTTP de type GET avec autorisation via socket ?

// Effectuer une requête HTTP de type GET avec autorisation via socket
sMessage est une chaîne = [
GET %1 HTTP/1.1
Host: %2
User-Agent: %3
Connection: keep-alive
Authorization: Basic %4
] // Message à transmettre
sMessage = ChaîneConstruit(sMessage, "<URL>", "UneAdresseIP", "", Crypte("UnIdentifiant:UnMotDePasse", "", crypteAnsi, encodeBASE64)) +RC+RC
SI SocketExiste("MaSocketHTTP") ALORS 
    SocketFerme("MaSocketHTTP")
FIN
SI SocketConnecte("MaSocketHTTP", UnNuméroDePort, "UneAdresseIP", 5000) ALORS
    // Aucun marqueur n'est ajouté/enlevé au message
    SocketChangeModeTransmission("MaSocketHTTP", SocketSansMarqueurFin)
    SI SocketEcrit("MaSocketHTTP", sMessage) ALORS
        Trace( SocketLit("MaSocketHTTP", Vrai) )
    FIN
FIN

Que fait le système d'exploitation Windows lorsque l'utilisateur effectue un clic droit sur un fichier et sélectionne "Imprimer" ?

Nous allons prendre comme exemple un fichier au format PDF :
- Ouvrir la base de registre grâce à la commande "regedit"
- Accéder à "HKEY_CLASSES_ROOT\.pdf"
- Lire le contenu de la chaîne par défaut, dans mon cas "AcroExch.Document.11"
- Accéder à "HKEY_CLASSES_ROOT\AcroExch.Document.11\shell\Print\command"
- Lire le contenu de la chaîne par défaut

Exemples de contenu de la base de registre pour l'impression d'un fichier PDF :
"C:\Program Files (x86)\Adobe\Reader 11.0\Reader\AcroRd32.exe" /p /h "%1"
"C:\Program Files (x86)\Foxit Software\Foxit Reader\Foxit Reader.exe" /p "%1"
"C:\Program Files\Adobe\Acrobat DC\Acrobat\Acrobat.exe" /p /h "%1"

Sous l'AGL WINDEV®, le code suivant exécute les étapes ci-dessus :

LanceAppliAssociée(sCheminEtNomDuFichier, "print")

Comment intercepter une frappe au clavier en dehors d'une fenêtre WINDEV® ?

Si vous souhaitez intercepter les touches enfoncées du clavier en dehors d'une interface WINDEV®, voici un petit exemple :
Dans le code d'initialisation du projet :

EXTERNE "keyconst.wl" // Constantes standard utilisées pour les touches du clavier
EXTERNE "winconst.wl" // Constantes standard
gnHandleHook est un entier système
gnHandleHook = API("User32", "SetWindowsHookExA", WH_KEYBOARD_LL, &KeyboardHook, Instance(), 0) 

Dans le code de fermeture du projet :

// Retirer la procédure de traitement de la chaîne de procédures de traitement
API("user32","UnhookWindowsHookEx", gnHandleHook) 

Création d'une procédure globale :

PROCÉDURE KeyboardHook(nCode est un entier système, wparam est un entier système, lparam est un entier système)
nCaractTouche est un entier système
SI (nCode = 0) ALORS
    Transfert(&nCaractTouche, lparam, 4)
    // ... Le traitement à exécuter
    Trace( nCaractTouche, ToucheEnfoncée(VK_LCONTROL, Vrai), ToucheEnfoncée(VK_LSHIFT, Vrai) )
FIN
RENVOYER API("user32", "CallNextHookEx", gnHandleHook, nCode, wparam, lparam)
// Renvoyer Vrai afin de permettre le traitement de l'appui par la fenêtre d'origine

Initialiser plusieurs tableaux avec les mêmes valeurs ?

Si vous souhaitez optimiser l'initialisation de plusieurs variables de type tableau avec les mêmes valeurs, voici un petit exemple :

tab1, tab2, tab3, tab4, tab5 est un tableau de chaîne

// Code non optimisé :
POUR i = 1 À 100 000
    TableauAjoute(tab1, NumériqueVersChaîne(i))
    TableauAjoute(tab2, NumériqueVersChaîne(i))
    TableauAjoute(tab3, NumériqueVersChaîne(i))
    TableauAjoute(tab4, NumériqueVersChaîne(i))
    TableauAjoute(tab5, NumériqueVersChaîne(i))
FIN

// Code optimisé :
POUR i = 1 À 100 000
    TableauAjoute(tab1, NumériqueVersChaîne(i))
FIN
TableauCopie(tab1, tab2)
TableauCopie(tab1, tab3)
TableauCopie(tab1, tab4)
TableauCopie(tab1, tab5)

En bonus, voici le code pour optimiser la déclaration d'une variable de type structure d'un tableau de structure comportant un ou plusieurs tableaux :

ST_Structure est une Structure
    nEntier est un entier
    schaîne est une chaîne
    tabchaîne est un tableau de chaîne
FIN
gtabStructure est un tableau de ST_Structure
PROCÉDURE TrtTableau(LOCALE i est un entier)

// Code non optimisé : 
stS est un ST_Structure
stS = gtabStructure[i]
... // Traitement

// Code optimisé : 
stS est un ST_Structure dynamique
stS <- gtabStructure[i]
... // Traitement

Comment contrer la limitation des 2100 paramètres en MSSQL Server en utilisant l'accès natif de WINDEV® ?

sdReq est une Source de Données
sReq est une chaîne = "SELECT * FROM SOCIETE WHERE IDSOCIETE IN ({pListe_IDSOCIETE})"
sListe_IDSOCIETE est une chaîne
POUR i = 1 À 7000
    sListe_IDSOCIETE += [","]+ NumériqueVersChaîne(i)
FIN

// Si la connexion n'est pas sur une base MSSQL Serveur alors exécuter le code classique :
HAnnuleDéclaration(sdReq)
sdReq.pListe_IDSOCIETE = sListe_IDSOCIETE
HExécuteRequêteSQL(sdReq, hRequêteDéfaut, sReq)
POUR TOUT sdReq
    // Traitement
FIN
HAnnuleDéclaration(sdReq)

// Pour contrer la limitation des 2100 paramètres en MSSQL Server en utilisant l'accès natif de WINDEV® : 
// "The incoming tabular data stream (TDS) remote procedure call (RPC) protocol stream is incorrect.  
// Too many parameters were provided in this RPC request. The maximum is 2100."
// Si la connexion est sur une base MSSQL Serveur alors exécuter le code suivant :
sListeIDSOCIETE_MssqlLimiteParam est une chaîne
POUR TOUTE CHAÎNE sUnIDSOCIETE, nPos, nCompteur DE sListe_IDSOCIETE SEPAREE PAR ","
    sListeIDSOCIETE_MssqlLimiteParam += [","]+ sUnIDSOCIETE
    SI (Modulo(nCompteur, 2098) = 0) _OU_ (nCompteur = ChaîneOccurrence(sListe_IDSOCIETE, ",")+1) ALORS 
        
        HAnnuleDéclaration(sdReq)
        sdReq.pListe_IDSOCIETE = sListeIDSOCIETE_MssqlLimiteParam
        HExécuteRequêteSQL(sdReq, hRequêteDéfaut, sReq)
        POUR TOUT sdReq
            // Traitement
        FIN
        HAnnuleDéclaration(sdReq)
        
        sListeIDSOCIETE_MssqlLimiteParam = ""
    FIN
FIN

Pour sauver l'état actuel d'un champ table

Pour sauvegarder l'état d'un champ table :

PROCÉDURE TableSauveParam(champTable est un Champ) : tableau associatif de chaîne

taParam est un tableau associatif de chaîne

taParam["Trie"] = TableColonnesTriées(champTable)
taParam["Filtre"] = TableColonnesFiltrées(champTable)
taParam["PosSel"] = TableSauvePositionEtSélection(champTable)
taParam["AscPos_Vert"] = AscenseurPosition(champTable, ascVert)
taParam["AscPos_Horz"] = AscenseurPosition(champTable, ascHorz)

RENVOYER taParam 

Pour restaurer l'état d'un champ table :

PROCÉDURE TableRestaureParam(champTable est un Champ, LOCALE taParam est une tableau associatif de chaîne)

SI (PAS taParam["Trie"]..Vide) _ET_ (PAS taParam["Trie"] ~= "") ALORS  
    sValeur est une chaîne = ""
    POUR TOUTE CHAÎNE sColonne DE taParam["Trie"] SEPAREE PAR TAB
        SI (sColonne[[1]] = "-") ALORS //Sens du tri : décroissant
            sValeur += [TAB]+ "-"+ champTable..NomComplet +"."+ sColonne[[2 À]]
        SINON
            sValeur += [TAB]+ champTable..NomComplet +"."+ sColonne
        FIN
    FIN
    TableTrie(sValeur)
FIN

SI (PAS taParam["Filtre"]..Vide) _ET_ (PAS taParam["Filtre"] ~= "") ALORS
    POUR TOUTE CHAÎNE sFiltre DE taParam["Filtre"] SEPAREE PAR RC
        TableActiveFiltre(champTable..NomComplet +"."+ ExtraitChaîne(sFiltre, 1, ";"), Val(ExtraitChaîne(sFiltre, 2, ";")), ExtraitChaîne(sFiltre, 3, ";"))
    FIN
FIN

SI (PAS taParam["PosSel"]..Vide) _ET_ (PAS taParam["PosSel"] ~= "") ALORS
    TableRestaurePositionEtSélection(champTable, taParam["PosSel"])
FIN
SI (PAS taParam["AscPos_Vert"]..Vide) _ET_ (Val(taParam["AscPos_Vert"]) = 0 À AscenseurPositionMax(champTable, ascVert)) ALORS
    AscenseurPosition(champTable, ascVert, Val(taParam["AscPos_Vert"]))
FIN
SI (PAS taParam["AscPos_Horz"]..Vide) _ET_ (Val(taParam["AscPos_Horz"]) = 0 À AscenseurPositionMax(champTable, ascHorz)) ALORS
    AscenseurPosition(champTable, ascHorz, Val(taParam["AscPos_Horz"]))
FIN

Un exemple d'utilisation :

taParam est un tableau associatif de chaîne

taParam = TableSauveParam(NomChampTable)
TableAffiche(NomChampTable, taInit)
TableRestaureParam(NomChampTable, taParam)

Comment calculer le dernier jour d'un mois ?

Pour obtenir le dernier jour d'un mois, il suffit d'affecter le nombre 31 à la propriété ..Jour.

dTemp est une Date
dTemp..Année = 2016
dTemp..Mois = 2
dTemp..Jour = 31
Trace( DateVersChaîne(dTemp) )

Depuis la version 16 de WINDEV® et grâce à la nouvelle fonction DernierJourDuMois() :

dTemp est une Date
dTemp = DernierJourDuMois(2016, 2)
Trace( DateVersChaîne(dTemp) )

Ajouter un nombre décimal d'années, de mois, de jours, d'heures, de minutes et/ou de secondes à une variable de type dateheure ?

WINDEV® permet facilement d'ajouter un nombre entier d'années, de mois, de jours, d'heures, de minutes et de secondes à une variable de type dateheure. Mais il ne permet pas d'ajouter des nombres décimaux/flottants.
Dans une déclaration globale :

ENUM_PERIODE est une énumération
    PERIODE_ANNEE
    PERIODE_MOIS
    PERIODE_JOUR
    PERIODE_HEURE
    PERIODE_MINUTE
    PERIODE_SECONDE
FIN

Dans une procédure :

PROCÉDURE DateHeureAjoute(LOCALE dhAjout est une DateHeure, LOCALE ePeriode est un ENUM_PERIODE, LOCALE xNbAjout est un numérique (*)) : DateHeure

SI (xNbAjout <> 0) ALORS
    
    SELON ePeriode
        CAS ENUM_PERIODE.PERIODE_ANNEE :
            dhAjout..Année += xNbAjout // ((xNbAjout > 0) ? PartieEntière(xNbAjout) SINON ArrondiSupérieur(xNbAjout, 0))
            // Avec comme postulat que 1 an = 12 mois
            dhAjout = DateHeureAjoute(dhAjout, ENUM_PERIODE.PERIODE_MOIS, PartieDécimale(xNbAjout) * 12 * ((xNbAjout < 0) ? -1 SINON 1))
            
        CAS ENUM_PERIODE.PERIODE_MOIS :
            dhAjout..Mois += xNbAjout // ((xNbAjout > 0) ? PartieEntière(xNbAjout) SINON ArrondiSupérieur(xNbAjout, 0))
            // Avec comme postulat que 1 mois = 30 jours
            dhAjout = DateHeureAjoute(dhAjout, ENUM_PERIODE.PERIODE_JOUR, PartieDécimale(xNbAjout) * 30 * ((xNbAjout < 0) ? -1 SINON 1))
            
        CAS ENUM_PERIODE.PERIODE_JOUR :
            dhAjout..Jour += xNbAjout // ((xNbAjout > 0) ? PartieEntière(xNbAjout) SINON ArrondiSupérieur(xNbAjout, 0))
            // Avec comme postulat que 1 jour = 24 heures
            dhAjout = DateHeureAjoute(dhAjout, ENUM_PERIODE.PERIODE_HEURE, PartieDécimale(xNbAjout) * 24 * ((xNbAjout < 0) ? -1 SINON 1))
            
        CAS ENUM_PERIODE.PERIODE_HEURE :
            dhAjout..Heure += xNbAjout // ((xNbAjout > 0) ? PartieEntière(xNbAjout) SINON ArrondiSupérieur(xNbAjout, 0))
            // Avec comme postulat que 1 heure = 60 minutes
            dhAjout = DateHeureAjoute(dhAjout, ENUM_PERIODE.PERIODE_MINUTE, PartieDécimale(xNbAjout) * 60 * ((xNbAjout < 0) ? -1 SINON 1))
            
        CAS ENUM_PERIODE.PERIODE_MINUTE :
            dhAjout..Minute += xNbAjout // ((xNbAjout > 0) ? PartieEntière(xNbAjout) SINON ArrondiSupérieur(xNbAjout, 0))
            // Avec comme postulat que 1 minute = 60 secondes
            dhAjout = DateHeureAjoute(dhAjout, ENUM_PERIODE.PERIODE_SECONDE, PartieDécimale(xNbAjout) * 60 * ((xNbAjout < 0) ? -1 SINON 1))
            
        CAS ENUM_PERIODE.PERIODE_SECONDE :
            dhAjout..Seconde += xNbAjout // ((xNbAjout > 0) ? PartieEntière(xNbAjout) SINON ArrondiSupérieur(xNbAjout, 0))
            
        AUTRE CAS :
    FIN
    
FIN

RENVOYER dhAjout

Un exemple d'utilisation :

// Ajoute 6 mois à la date du 01/01/2021 00h00 : 
Trace( DateHeureAjoute("20210101000000", ENUM_PERIODE.PERIODE_MOIS, 6) )
// Retire 2 jours à la date du 02/03/2020 12h34 : 
Trace( DateHeureAjoute("20200302123400", ENUM_PERIODE.PERIODE_JOUR, -2) )

Comment extraire les initiales d'un nom et/ou d'un prénom ?

// Pour extraire les intiales d'un nom/prénom
PROCÉDURE InitialesExtrait(LOCALE sNom est une chaîne) : chaîne

sInitiale est une chaîne = ""

// Recherche les mots séparés par un espace et/ou un tiret
POUR TOUTE CHAÎNE sUnMot DE sNom SEPAREE PAR [" ", "-"]
    sInitiale += Majuscule(sUnMot[[1]]) +"."
FIN

RENVOYER sInitiale

Un exemple d'utilisation :

Trace( InitialesExtrait("yves dupont") ) // Affiche Y.D.
Trace( InitialesExtrait("Jean-Luc DE-VINCI") ) // Affiche J.L.D.V.

Comment effectuer une capture d'écran d'un champ ?

Ce code effectue une capture d'écran à l'emplacement d'un champ, c'est-à-dire que s'il y a un élément par dessus le champ alors cet élément sera aussi capturé dans l'image.

PROCÉDURE ChampCapture(cUnChamp est un Champ, LOCALE sCheminEtNomDuFichier est une chaîne) : booléen

bufImage est un Buffer

bufImage = dCopieImageEcran(FenIntPosX()+cUnChamp..X, FenIntPosY()+cUnChamp..Y, cUnChamp..Largeur, cUnChamp..Hauteur)
//bufImage = dCopieImageChamp(cUnChamp) // Nouveauté en version 28
SI PAS fEstUneImage(bufImage) ALORS
    RENVOYER Faux
FIN

// Création du répertoire si inexistant
SI PAS fRépertoireExiste(fExtraitChemin(sCheminEtNomDuFichier, fDisque+fRépertoire)) ALORS
    fRepCrée(fExtraitChemin(sCheminEtNomDuFichier, fDisque+fRépertoire))
FIN

RENVOYER fSauveBuffer(sCheminEtNomDuFichier, bufImage)

Un exemple d'utilisation :

Trace( ChampCapture(IMG_Logo, ComplèteRep(fDisqueEnCours())+"capture.jpg") ) 

Comment libérer les ressources ?

Ce code est à utiliser avec la plus grande prudence.

nX est un entier = 0
SI (SysVersionWindows() _DANS_ ("NT 3.5", "NT 4", "NT 5", "XP", "2003S", "2008S", "VISTA")) ALORS
    nX = API("KERNEL32", "GetCurrentProcess")
    API("KERNEL32", "SetProcessWorkingSetSize", nX, -1, -1)
    SI (SysVersionWindows(sysVersionPlateForme) = "NT") ALORS
        API("psapi", "EmptyWorkingSet", nX)
    FIN
FIN

Surcharger la fonction HCréationSiInexistant()

// Créer tous les fichiers de la base de données.
PROCÉDURE HCréationSiInexistant() : booléen

bHCréationSiInexistant est un booléen = Vrai

SI PAS WL.HCréationSiInexistant("*") ALORS
    
    sHListeFichier est chaîne = HListeFichier(hLstNormal)
    POUR TOUTE CHAÎNE sHFichier DE sHListeFichier SEPAREE PAR RC
        QUAND EXCEPTION DANS
            SI PAS WL.HCréationSiInexistant(sHFichier) ALORS
                bHCréationSiInexistant = Faux
            FIN
        FAIRE
            ExceptionActive()
        FIN
    FIN
    
FIN

RENVOYER bHCréationSiInexistant

Surcharger la fonction PDFFusionne()

// Résumé : Surcharge la fonction PDFFusionne() au cas où il n'y ait qu'un élément dans le tableau.
// Paramètres :
//    sFichierPdfResultat (chaîne ANSI) : Nom et chemin complet du fichier PDF à créer. Ce fichier contiendra la fusion des différents fichiers PDF. 
//    tabFichiersPdf (tableau (chaîne ANSI)) : Nom du tableau de chaînes contenant les chemins des fichiers PDF à fusionner.
// Valeur de retour :
//    entier : 1: tout s'est bien passé ; -1 : Erreur sur PDFFusionne() ; -2 Erreur sur fCopieFichier() ; 0 : aucun élément dans le tableau
//
PROCÉDURE PDFFusionne(LOCALE sFichierPdfResultat est une chaîne, LOCALE tabFichiersPdf est un tableau de chaîne) : entier

SI fFichierExiste(sFichierPdfResultat) ALORS
    fSupprime(sFichierPdfResultat, frLectureSeule)
FIN

SI (tabFichiersPdf..Occurrence > 1) ALORS
    SI PAS WL.PDFFusionne(sFichierPdfResultat, tabFichiersPdf) ALORS
        ErreurPropage()
        RENVOYER -1
    FIN
SINON SI (tabFichiersPdf..Occurrence = 1) ALORS
    SI PAS fCopieFichier(tabFichiersPdf[1], sFichierPdfResultat) ALORS
        ErreurPropage()
        RENVOYER -2
    FIN
SINON
    ErreurDéclenche(1, "Aucun élément à fusionner.")
    RENVOYER 0
FIN

RENVOYER fFichierExiste(sFichierPdfResultat)

Calculer le nombre de jours ouvrés ou ouvrants pour une période

Ne pas oublier d'initialiser les jours fériés avec la fonction JourFériéAjoute(). (exemple : https://doc.pcsoft.fr/?1000017304)

// Résumé : Calculer le nombre de jours (ouvrés, ouvrants, personnalisés) pour une période.
//     ! Ne pas oublier d'initialiser les jours fériés avec la fonction JourFériéAjoute() 
// Paramètres :
//    dDebut (date) : date de début de la période de calcul
//    dFin (date) : date de fin de la période de calcul
//    tabJours (tableau) : numéros de jour de la semaine à ne pas prendre en compte (avec 1:Lundi, 2:Mardi, 3:Mercredi, 4:Jeudi, 5:Vendredi, 6:Samedi, 7:Dimanche)
// Valeur de retour :
//    entier : nombre de jours calculés pour la période
//
PROCÉDURE NombreDeJoursOuvrés(LOCALE dDebut est une Date, LOCALE dFin est une Date, LOCALE tabJours est un tableau de entier) : entier

nNombreDeJoursCalculés est un entier = 0

SI DateValide(dDebut) _ET_ DateValide(dFin) ALORS
    
    TANTQUE (dDebut <= dFin)
        SI (TableauCherche(tabJours, tcLinéaire, DateVersJour(dDebut)) = -1) _ET_ PAS JourFérie(dDebut) ALORS
            nNombreDeJoursCalculés++
        FIN
        dDebut..Jour++
    FIN
    
FIN

RENVOYER nNombreDeJoursCalculés

Un exemple d'utilisation :

// Calculer le nombre de jours ouvrés entre le 01/01/2021 et le 31/01/2021 pour une semaine travaillée du lundi au vendredi : 
Trace( NombreDeJoursOuvrés("20210101", "20210131", [6,7]) )
// Calculer le nombre de jours ouvrables entre le 01/01/2021 et le 31/01/2021 pour une semaine travaillée du lundi au vendredi : 
Trace( NombreDeJoursOuvrés("20210101", "20210131", [7]) )
// Calculer le nombre de jours ouvrés entre le 09/01/2021 et le 27/01/2021 pour une semaine travaillée du mardi au samedi : 
Trace( NombreDeJoursOuvrés("20210109", "20210127", [1,7]) )

Comment remplacer des mots dans un document de type traitement de texte ?

PROCÉDURE TraitementDeTexteRemplace(LOCALE sSource est une chaîne, LOCALE sCible est une chaîne, LOCALE taMots est un tableau associatif de chaîne) : booléen

// Copier le fichier Source vers le fichier Cible
SI fFichierExiste(sSource) _ET_ fCopieFichier(sSource, sCible) _ET_ fFichierExiste(sCible) ALORS
    
    SELON Minuscule(fExtraitChemin(sCible, fExtension))
        CAS ".doc", ".docx" : // Word Microsoft Office
        
            QUAND EXCEPTION DANS
                autWorddoc est un objet Automation "Word.Application"
                autWorddoc>>documents>>Open(sCible)
                autWorddoc>>visible = Faux
                autWorddoc>>Selection
                POUR TOUT ELEMENT sMot, sCle DE taMots
                    // http://sepia.developpez.com/office/word/rechercherremplacer/
                    sMot = Remplace(sMot, RC, "^p")
                    sMot = Remplace(sMot, TAB, "^t")
                    autWorddoc>>Selection>>Find>>Execute(sCle, Faux, Vrai, Faux, Faux, Faux, Vrai, 1, Faux, sMot, 2)
                FIN
                autWorddoc>>ActiveDocument>>SaveAs(sCible, 16) // http://msdn.microsoft.com/en-us/library/office/bb238158%28v=office.12%29.aspx
                autWorddoc>>ActiveDocument>>Close()
                RENVOYER Vrai
            FAIRE
                SI (autWorddoc <> Null) ALORS
                    autWorddoc>>ActiveDocument>>Close()
                FIN
            FIN
            
        CAS ".odt" : // OpenOffice ; LibreOffice ; etc...
        
            QUAND EXCEPTION DANS
                // Création d'un service OpenOffice si nécessaire
                pautServiceManager est un objet OLE dynamique = allouer un objet OLE ("com.sun.star.ServiceManager")
                pautDesktop est un objet OLE dynamique = pautServiceManager>>createInstance("com.sun.star.frame.Desktop")
                pautDocument est un objet OLE dynamique
                pautRecherche est un objet OLE dynamique
                tabNoArgs_ est un tableau de 0 Variant = allouer un tableau dynamique de 0 Variant
                tabNoArgs est un tableau de 1 objet Automation dynamique
                sCheminFormate est une chaîne = "file:///"+Remplace(sCible, "\", "/") // Formatage du chemin du fichier
                // Ouverture du fichier dans OpenOffice Writer
                    // Traitement de la visibilité du document
                    tabNoArgs[1] = pautServiceManager>>Bridge_GetStruct("com.sun.star.beans.PropertyValue")
                    tabNoArgs[1]>>Name = "Hidden"
                    tabNoArgs[1]>>Value = Vrai
                pautDocument = pautDesktop>>LoadComponentFromURL(sCheminFormate, "_blank", 0, tabNoArgs)
                SI (pautDocument <> Null) ALORS
                    //oDocument = oDesktop>>getCurrentComponent()
                    
                    POUR TOUT ELEMENT sMot, sCle DE taMots
                        // Créer un objet JeCherche qui contiendra tous les paramètres nécessaires à ce remplacement
                        pautRecherche = pautDocument>>createReplaceDescriptor()
                        // Définir la balise à rechercher dans le document OpenOffice Word
                        pautRecherche>>SearchString = sCle
                        // Définir la valeur de remplacement
                        pautRecherche>>ReplaceString = sMot
                        // Distinguer les majuscules des minuscules dans la recherche
                        pautRecherche>>SearchCaseSensitive = Vrai
                        // Ne rechercher que des mots
                        pautRecherche>>SearchWords = Faux
                        // Rechercher à reculons
                        pautRecherche>>SearchBackwards = Faux
                        // Faire une recherche avec la méthode des expressions régulières
                        pautRecherche>>SearchRegularExpression = Faux
                        // Rechercher des paragraphes d’un style donné par SearchString
                        pautRecherche>>SearchStyles = Faux
                        // Rechercher un texte similaire au texte cherché
                        pautRecherche>>SearchSimilarity = Faux
                        // Remplacer toutes les balises
                        pautDocument>>replaceAll(pautRecherche)
                    FIN
                    // Enregistrement sous... du document (https://wiki.openoffice.org/wiki/FR/Documentation/BASIC_Guide/Saving_a_document)
                    pautDocument>>storeToURL(sCheminFormate, tabNoArgs_)
                    // Fermer le document
                    pautDocument>>Store()
                    pautDocument>>close(Vrai)
                    pautDesktop>>Terminate()
                    RENVOYER Vrai
                FIN
            FAIRE
                SI (pautDocument <> Null) ALORS
                    // Fermer le document
                    pautDocument>>Store()
                    pautDocument>>close(Vrai)
                    pautDesktop>>Terminate()
                FIN
            FIN
            
        AUTRE CAS :
    FIN
    
    // Si échec du rechercher/remplacer alors supprimer le fichier "Cible"
    SI fFichierExiste(sCible) ALORS
        fSupprime(sCible, frLectureSeule)
    FIN
    
FIN

RENVOYER Faux

Un exemple d'utilisation :

sModeleDoc est une chaîne = ComplèteRep(fDisqueEnCours())+"Modele_TraitementDeTexte.doc"
sNouveauDoc est une chaîne = ComplèteRep(fDisqueEnCours())+"Doc_TraitementDeTexte.doc" // ou bien ComplèteRep(fDisqueEnCours())+"Doc_TraitementDeTexte.odt"
taMotsARemplacer est un tableau associatif de chaîne
    taMotsARemplacer["<Mot_a_trouver_1>"] = "Mot 1 a remplacer"
    taMotsARemplacer["<Mot_a_trouver_2>"] = "Mot 2 a remplacer"
    taMotsARemplacer["<Date_Du_Jour>"] = DateVersChaîne(DateSys(), "JJ/MM/AAAA")

SI TraitementDeTexteRemplace(sModeleDoc, sNouveauDoc, taMotsARemplacer) ALORS
    LanceAppliAssociée(sNouveauDoc)
FIN

Depuis la version 22 de WINDEV® et grâce au nouveau type de variable Document :

sModeleDoc est une chaîne = ComplèteRep(fDisqueEnCours())+"Modele_TraitementDeTexte.docx"
sNouveauDoc est une chaîne = ComplèteRep(fDisqueEnCours())+"Doc_TraitementDeTexte.doc" // ou bien ComplèteRep(fDisqueEnCours())+"Doc_TraitementDeTexte.odt"
taMotsARemplacer est un tableau associatif de chaîne
    taMotsARemplacer["<Mot_a_trouver_1>"] = "Mot 1 a remplacer"
    taMotsARemplacer["<Mot_a_trouver_2>"] = "Mot 2 a remplacer"
    taMotsARemplacer["<Date_Du_Jour>"] = DateVersChaîne(DateSys(), "JJ/MM/AAAA")

docT2T est un Document = DocOuvre(sModeleDoc)
SI PAS ErreurDétectée() ALORS
    
    POUR TOUT ELEMENT sMot, sCle DE taMotsARemplacer
        DocRemplace(docT2T, sCle, sMot)
    FIN
    
    SI DocSauve(docT2T, sNouveauDoc) ALORS
        DocFerme(docT2T)
        LanceAppliAssociée(sNouveauDoc)
    FIN
FIN

Comment savoir si un nombre est contenu dans une chaîne ?

Il existe plusieurs approches pour savoir si un nombre particulier est contenu dans une chaîne (une liste de nombres séparés par un séparateur commun).

// La liste de nombres séparés par un séparateur commun
sListe_IDSOCIETE est une chaîne = "4;8;15;16;23;42"
// Le nombre particulier à rechercher dans la chaîne
sUnIDSOCIETE est une chaîne = "2" 

bContient est un booléen

// Code le plus simple ... 
// ... en utilisant la fonction Contient()
bContient = Contient(";"+sListe_IDSOCIETE+";", ";"+sUnIDSOCIETE+";")
Trace("Contient", bContient)
// ... en utilisant la fonction Position()
bContient = (Position(";"+sListe_IDSOCIETE+";", ";"+sUnIDSOCIETE+";") > 0)
Trace("Position", bContient)

// Code le plus rapide grâce à l'opérateur de comparaison [=]
bContient = (";"+sListe_IDSOCIETE+";" [=] ";"+sUnIDSOCIETE+";")
Trace("[=]", bContient)

// Code le plus sûr
tabEntier est un tableau de entier
ChaîneVersTableau(sListe_IDSOCIETE, tabEntier, ";")
bContient = (TableauCherche(tabEntier, tcLinéaire, Val(sUnIDSOCIETE)) <> -1)
Trace("TableauCherche", bContient)

Lors de la recherche sur les chaînes, les concaténations du séparateur (";") sont obligatoires sinon le nombre à rechercher ("2") est trouvé dans les nombres "23" et "42".

Si le développeur ne maitrise pas le contenu de la chaîne (exemples: sListe_IDSOCIETE = "4; 8;15;16;23;42" ou sListe_IDSOCIETE = "4;08;15;16;23;42") alors le code est plus sûr en utilisant un tableau.


Vérifier la validité d'un calcul mathématique

PROCÉDURE VérifierCalcul(LOCALE sCalcul est une chaîne) : (booléen, numérique (*))
sSource est une chaîne = [
    i est un numérique (*) = %1
    RENVOYER i
]
bResultatValide est un booléen
xResutatDuCalcul est un numérique (*)

SI (Compile("CalculerFormule", ChaîneConstruit(sSource, sCalcul)) = "") ALORS
    QUAND EXCEPTION DANS
        xResutatDuCalcul = ExécuteTraitement("CalculerFormule", trtProcédure)
        bResultatValide = Vrai
    FAIRE
    FIN
FIN

RENVOYER (bResultatValide, xResutatDuCalcul)

Un exemple d'utilisation :

Trace( VérifierCalcul("(1+2.5-0.25)") )
Trace( VérifierCalcul("( 3 * 0.5) / 2") )
Trace( VérifierCalcul("1 / 0") )


Les optimisations de code


Non optimisé Optimisé
DateSys() + HeureSys() ou 
DateDuJour() + Maintenant()
DateHeureSys()

// À , OU , ET , DANS ou PAS 
//_À_, _OU_, _ET_, _DANS_ ou _PAS_
NumériqueVersChaîne(i, "03d") 
Droite("00"+i, 3)
// Pour ajouter 1 à un entier 
i = i + 1 // ou 
i += 1 
// Pour ajouter une autre valeur que 1
i += 2 
// Pour ajouter 1 à un entier 
i++ 

// Pour ajouter une autre valeur que 1
i = i + 2 
i est un entier = 1 
TANTQUE (i <= 100 000) 
	...
	i++
FIN
POUR i = 1 _A_ 100 000 
	...
FIN

POUR i = 1 _À_ 100000 _PAS_ 1
    HRAZ(NomTableBDD)
    // Afffectations
    HAjoute(NomTableBDD)
FIN

POUR i = 1 _À_ 100000 _PAS_ 1
    HRAZ(NomTableBDD)
    // Afffectations
    HEcrit(NomTableBDD, HNbEnr(NomTableBDD)+1)
FIN
HRéindexe(NomTableBDD)
HLitPremier(SOCIETE)
TANTQUE PAS HEnDehors(SOCIETE)
    // Traitements
    HLitSuivant(SOCIETE)
FIN



HAnnuleDéclaration(REQ_SELECT_SOCIETE)
HExécuteRequête(REQ_SELECT_SOCIETE)
HLitPremier(REQ_SELECT_SOCIETE)
TANTQUE PAS HEnDehors(REQ_SELECT_SOCIETE)
    // Traitements
    HLitSuivant(REQ_SELECT_SOCIETE)
FIN
HAnnuleDéclaration(REQ_SELECT_SOCIETE)
→ De préférence, utiliser une requête SQL plutôt que les fonctions H*.
→ De préférence, utiliser les fonctions H* plutôt que les fonctions POUR TOUT.
{NomChamp, indChamp}..Largeur = 10
{NomChamp, indChamp}..Hauteur = 20

cUnChamp est un Champ <- {NomChamp, indChamp}
cUnChamp..Largeur = 10
cUnChamp..Hauteur = 20
→ De préférence, utiliser une variable de type champ plutôt que les indirections de champs.
{NomChampTable+".COL_1"}
{NomChampTable+".COL_1", indChamp}
→ De préférence, typer toutes les indirections.
Occurrence(NomChampTable) ou 
TableOccurrence(NomChampTable) ou 
NomChampTable.Occurrence()

ChampActif(NomChampTable)
ChampGrise(NomChampTable)
ChampInvisible(NomChampTable)
ChampVisible(NomChampTable)

Demain(dTemp)
Hier(dTemp)
NomChampTable..Occurrence



NomChampTable..Etat = actif
NomChampTable..Etat = grise
NomChampTable..Visible = Faux
NomChampTable..Visible = Vrai

dTemp..Jour += 1
dTemp..Jour -= 1
→ De préférence, utiliser les propriétés plutôt que les fonctions.
Contient("Tester c'est bien", "bien")
ChaîneCommencePar("Tester c'est bien", "Tester")

sChaîne = SansCaractère(sChaîne, "aeiouy", sscTout)
tabChaîne = ChaîneDécoupe(sChaîne, ",")
("Tester c'est bien" [=] "bien")
("Tester c'est bien" [= "Tester")

sChaîne = Remplace(sChaîne, ["a","e","i","o","u","y"], "")
ChaîneVersTableau(sChaîne, tabChaîne, ",")
→ De préférence, utiliser les opérateurs de comparaison plutôt que les fonctions.
// Récupère tous les enregistrements de SOCIETE
//  pour trouver celle dont le libellé est "Orange"
HDésactiveFiltre(SOCIETE)
POUR TOUT SOCIETE 
    SI (SOCIETE.Libelle = "Orange") ALORS
        ... // Traitements
    FIN
FIN
HDésactiveFiltre(SOCIETE)
// Récupère uniquement l'enregistrement de SOCIETE 
//  dont le libellé est "Orange"
SI HLitRecherchePremier(SOCIETE, Libelle, "Orange") ALORS
    ... // Traitements
FIN
HAnnuleRecherche(SOCIETE, Libelle)


// Recherche et suppression d’un enregistrement 
//  par une de ces rubriques clés.
// L'enregistrement est lu 
//  et ses variables HFSQL sont mises à jour
SI HLitRecherchePremier(SOCIETE, IDSOCIETE, 3) ALORS 
    HSupprime(SOCIETE)
FIN
HAnnuleRecherche(SOCIETE, IDSOCIETE)
// Recherche et suppression d’un enregistrement 
//  par une de ces rubriques clés.

// L’enregistrement n’est pas lu
SI HRecherchePremier(SOCIETE, IDSOCIETE, 3) ALORS
    HSupprime(SOCIETE, HNumEnr())
FIN
HAnnuleRecherche(SOCIETE, IDSOCIETE)
SI (o..Classe = CSociete) ALORS
SI (o est CSociete) ALORS
SI (tab1..Occurrence > 0) ALORS
SI PAS tab1..Vide ALORS

Privilégier les opérateurs aux fonctions

Lors de l'appel à une fonction WLangage®, tout un mécanisme se lance dans la machine virtuelle :
- recherche dans quel fichier DLL se trouve cette fonction
- vérification du chargement du fichier DLL
- recherche de la fonction
- empilement des paramètres
- appel de la fonction
- dépilement de la valeur de retour
- remontée jusqu’au WLangage®

A contrario, lors de l'utilisation d'un opérateur, celui-ci est directement dans la machine virtuelle. Le temps de traitement est donc réduit :
- l’opération est exécutée et le résultat renvoyé
Donc, si un opérateur existe, utilisez-le.

Par exemple, vous pouvez remplacer :
- la fonction Gauche() par l’opérateur [=
- la fonction ChaîneFinitPar() par l’opérateur ~] (termine par sans casse)




Les conseils


L'opérateur _OU_ et la valeur NULL

L'addition logique optimisée _OU_ ne doit pas être utilisée si une des expressions à comparer utilise le résultat d'une fonction pouvant renvoyer NULL.
https://doc.pcsoft.fr/?1512003&name=operateurs_logiques

	PROCÉDURE INTERNE piInit(LOCALE nEntier est un entier)
		// Si la valeur du paramètre nommé "nEntier" est impair alors renvoyer null sinon renvoyer 2
		RENVOYER EstImpair(nEntier) ? Null SINON 2
	FIN

// Avec _OU_ : le résultat de la trace vaut "OK" alors qu'elle devrait être "KO"
SI (piInit(1) = 3) _OU_ (piInit(2) = 4) ALORS
		Trace("OK")
SINON
		Trace("KO")
FIN

// Avec OU : le résultat de la trace vaut "KO" et c'est bien le résultat souhaité
SI (piInit(1) = 3) OU (piInit(2) = 4) ALORS
		Trace("OK")
SINON
		Trace("KO")
FIN

Faire un HDésactiveFiltre() avant et après un POUR TOUT

Avant pour désactiver le filtre déjà existant des codes précédents.
Après pour désactiver le filtre pour les codes suivants.

HDésactiveFiltre(NomTableBDD)
POUR TOUT NomTableBDD AVEC "condition(s)"
    ... // Instructions
    SORTIR
FIN
HDésactiveFiltre(NomTableBDD)

Libérer en mémoire les requêtes utilisées avec les fonctions HAnnuleDéclaration() ou HLibèreRequête()

Avant pour libérer les ressources de la requête SQL des codes précédents.
Après pour libérer les ressources (espace mémoire) de la requête SQL pour les codes suivants.

HAnnuleDéclaration(REQ_SELECT_SOCIETE)
HExécuteRequête(REQ_SELECT_SOCIETE)
POUR TOUT REQ_SELECT_SOCIETE
    ... // Traitement
FIN
HAnnuleDéclaration(REQ_SELECT_SOCIETE)

Attention à ne pas libérer la requête SQL trop tôt !

HAnnuleDéclaration(REQ_SELECT_SOCIETE)
HExécuteRequête(REQ_SELECT_SOCIETE)
TableAffiche(NomChampTable, taInit)
HAnnuleDéclaration(REQ_SELECT_SOCIETE)

Dans le code ci-dessus, après la fonction TableAffiche() la fonction HAnnuleDéclaration() est appelée trop tôt et le champ n'affichera pas la totalité des enregistrements.


Utiliser la source de données dans les requêtes SQL en dur pour les sécuriser

sdReq est une Source de données
sReq est une chaîne

// Le code suivant n'est pas sécurisé : 
sReq = ChaîneConstruit("SELECT * FROM SITE WHERE Libelle LIKE '%1'", "Paris")
HExécuteRequêteSQL(sdReq, hRequêteDéfaut, sReq)

// Les requêtes préparées permettent de sécuriser contre les failles de sécurité (exemple: injection SQL) 
sReq = "SELECT * FROM SITE WHERE Libelle LIKE {pLibelle}"
sdReq.pLibelle = "Paris"
HExécuteRequêteSQL(sdReq, hRequêteDéfaut, sReq)

Optimisation de l'accès aux données en HFSQL Classic

HChangeLocalisation("*", hDisque)

Code à insérer dans l'initialisation du projet

EXTERNE "WINCONST.wl" //  Constantes standard de Windows
EXTERNE "KEYCONST.wl" // Constantes standard de Windows utilisées pour les touches du clavier
EXTERNE "EXCEPT.wl" // Constantes utilisées pour la gestion des exceptions
EXTERNE "LIMITES.wl" // Constantes correspondant aux limites des types de données WINDEV®
EXTERNE "ListeDefinitionHF.wl" // Constantes HFSQL utilisées pour la journalisation 

//threadMultiProcesseur : Les threads sont automatiquement répartis entre les différents processeurs.
//threadSectionCritique : Chaque procédure pourra être exécutée par plusieurs threads simultanément. 
// Il est nécessaire de protéger les accès aux ressources partagées entre les différents threads par des sections critiques.
ThreadMode(threadMultiProcesseur + threadSectionCritique)

// Nouveau mode d'exécution à appliquer
ModeExécution(modeNormal + AppelsExternesOptimisés)

// Modifie la priorité de l'application en cours d'exécution
ExePriorite(ExeDonnePID(exePID), exePrioritéSupérieureNormale)
SI (ExePriorite(ExeDonnePID(exePID)) <> exePrioritéSupérieureNormale) ALORS
    ExePriorite(ExeDonnePID(exePID), exePrioritéHaute)
FIN

//http://faq.pcsoft.fr/8865-champ_oauth_activex_assemblage_ole_automation_coinitializeex-read.awp :
// Que faire si l'utilisation d'un module externe impacte des fonctionnalités d'une application WINDEV® ?
// Gestion du Drag and Drop // Connecteur Natif SQL Server : Spécificités et remarques
//   Lors de l'utilisation de le Connecteur Natif SQL Server via SQLnCli, certaines fonctionnalités peuvent ne pas fonctionner.
//   Pour retrouver un fonctionnement correct, il est conseillé d'ajouter ces lignes de code dans l'initialisation du projet :
SI (ChargeDLL("OLE32") <> 0) ALORS
    SELON API("OLE32", "CoInitializeEx", Null, 2)
        CAS 0 : // OK
        CAS 1 :
            API("OLE32", "CoUninitialize")
            SI (API("OLE32", "CoInitializeEx", Null, 2) <> 0) ALORS
                ... // Erreur("Echec de CoInitializeEx", ErreurInfo())
            FIN
        AUTRE CAS :
            ... // Erreur("Echec de CoInitializeEx", ErreurInfo())
    FIN
FIN
//ChargeDLL("ole32")
//API("ole32", "CoInitializeEx", 0, 2)


// Gestion des effets
SI EnModeTSE() ALORS
    // Pour désactiver des effets non supportés en TSE
    FenDésactiveEffet(effetAnimation + effetCadreTranslucide + effetGFI + effetHalo)
SINON
    // Animation des champs, leurs effets de transitions
    AnimationMinFPS(24)
    // Les fenêtres ayant un cadre translucide sont affichées avec cet effet
    StyleDessin(styleCadreFenTranslucide, Vrai)
    // Chargement rapide : le temps de chargement des images est prioritaire sur leur qualité
    StyleDessin(styleImageFaibleQualite, Vrai)
    // Les icônes, images des boutons grisés sont affichées en niveau de gris. 
    // Si ce mode est sélectionné, l'affichage peut prendre un plus de temps mais le rendu est meilleur
    StyleDessin(stylePictoGriseDegrade, Vrai)
FIN


// Aperçu avant impression
iParamètreAperçu(iBoutonTous - iBoutonHtml - iBoutonXml, 150)
// Afficher le volet des miniatures dans l'aperçu avant impression
iParamètreAperçu(iVoletMiniatures, Vrai)

// Génération PDF
iParamètrePDF("", "", iQualitéMaximale + iPDFUnicode)
// Génération XLS
iParamètreXLS(iAvecMiseEnForme)
// Paramétrer l'impression de l'état (et notamment la conservation de la mise en forme des cellules)
ParamètreFAA(faaImprimeEtatSurTable, 1) 
// Génération pour le champ "table" XLS / Word / XML
ParamètreFAA(faaTableVersExcel, taAvecMiseEnForme + taColonneOrdreAffiche + taTitreColonnes)
ParamètreFAA(faaTableVersWord, taAvecMiseEnForme + taColonneOrdreAffiche + taTitreColonnes)
ParamètreFAA(faaTableVersXML, taAvecMiseEnForme + taColonneOrdreAffiche + taTitreColonnes)

// Désactivation ReconnaissanceVocale
DésactiveFAA(faaReconnaissanceVocale)

// Active la vérification d'orthographe pour toute l'application
ParamètreOrthographe(orthographeActif, Vrai)
// Sélectionne le dictionnaire du langage en cours
ParamètreOrthographe(orthographeNation, SysNation())

// Gestion des jours fériés
JourFériéSupprimeTout()
SELON SysNation()
    CAS nationFrançais :
        // Initialisation des 11 jours fériés communs aux départements français et DOM/TOM
        JourFériéAjoute("0101")                 // 1er Janvier
        JourFériéAjoute(jfLundiDePâques)        // Lundi de Pâques
        JourFériéAjoute("0501")                 // 1er Mai
        JourFériéAjoute("0508")                 // 8 Mai
        JourFériéAjoute(jfJeudiDeLAscension)    // Jeudi de l'Ascension
        JourFériéAjoute(jfLundiDePentecôte)     // Lundi de Pentecôte
        JourFériéAjoute("0714")                 // 14 Juillet
        JourFériéAjoute("0815")                 // 15 Août (Assomption)
        JourFériéAjoute("1101")                 // Toussaint
        JourFériéAjoute("1111")                 // 11 Novembre
        JourFériéAjoute("1225")                 // Noël
   
    AUTRE CAS :
FIN


// Délai à attendre avant l'affichage des bulles d'aide
BulleDélai(bulleDuréeAvantOuverture, -1) // Valeur par défaut gérée par Windows
// Durée d'affichage des bulles d'aide des champs
BulleDélai(bulleDuréeAffichage, 32s) // Valeur maximale : 32,6s

// Initialisation du générateur de nombres aléatoires
InitHasard()

// Règle le timeout pour les fonctions WLangage® utilisant le protocole HTTP 
HTTPTimeOut(10s)