Astuces, conseils, optimisations, bonnes pratiques en WINDEV®*


Pages dédiées aux astuces, conseils, optimisations, partages de connaissances et bonnes pratiques en WLangage® *

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

Les conseils et recommandations générales

Les questions fréquentes et aides techniques

Les astuces classiques
 - Effectuer un tri numérique sur une colonne 'chaine' d'une table
 - Les masques des colonnes 'numérique' dans une table
 - L'affectation multiple s'applique aux structures
 - Optimiser les traitements sur un champ avec ..AffichageActif
 - Remplir une chaine avec un séparateur
 - Transformer un tableau vers une chaine 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()
 - Extraire un mémo binaire avec HExtraitMémo()
 - 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

Les astuces
 - Tester si un fichier existe sans le télécharger
 - 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()
 - 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 chaine ?
 - Vérifier un calcul mathématique

Les optimisations

Les conseils
 - Faire un HDésactiveFiltre avant et après un POUR TOUT
 - Libérer en mémoire les requêtes utilisées
 - Utiliser la source de données dans les requêtes
 - 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 chaine d'un champ table ?

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

Les masques d'une colonne de type numérique dans un champ table :

- Le masque "099,99" donnera un résultat de la forme "012.34".
- Le masque "999,00" donnera un résultat de la forme "999" et non "999,00".

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 chaine 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 chaine 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, ",")

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 l'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
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 extraire un mémo binaire avec la fonction HExtraitMémo() ?

Il y a 2 pré-requis avant d'utiliser le code ci-dessous, il faut :
- se positionner sur l'enregistrement en utilisant une fonction H* (exemple : HLitRecherche, HLitRecherchePremier, ...)
- utiliser la base de données HFSQL et le système d'exploitation Windows

// Se positionner sur l'enregistrement en utilisant une fonction H* (exemple : HLitRecherche, HLitRecherchePremier, ...)
// Ne fonctionne que sous HFSQL et Windows
sNomTable est une chaîne = "Nom de la table en BDD"
sNomRubrique est une chaîne = "Nom de la rubrique de la table en BDD"
sCheminDossier est une chaîne = ComplèteRep(fDisqueEnCours())

// Récupère les informations, extrait et ouvre le mémo
sHInfoMémo est une chaîne = HInfoMémo(sNomTable, sNomRubrique)
SI (sHInfoMémo <> "") _ET_ (ExtraitChaîne(sHInfoMémo, 2, TAB) <> "") ALORS
    // S'il existe un mémo binaire avec des informations disponibles
    sCheminFichier est une chaîne = sCheminDossier + fExtraitChemin(ExtraitChaîne(sHInfoMémo, 2, TAB), fFichier+fExtension)
    QUAND EXCEPTION DANS 
        HExtraitMémo(sNomTable, sNomRubrique, sCheminFichier)
    FAIRE
        ... // Exceptions possibles : "Répertoire inexistant", "Fichier déjà ouvert par une application", ...
    SINON
        SI fFichierExiste(sCheminFichier) ALORS
            // Tentative d'ouverture directement du fichier
            SI PAS LanceAppliAssociée(sCheminFichier, "open") ALORS
                // Ouverture de la fenêtre "Ouvrir avec..."
                LanceAppli("rundll32.exe shell32.dll, OpenAs_RunDLL """ + sCheminFichier+"""")
            FIN
        FIN
    FIN
FIN

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 :

 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


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 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 chaine 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 chaine par défaut

Deux 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"

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
    sChaine est une chaîne
    tabChaine 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, 2095) = 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 :
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)

SI fEstUneImage(bufImage) ALORS
    
    // 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)
    
FIN

RENVOYER Faux
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

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() (exemple : https://doc.pcsoft.fr/?1000017304)
// 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 chaine ?

Il existe plusieurs approches pour savoir si un nombre particulier est contenu dans une chaine (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 chaine
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 chaines, 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 chaine (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("CaculerFormule", ChaîneConstruit(sSource, sCalcul)) = "") ALORS
	QUAND EXCEPTION DANS
		xResutatDuCalcul = ExécuteTraitement("CaculerFormule", 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 
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)
POUR TOUT REQ_SELECT_SOCIETE
    // Traitements
FIN
HAnnuleDéclaration(REQ_SELECT_SOCIETE)
→ De préférence, utiliser une requête plûtot que les fonctions H*.
{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 plûtot 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")
("Tester c'est bien" [=] "bien")
("Tester c'est bien" [= "Tester")
→ De préférence, utiliser les opérateurs de comparaison plûtot 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"
HLitRecherche(SOCIETE, Libelle, "Orange", hIdentique)
SI HTrouve(SOCIETE) 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)

Les conseils

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 des codes précédents.
Après pour libérer les ressources de la requête 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 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, la fonction HAnnuleDéclaration() après la fonction TableAffiche() 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 en dur pour sécuriser la requête

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)

// Le code suivant est sécurisé : 
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, 30s) // 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)