vendredi 27 juin 2014

gestion des erreurs et des accès concurrent en windev

Bonjours et bienvenue aux windeveurs et windeveuses.

Cet article concerne certaines problématiques qui apparaissent après le déploiement chez les clients. Notamment, les bugs qui passent entre les mailles de la validation et les problèmes de gestion des accès concurrentiels lors de l'utilisation d'une base de données hyperfile c/s.

Alors nous allons diviser cet article en deux parties ou nous allons voir la solution à ces problèmes.


gestion des erreurs


Ou devrais je dire récupération des messages d'erreur.

Vous savez bien que lorsqu'un bug survient, une fenêtre automatique de windev apparaît indiquant le message de l'erreur.
On ne peut pas modifier entièrement ces fenêtres car il font appel à des codes externes mais on peut quand même apporter quelques modification.

pour cela il faut d'abord charger ces fenêtres dans le projet.

Rien de plus simple. Dans le menu cliquez sur "fichier", dans le groupe "importer", cliquez sur "Des éléments WinDev et leurs dépendances".
Il ne vous reste plus qu'à sélectionner le répertoire adéquat comme indiqué dans l'image qui suit.



Et voila maintenant vous avez un nouveau modèle ainsi que trois fenêtres qui viennent s'ajouter à votre projet, mais ce qui nous intéresse c'est "WD_AfficheException", car c'est la que les erreur critiquent ou bug s'affichent.
Alors comment allons nous faire pour récupérer de façon automatique les erreur affichés dans cette fenêtre?

C'est très simple. Nous allons nous débrouiller pour nous l'envoyer par email (bien que le client final peut le faire lui même mais c'est rare qu'il le fasse). et pour cela nous allons utiliser deux adresses emails, une pour l'envoie et une pour la réception.

maintenant allez dans le code 'initialisation de la fenêtre et mettez ce code
sText_err est chaîne=AfficheDétail
sText_err=Remplace(sText_err,"pivo dent","Dental Pro",MotComplet)
sCore_email est chaîne=""
tabTableau_text_err est un tableau de 3 chaînes
ChaîneVersTableau(sText_err,tabTableau_text_err)
sText_err=TableauVersChaîne(tabTableau_text_err,"
")
SI AfficheAssistance="" ET ListeAssistance..Occurrence=0 ALORS
 Onglet1[3]..Visible = Faux
FIN

SI InternetConnecté() ALORS
 SI OuiNon(1,"Une erreur est survenue dans le programme","Voulez vous que votre fournisseur soit informé?") ALORS
  //Ouverture d'une session SMTP
  EmailOuvreSessionSMTP("Votreemaildenvoi@gmail.com","motdepass","smtp.gmail.com",587,Faux,EmailOptionSécuriséTLS)
  SI ErreurDétectée ALORS
   Erreur()
   RETOUR
  FIN
  //Remise à zéro des variables Email
  EmailRAZ()
  //Expéditeur du message
  Email.Expéditeur = "Votreemaildenvoi@gmail.com"
  //Destinataire(s) du message
  Email.Destinataire[1] = "Votreemaildereception@votredomaine.net"
  Email.NbDestinataire = 1
  //Pièces jointes
  Email.NbAttache = 0
  //Sujet et contenu du message
  Email.Sujet = "Bug Votre logiciel"
  Email.Message = ""
                //en principe en récupere des données qui font référence au client mais c'est obligatoire
  sCore_email="client: "+" le nom du client"+"
"

  sCore_email=sCore_email+"

"+sText_err
  
  Email.HTML = sCore_email
  
  //Envoi du message
  EmailEnvoieMessage("Votreemaildenvoi@gmail.com")
  SI ErreurDétectée ALORS
   Erreur(ErreurInfo(errComplet))
  SINON
   Info("Votre Fournisseur a été informé du bug")
  FIN
  
  //Fermeture de la session SMTP
  EmailFermeSession("Votreemaildenvoi@gmail.com")
 FIN
FIN



Évidement le code n'est pas complet, il faut mettre votre email et votre mot de passe voir même l'adapter pour qui fonctionne à votre guise.

NB: c'est une alternative au composant "CCFeedback" que vous ne pouvez pas personnaliser et qui n'est pas très simple à paramétrer, enfin selon moi.

Voila maintenant que nous avons mis le système de récupération des erreurs en place, passons à autres choses.


gestion des accès concurrent

Tout ceux qui ont travaillé avec hyperfilesql en mode client/serveur ont fait face à une fenêtre dérangeante lorsque deux enregistrement se chevauches. Nous vous inquiétez pas nous allons faire en sorte que cette fenêtre n’apparaît plus d’ailleurs j'en ai pas une sous la main pour la montrer à ceux qui ne l'ont jamais vue.
D'ailleurs la meilleur façon de contourner ce problème est d'utiliser le blocage des fichier. et comme ça nous allons obliger notre programme à bloquer le fichier avant modification et si le fichier et bloqué par un autre utilisateur recommencer l'opération

Pour cela nous allons créer une collection de procédures globales dans notre projet que j'appellerai "accesconcurrent". Et dans cette collection nous allons surcharger les fonctions de gestion des fichiers (HAjoute, Hmodifie,Hsupprime)

Et voila les code de chaqu'une des fonctions surchargées.

HAjoute:
PROCEDURE Hajoute(fichier,opt est un numérique=0)
TANTQUE HBloqueFichier(fichier,hBlocageEcriture)=Faux

FIN
SI opt=0 ALORS
 WL.HAjoute(fichier)
SINON 
 WL.HAjoute(fichier,opt)
FIN
HDébloqueFichier(fichier)
RENVOYER Vrai


HModifie:
PROCEDURE hmodifie(fichier,enre est un numérique=0,opt est un numérique=0)
TANTQUE HBloqueFichier(fichier,hBlocageEcriture)=Faux
 
FIN
SI opt=0 ET enre=0 ALORS
 BOUCLE
  WL.HModifie(fichier,hNumEnrEnCours,hBlocageEcriture)
  SI HErreurBlocage() ALORS
   trace("erreur")
  SINON
   SORTIR
  FIN
 FIN
 
SINON SI enre<>0 ET opt=0
 WL.HModifie(fichier,enre,hBlocageEcriture)
SINON SI opt<>0 ET enre=0
 WL.HModifie(fichier,hNumEnrEnCours,opt+hBlocageEcriture)
SINON SI opt<>0 ET enre<>0
 WL.HModifie(fichier,enre,opt+hBlocageEcriture)
FIN

HDébloqueFichier(fichier)
RENVOYER Vrai


HSupprime:
PROCEDURE HSupprime(Fichier,xEnre est un numérique=0,xOpt est un numérique=0)
TANTQUE HBloqueFichier(Fichier,hBlocageEcriture)=Faux
 
FIN
SI xOpt=0 ET xEnre=0 ALORS
 WL.HSupprime(Fichier)
SINON SI xEnre<>0 ET xOpt=0
 WL.HSupprime(Fichier,xEnre)
SINON SI xOpt<>0 ET xEnre=0
 WL.HSupprime(Fichier,hNumEnrEnCours,xOpt)
SINON SI xOpt<>0 ET xEnre<>0
 WL.HSupprime(Fichier,xEnre,xOpt)
FIN

HDébloqueFichier(Fichier)
RENVOYER Vrai


Et voila, nous avons presque terminé.

Oui enfin il faut se poser la question qu'est ce qui ce passe si on utilise une requête

Ne vous inquiétez pas windev a déjà prévue un système de gestion des erreur qui est moins bien que le notre mais qui est utile (voir la documentation de la fonction "HSurErreur"). et dans ce cas nous allons l'utiliser un peut.

Alors retournons à notre collection de procédure et ajoutons une nouvelle procedure

PROCEDURE SurErreurBlocage()

RENVOYER opRéessayer


et pour couronner le tout nous allons ajouter une ligne dans le code du projet
HSurErreur("*",hErrIntégrité+hErrDoublon+hErrModification+hErrMotDePasse,"SurErreurBlocage")


Et Voila. Nous avons terminé.
J’espère que cet article vous soit utile et à la prochaine

mardi 6 mai 2014

Personnaliser le groupware utilisateur


Bonjours, Ms et Mrs les windeveurs.
Aujourd'hui je vais vous parler d'un outils incontournable dans windev, le Groupware  Utilisateur alors nous allons commencer par savoir ce qu'est ce Groupware et ce qu'il fait

Groupware Utilisateur??

C'est le module de windev qui s'occupe de la gestion des utilisateurs et de leurs droits dans le logiciel.
Son fonctionnement est simple. Il énumère tous les champs et fenêtres et vous donne une interface bien aboutis pour gérer les droit d’accès des utilisateur ou des groupes.
Les informations sont par la suite enregistrée dans des tables de base de données crypté. Le reste, il le fait tout seul.

au premier lancement, le système crée automatiquement un utilisateur avec les droit de super administrateur (Superviseur) avec login et mot de passe par défaut (superviseur/superviseur)
C'est à partir de lui que vous commencer à créer des utilisateurs, des groupes et de gérer leurs droits.

Par exemple

Vous creez un utilisateur "X", et vous indiquez que "X" ne doit pas voire le bouton "BTN_b" dans la fenêtre "Fen_F". lorsque Mr "X" ouvre le logiciel avec son login et mot de passe, il ne trouve pas le bouton "BTN_b" dans la fentre "FEN_F"

Comment intégrer le groupware utilisateur??

C'est trés simple. Même plus simple que tous ce que vous imaginez. Allez dans le menu "Atelier->Paramétrer le Groupware utilisateur", Une interface apprêt avec 3 onglets(générale, fichier,LDAP).
Dans l'onglet "générale" vous cochez "Activez la gestion du groupware utilisateur" pour l'activer et puis vous choisissez intégration par défaut ou personnalisable.

Je vous conseille l'intégration personnalisable, celle ci vous ajoute les interfaces du groupware afin de les modifier et c'est d’ailleurs la partie la plus intéressante.
Et après y'a démarrage automatique ou manuel.
le démarrage automatique lance le groupware sans aucun appel au lancement de l'application mais avec le démarrage manuelle, vous choisissez le moment du lancement avec l'appel de la fonction "gpwlogin".

Je vous laisse avec les 2 autres onglets faire ce qui vous semble le mieux et maintenat nous allons passer à la personnalisation du groupware.

les interfaces du groupware

Comment je vous l'ai déjà dis, l'integration personnalisé vous ajoute les interfaces du groupeware et nous allons les voir rapidement:

GPWLogin

c'est la première interface, et celle qui est ouverte lors du lancement du groupware

Vous pouvez dés à présent modifier son apparence et même son comportement si vous le souhaitez. Il suffit de parcourir les élément de l'interface et son code afin de comprendre son fonctionnement. Nous verrons plus de détails par la suite mais pour la personnalisation de l'affichage c'est simple. Vous pouvez obtenir un résultat semblable à celui si sans effort.



GPWMenuSuperviseur

Cette interface appert lorsque l'utilisateur ce connecte avec un compte Superviseur.
Rien de compliqué, y a pas grand chose à modifier dans cette interface, juste les textes et l'apparence.
Inutile de vous expliquez à quoi sert chaque bouton mais je doit notifier que le bouton "Configurer le groupware" ouvre la partie de la gestion des utilisateurs et donc l'interface suivante est "GPWAssociationConfiguration"

GPWAssociationConfiguration

Cette interface est très importante. c'est celle à partir de laquelle on peut choisir le groupe de chaque utilisateur et définir ses paramètres.
on peut y appliquer quelques modification au niveau du comportement. afin d’empêcher un utilisateur de se supprimer lui même ou alors de s'enlever les mode superviseur.
nous y reviendrons plus tard pour ça.

GPWDetailsConfiguration



Alors la c'est la cerise sur le gâteaux. Cette interface est la plus importante dans le groupware. c'est d'ailleurs celle qui définie les droit de chaque utilisateur ou groupe (Configuration).
Son utilisation est un peut difficile pour les client (utilisateurs finaux de l'application) à cause de l’énumération des champs et des fenêtres qui sont affichés selon leurs noms physiques. Alors si vous utilisez une nomination qui n'est pas très claires, vos clients ferons appel à vous à chaque fois qu'ils ont besoin de changer les droit d'un utilisateur. Nous allons nous occupez plus tard de la rendre plus simple et plus compréhensible à utiliser.

GPWChoixConfiguration


Y a pas grand choses à dire sur cette interface. Modifiez simplement son apparence.

GPWFicheConfiguration


Encore la y a pas grand chose à dire. cette interface s'occupe de l'ajout et la modification d'un groupe.

GPWFicheUtilisateur


Cette interface à de l'importance. c'est celle ou on ajoute et on modifie les paramètres généraux d'un utilisateur.


La structure de la base de données du groupware

la base de donnée est constitué de 5 tables.

GPWConfiguration


Caractéristiques des rubriques :
Nom ExplicationTypeTailleClé
ConfigurationNom du groupe d'utilisateurs ou de l'utilisateurChaîne de caractères255 caractèresUnique
ApplicationNom de l'applicationChaîne de caractères255 caractèresAvec doublons
GroupeDéfinit si la configuration correspond à un groupe d'utilisateurs ou à un utilisateurBooléen
Groupe_ConfigurationClé composée des rubriques Groupe et ConfigurationClé composéeUnique
Application_ConfigurationClé composée des rubriques Application et ConfigurationClé composéeAvec doublons

GPWConfigurationElément


Caractéristiques des rubriques :
NomExplicationTypeTailleClé
ConfigurationNom du groupe d'utilisateurs ou de l'utilisateurChaîne de caractères255 caractèresAvec doublons
EtatEtat de l'élément (grisé, inactif, ...)SélecteurEntier non signé sur 1 octetAvec doublons
ElementNom de l'élémentChaîne de caractères255 caractèresAvec doublons
Configuration_ElementClé composée des rubriques Configuration et ElementClé composéeUnique

GPWElément


Caractéristiques des rubriques :

Nom

Explication

Type

Taille

Clé
ElementNom de l'élémentChaîne de caractères255 caractèresUnique
TypeType de l'élément (champ de saisie, table, ...)NumériqueEntier sur 4 octetsAvec doublons

GPWHistoriqueConnexion


Caractéristiques des rubriques
NomExplicationTypeTailleClé
IDLogConnexionIdentifiantIdentifiantUnique
ApplicationNom de l'applicationChaîne de caractères255 caractèresAvec doublons
LoginLogin de l'utilisateur. Ce login correspond à l'identifiant de l'utilisateur lors de sa connexion à l'applicationChaîne de caractères255 caractèresAvec doublons
DateConnexionDate de la connexion à l'applicationDate8 caractèresAvec doublons
HeureConnexionHeure de la connexion à l'applicationHeure4 caractèresAvec doublons
AdresseIPAdresse IP du posteChaîne de caractères39 caractèresAvec doublons

GPWUtilisateur


Caractéristiques des rubriques :

Nom

Explication

Type

Taille

Clé
LoginLogin de l'utilisateur. Ce login correspond à l'identifiant de l'utilisateur lors de sa connexion à l'applicationChaîne de caractères255 caractèresUnique
NomNom de l'utilisateurChaîne de caractères255 caractères
PrenomPrénom de l'utilisateurChaîne de caractères255 caractères
MotPasseMot de passe de l'utilisateurChaîne de caractères255 caractères
SuperviseurDéfinit si l'utilisateur est un superviseurBooléen
MotPasseASaisirDéfinit si l'utilisateur peut modifier son mot de passe lors de sa première connexionBooléen

GPWUtilisateurConfiguration


Caractéristiques des rubriques :
NomExplicationTypeTailleClé
ConfigurationNom du groupe d'utilisateurs ou de l'utilisateurChaîne de caractères255 caractèresAvec doublons
LoginLogin de l'utilisateur. Ce login correspond à l'identifiant de l'utilisateur lors de sa connexion à l'applicationChaîne de caractères255 caractèresAvec doublons
Login_ConfigurationClé composée des rubriques Login et ConfigurationClé composéeAvec doublons
ApplicationNom de l'applicationChaîne de caractères255 caractèresAvec doublons
Login_ApplicationClé composée des rubriques Login et ApplicationClé composéeUnique
Application_ConfigurationClé composée des rubriques Application et ConfigurationClé composéeAvec doublons

Corriger une faille dans le groupware utilisateur

Une faille?
Oui une faille. Il y en a pas mal de failles mais celle ci est particulière.
lors de la première utilisation du groupeware les fichier des données sont vides. donc le système ajoute automatiquement les données de bases comme l'utilisateur "Superviseur" avec le mot de passe "Superviseur".
Et comme les fichiers existe en dur sur le disque dur du serveur, il suffit de les supprimer pour que tout soit réinitialisé.

Pour y remédié rien de plus simple.
Allez Maintenant sur l'interface "GPWLogin"=>code d'initialisation.
Après la dernière ligne nous allons ajouter une petite partie de code:
HLitRecherche(GPWUtilisateur,Login,"Superviseur")
SI HTrouve(GPWUtilisateur) Alors
   SI GPWUtilisateur.MotPasse="Superviseur"
      GPWUtilisateur.MotPasse=" Votre Mot de passe ici"
   FIN
FIN
Voila c'est pas compliqué. Et maintenant nous allons mettre quelques mesure de sécurités supplémentaires.

Se supprimer

Un utilisateur superviseur (Admin) peut supprimer son propre compte, ce qui peut poser problème dans certains cas ou alors révoquer les droits admin pour tout les utilisateurs ce qui peut poser plus de problèmes.
La solution est très simple.
Allez dans l'interface "GPWAssociationConfiguration"=>bouton supprimer=>code=>clic et vous trouverez ce code la:

SI OuiNon("Voulez-vous vraiment supprimer l'utilisateur '"+Login+"' ?") ALORS
 // recherche du login dans le fichier de relation entre les login et les applications
 HLitRecherchePremier(GPWUtilisateurConfiguration,"Login",Login)
 TANTQUE HTrouve(GPWUtilisateurConfiguration)
  SI PAS HSupprime(GPWUtilisateurConfiguration) ALORS
   Erreur("Impossible de supprimer l'utilisateur.",HErreurInfo())
  FIN
  HLitSuivant(GPWUtilisateurConfiguration,"Login")
 FIN

 // recherche de l'utilisateur
 HLitRecherchePremier(GPWUtilisateur,"Login",Login)
 SI HTrouve(GPWUtilisateur) ALORS
  SI PAS HSupprime("GPWUtilisateur") ALORS
   Erreur("Impossible de supprimer l'utilisateur.",HErreurInfo())
  FIN
 FIN

 // rafraichissement de la table
 AfficheTable()
FIN
Nous allons faire une petite modification au début pour empêcher un utilisateur de se supprimer
SI Login=gpwRecupInfoUtilisateur(gpwInfoLogin)
 Erreur("Impossible de supprimer l'utilisateur")
SINON SI OuiNon("Voulez-vous vraiment supprimer l'utilisateur '"+Login+"' ?") ALORS
 // recherche du login dans le fichier de relation entre les login et les applications
 HLitRecherchePremier(GPWUtilisateurConfiguration,"Login",Login)
 TANTQUE HTrouve(GPWUtilisateurConfiguration)
  SI PAS HSupprime(GPWUtilisateurConfiguration) ALORS
   Erreur("Impossible de supprimer l'utilisateur.",HErreurInfo())
  FIN
  HLitSuivant(GPWUtilisateurConfiguration,"Login")
 FIN

 // recherche de l'utilisateur
 HLitRecherchePremier(GPWUtilisateur,"Login",Login)
 SI HTrouve(GPWUtilisateur) ALORS
  SI PAS HSupprime("GPWUtilisateur") ALORS
   Erreur("Impossible de supprimer l'utilisateur.",HErreurInfo())
  FIN
 FIN

 // rafraichissement de la table
 AfficheTable()
FIN
Et maintenant nous allons nous assurer qu'il y a au moins un superviseur dans l'application et pour cela nous allons créer une requête de sélection appelé "REQ_liste_superviseur" et dont le code est:

SELECT 
 *
FROM 
 GPWUtilisateur
 WHERE Superviseur=1
Et puis nous allons à l'interface "GPWFicheUtilisateur" puis le bouton "ok"=>code de clic.
en bas nous allons modifier la partie:

// modification de l'utilisateur
SINON
 // modifie l'enregistrement dans le fichier
 SI PAS hmodifie("GPWUtilisateur") ALORS
  Erreur("Impossible de modifier l'utilisateur."+RC+HErreurInfo())
 FIN
FIN
Pour obtenir:
// modification de l'utilisateur
SINON
    bModif_ok est boolean=vrai
    // modifie l'enregistrement dans le fichier
    SI GPWUtilisateur.superviseur=faux ALORS
        HExécuteRequête(REQ_liste_superviseur,hRequêteDéfaut)
        SI HNbEnr(REQ_liste_superviseur)=1 ALORS
            Erreur("Impossible de révoquer le mode superviseur à cet utilisateur.+RC+"Cet utilisateur est le seul superviseur")
            bModif_ok=faux
        FIN
    FIN
    SI bModif_ok=vrai ALORS
        SI PAS hmodifie("GPWUtilisateur") ALORS
            Erreur("Impossible de modifier l'utilisateur."+RC+HErreurInfo())
 FIN
    FIN
FIN
J’espère que ça vous aide.

Faciliter la gestion des droits

J'ai déjà dis que l'utilisation de la gestion des droits dans la groupware n'est pas facile pour les clients à cause des noms physiques des champs et des fenêtres. Et pour facilité son utilisation, nous allons devoir modifier certaines choses et inscrire manuellement les champs que nous voulons. mais pour il faut comprendre le fonctionnement de l'interface "GPWDetailconfiguration"

Comprendre le fonctionnement de base

Cette interface est constitué de 5 parties:
  • Combo utilisateur/groupe: c'est la combo qui défini sur quelle configuration nous allons modifier les droits. Nous n'allons pas y toucher
  • Boutons (ajouter/modifier/supprimer): il agisse sur la configuration sélectionnée. Nous n'allons pas y toucher non plus
  • Combo Nom de la fenêtre: cette combo est remplie par l’énumération des fenêtres de l'application et du coup elle comprend la liste de toutes les fenêtres avec leurs noms physiques. Nous allons la remplir de façon manuelle et changer son comportement.
  • La liste Type: c'est une liste qui nous permet de choisir quel type de champs nous voulons afficher dans la liste des champs. Nous pouvons nous en débarrasser, ce serai plus simple pour l'utilisateur final qui ne fait pas la différence entre une liste et une combo ou alors sélecteur et interrupteur.
  • Le tableau des objets: c'est la liste des champs dans une fenêtre. et c'est l'endroit ou nous choisissons l’état du champs (droit). Bien évidement nous allons changer ça façon de se remplir et ça façon d'interagir.
Après avoir pris connaissance de ce que nous allons faire il va falloir voire tous ça de plus près.
Alors nous allons ouvrir le code de l'interface=>déclaration globale.
Nous y trouverons, la déclaration des tables de base de données, puis la déclaration des constantes du type de champs. Dans notre cas ils ne nous aiderons pas beaucoup mais pour ceux qui désire utiliser les types des champs ce serais intéressant.

Apres la déclaration globale, nous trouvons l'initialisation de la fenêtre. On y trouve un petit code:

// remplit la combo
InitComboConfiguration()

// sélection de la bonne configuration
SelectConfiguration()

// rafraichissement de la fenêtre
Rafraichissement()
Je pense que tout est claire, inutile de détailler la dessus mais il faut savoir que la fonction Rafraichissement() est la fonction qui remplit le tableau des objets en prenant en compte le type du champs et la fenêtre et voici son code avant modification:


// Rafraichissement de la fenêtre en fonction de la sélection de la fenêtre et de la configuration
PROCEDURE Rafraichissement()

// propose d'activer les options du menu "?"
SI Fenetre[Fenetre]="FI_MenuHWinDevHelp" ALORS
 SI ListeOccurrence(ColEtat)<>5
  ListeAjoute(ColEtat,"Actif")
 FIN
SINON SI ListeOccurrence(ColEtat)=5 ALORS
 ListeSupprime(ColEtat,5)
FIN

// vide la table
TableSupprimeTout(EtatObjet)

// filtre le fichier de configiguration des éléments
HFiltre("GPWConfigurationElement","Configuration",ComboConfiguration)

// selon la sélection dans la liste
// il faut remplir correctement
selon type
 cas eTOUS : RemplirTous(Fenetre[Fenetre])
 cas eGROUPE : Remplirparent(Fenetre[Fenetre],typGrp)
 cas eMENU : RemplirMenu(Fenetre[Fenetre])
 cas eMENUCONTEXTUEL : RemplirMenuContextuel(Fenetre[Fenetre])
 cas eBOUTON : RemplirElement(Fenetre[Fenetre],typBouton)
 cas eSAISIE : RemplirSaisie(Fenetre[Fenetre])
 cas eLIBELLE : RemplirElement(Fenetre[Fenetre],typLibellé)
 cas eLISTE : RemplirElement(Fenetre[Fenetre],typListe)
 cas eCOMBO : RemplirCombo(Fenetre[Fenetre])
 cas eSELECTEUR : RemplirElement(Fenetre[Fenetre],typSélect)
 cas eINTERRUPTEUR : RemplirElement(Fenetre[Fenetre],typInterr)
 cas eTABLE : RemplirElement(Fenetre[Fenetre],typTable)
 cas eBARREOUTILS : RemplirElement(Fenetre[Fenetre],typBarreOutils)
 cas eIMAGE : RemplirImage(Fenetre[Fenetre])
 cas eONGLET : Remplirparent(Fenetre[Fenetre],typOnglet)
 cas eVOLET : RemplirVolet(Fenetre[Fenetre])
 CAS eSUPERCHAMP : RemplirParent(Fenetre[Fenetre],typSuperChamp)
 cas eAUTRE : RemplirAutre(Fenetre[Fenetre])
 cas eBOITEOUTILS : Remplirparent(Fenetre[Fenetre],typBoîteAOutils)
FIN
Vous commencer à cerner comment fonctionne le système n'est ce pas? C'est pas bien compliquer.

Mise en place des éléments

Comme nous l'avons déjà dis, nous allons remplacer le système d’énumération automatique par un système manuel. et pour ça nous devons préparer la liste de nos éléments dans la déclaration de la fenêtre.

Ma méthode consiste à utiliser des structure de données et des tableaux comme cela:
STChamp est une structure
 xChamp_type est un numérique=0
 sNom_champ est une chaîne=""
 sLib_champ est une chaîne=""
 sNom_physique_champ est une chaîne=""
 sGroupe_champ est une chaîne=""
FIN
STFenetre est une structure
 sNom_fenetre est une chaîne=""
 sNom_physique_fenetre est une chaîne=""
 ptabListe_champ est un tableau dynamique de STChamp
FIN

gtabListe_fenetre est un tableau de 2 STFenetre
gAux_fenetre est STFenetre
gtabAux_liste_champs est un tableau dynamique de STChamp

Dimension(gtabAux_liste_champs,1)
gAux_fenetre:sNom_fenetre="Agenda"
gAux_fenetre:sNom_physique_fenetre="FEN_agenda"
gtabAux_liste_champs[1]:sLib_champ="Votre Agenda"
gtabAux_liste_champs[1]:sNom_physique_champ="AGD_Agenda"
gtabAux_liste_champs[1]:sNom_champ="Agenda"
gtabAux_liste_champs[1]:xChamp_type={"FEN_agenda.AGD_Agenda",indGPW}..Type
gtabAux_liste_champs[1]:sGroupe_champ={"FEN_agenda.AGD_Agenda",indGPW}..Groupe
gAux_fenetre:ptabListe_champ=gtabAux_liste_champs
gtabListe_fenetre[1]=gAux_fenetre

Dimension(gtabAux_liste_champs,3)
gAux_fenetre:sNom_fenetre="Saisie"
gAux_fenetre:sNom_physique_fenetre="FEN_Saisie"
gtabAux_liste_champs[1]:sLib_champ="premier champs de saisie"
gtabAux_liste_champs[1]:sNom_physique_champ="SAI_champ_saisie_1"
gtabAux_liste_champs[1]:sNom_champ="champs saisie 1"
gtabAux_liste_champs[1]:xChamp_type={"FEN_Saisie.SAI_champ_saisie_1",indGPW}..Type
gtabAux_liste_champs[1]:sGroupe_champ={"FEN_Saisie.SAI_champ_saisie_1",indGPW}..Groupe
gtabAux_liste_champs[2]:sLib_champ="deuxieme champs de saisie"
gtabAux_liste_champs[2]:sNom_physique_champ="SAI_champ_saisie_2"
gtabAux_liste_champs[2]:sNom_champ="champs saisie 2"
gtabAux_liste_champs[2]:xChamp_type={"FEN_Saisie.SAI_champ_saisie_2",indGPW}..Type
gtabAux_liste_champs[2]:sGroupe_champ={"FEN_Saisie.SAI_champ_saisie_2",indGPW}..Groupe
gtabAux_liste_champs[3]:sLib_champ="Bouton ok"
gtabAux_liste_champs[3]:sNom_physique_champ="BTN_OK"
gtabAux_liste_champs[3]:sNom_champ="OK"
gtabAux_liste_champs[3]:xChamp_type={"FEN_Saisie.BTN_OK",indGPW}..Type
gtabAux_liste_champs[3]:sGroupe_champ={"FEN_Saisie.BTN_OK",indGPW}..Groupe
gAux_fenetre:ptabListe_champ=gtabAux_liste_champs
gtabListe_fenetre[2]=gAux_fenetre

Je sais que c'est pas évident de passer en revue toutes les fenêtres et entrer tout les champs mais je vous assure que le gain généré plus tard dans la configuration et la formation chez les clients et le suivie en vaux la peine.
Mais comment allons nous utiliser cette liste??

Liste des fenêtres

Nous avons maintenons la liste des fenêtres et leurs champs. Mais nous n'allons pas les remplir une par une. Il suffit d'aller dans le code d’initialisation de la combo fenêtre, commenter le code existant (afin de pouvoir revenir en arrière en cas de besoin) et mettre ce bout de code:


ListeSupprimeTout(Fenetre)
POUR i=1 A 2
 ListeAjoute(Fenetre,gtabListe_fenetre[i]:sNom_fenetre)
FIN
Fenetre=1

Liste des champs

Dans cette partie nous allons nous intéresser à la fonction rafraichissement() puisque c'est elle qui remplit la liste des champs. Nous allons la modifier de la sorte:
// Rafraichissement de la fenêtre en fonction de la sélection de la fenêtre et de la configuration
PROCEDURE Rafraichissement()

// propose d'activer les options du menu "?"
//si Fenetre[Fenetre]="FI_MenuHWinDevHelp" alors
SI gtabListe_fenetre[Fenetre]:sNom_physique_fenetre="FI_MenuHWinDevHelp" ALORS
 SI ListeOccurrence(ColEtat)<>5
  ListeAjoute(ColEtat,"Actif")
 FIN
SINON SI ListeOccurrence(ColEtat)=5 ALORS
 ListeSupprime(ColEtat,5)
FIN

// vide la table
TableSupprimeTout(EtatObjet)

// filtre le fichier de configiguration des éléments
HFiltre("GPWConfigurationElement","Configuration",ComboConfiguration)

// selon la sélection dans la liste
// il faut remplir correctement
//selon type
// cas eTOUS : RemplirTous(Fenetre[Fenetre])
// cas eGROUPE : Remplirparent(Fenetre[Fenetre],typGrp)
// cas eMENU : RemplirMenu(Fenetre[Fenetre])
// cas eMENUCONTEXTUEL : RemplirMenuContextuel(Fenetre[Fenetre])
// cas eBOUTON : RemplirElement(Fenetre[Fenetre],typBouton)
// cas eSAISIE : RemplirSaisie(Fenetre[Fenetre])
// cas eLIBELLE : RemplirElement(Fenetre[Fenetre],typLibellé)
// cas eLISTE : RemplirElement(Fenetre[Fenetre],typListe)
// cas eCOMBO : RemplirCombo(Fenetre[Fenetre])
// cas eSELECTEUR : RemplirElement(Fenetre[Fenetre],typSélect)
// cas eINTERRUPTEUR : RemplirElement(Fenetre[Fenetre],typInterr)
// cas eTABLE : RemplirElement(Fenetre[Fenetre],typTable)
// cas eBARREOUTILS : RemplirElement(Fenetre[Fenetre],typBarreOutils)
// cas eIMAGE : RemplirImage(Fenetre[Fenetre])
// cas eONGLET : Remplirparent(Fenetre[Fenetre],typOnglet)
// cas eVOLET : RemplirVolet(Fenetre[Fenetre])
// CAS eSUPERCHAMP : RemplirParent(Fenetre[Fenetre],typSuperChamp)
// cas eAUTRE : RemplirAutre(Fenetre[Fenetre])
// cas eBOITEOUTILS : Remplirparent(Fenetre[Fenetre],typBoîteAOutils)
//FIN

POUR i=1 _A_ TableauOccurrence(gtabListe_fenetre[ListeSelect(Fenetre)]:ptabListe_champ)
 TableAjoute(EtatObjet)
 EtatObjet[i].ColElement=gtabListe_fenetre[ListeSelect(Fenetre)]:ptabListe_champ[i]:sLib_champ
 EtatObjet[i].ColGroupe=gtabListe_fenetre[ListeSelect(Fenetre)]:ptabListe_champ[i]:sGroupe_champ
 EtatObjet[i].ColNom=gtabListe_fenetre[ListeSelect(Fenetre)]:ptabListe_champ[i]:sNom_champ
 EtatObjet[i].ColType=gtabListe_fenetre[ListeSelect(Fenetre)]:ptabListe_champ[i]:xChamp_type
 EtatObjet[i].NomLogique=gtabListe_fenetre[ListeSelect(Fenetre)]:sNom_physique_fenetre+"."+gtabListe_fenetre[ListeSelect(Fenetre)]:ptabListe_champ[i]:sNom_physique_champ
 HLitRecherche(GPWConfigurationElement,Configuration_Element,ComboConfiguration..ValeurAffichée+","+EtatObjet[i].NomLogique,hIdentique)
 SI HTrouve(GPWConfigurationElement) ALORS
  EtatObjet[i].ColEtat=GPWConfigurationElement.etat
 SINON
  EtatObjet[i].ColEtat=1
 FIN
FIN

Et voila nous avons presque terminé tout ce qui nous reste c'est une petite modification sur l’état afin de prendre en charge le nom de la fenêtre.
C'est juste une petite ligne à modifier.
Dans la procédure mofietat():

// vérification de la dissociation d'un groupe
//c'est la ligne d'origine
//VerifDissociation(ComboConfiguration, Fenetre[Fenetre], sElement, nEtat, sListeGroupe, nType)
//c'est la nouvelle ligne
VerifDissociation(ComboConfiguration, gtabListe_fenetre[ListeSelect(Fenetre)]:sNom_physique_fenetre, sElement, nEtat, sListeGroupe, nType)
Et voila le tour est joué.
j’espère que ça vous sera utile.

N'hésitez pas à mettre des commentaire ou à me contacter en cas de besoin.
A la prochaine.