IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Code source de l'éditeur d'États Rapides - Partie 1 (4D 2004)

Les états rapides ont été révisés pour la version 2003 avec notamment un assistant permettant de créer plus aisément et rapidement les états désirés.Cet assistant est entièrement réalisé grâce à 4e Dimension, vous pouvez donc réaliser par vous-même un assistant similaire. Cependant comme cet assistant nécessite un certain travail nous avons décidé de vous donner dans la présente note technique le code source. Vous pourrez donc l'intégrer dans vos bases et y apporter les modifications souhaitées. ♪

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Les états rapides ont été révisés pour la version 2003 avec notamment un assistant permettant de créer plus aisément et rapidement les états désirés.
Cet assistant est entièrement réalisé grâce à 4e Dimension, vous pouvez donc réaliser par vous-même un assistant similaire. Cependant comme cet assistant nécessite un certain travail nous avons décidé de vous donner dans la présente note technique le code source. Vous pourrez donc l'intégrer dans vos bases et y apporter les modifications souhaitées. Ce code source est en 4D 2004.

Dans cette note technique, nous allons détailler l'architecture générale de l'assistant des états rapides en insistant sur certains points particuliers nous semblant plus ardus à comprendre. Néanmoins nous recommandons au lecteur de garder en référence la documentation sur les commandes liées aux états rapides ; pour le néophyte dans le domaine, une lecture préalable est fortement recommandée.

La base fournie en annexe de cette note technique est principalement constituée d'un dialogue et d'une série de méthodes. Parmi ces méthodes, celle qui est nommée __Open_dialog permet de lancer l'assistant.
Toutes les autres méthodes sont préfixées par NQR_  afin de préparer l'éventuelle mise en composant du code.

II. Le dialogue principal

Ce dialogue principal nommé « NQR_Dialog » (dans la table [Interface]) est constitué de 15 pages plus une page zéro.

Le dialogue contient à la fois l'assistant et le mode manuel obtenu dès l'appel aux états rapides.
Le mode manuel est codé en page 1 du dialogue. L'assistant occupe les pages 2 à 15.
La page zéro est pour sa part présente en fond de chaque page.
Une méthode formulaire complète le dialogue. Cette méthode formulaire est construite très classiquement autour d'un Au cas où réagissant à quatre événements : Sur chargement, Sur libération, Sur redimensionnement, Sur appel zone du plug in.

Lors de l'ouverture du dialogue, l'événement Sur chargement permettra d'initialiser l'ensemble des variables nécessaires. Symétriquement l'événement Sur libération permettra, lors de la fermeture du dialogue, de décharger de la mémoire les objets construits au cours de la navigation dans les pages.
Lors du redimensionnement du formulaire, certaines pages nécessitent un recalcul des objets ; c'est l'objet de l'événement Sur redimensionnement.
L'événement Sur appel zone de plugin est déclenché suite à certaines actions dans l'éditeur d'états rapide ; nous en reparlerons.

La page zéro est principalement constituée de :

  • la zone de plug-in nommée « nqr_area » ;
  • la zone de navigation de l'assistant constituée d'images et de boutons.
Pictures 0452x0292

La zone de plug in est de type « Report » c'est-à-dire l'éditeur d'états rapides. Cette zone représentera graphiquement le rapport en cours de construction et permettra de le modifier via une barre de menus et des barres d'outils.

Regardons à présent la page une du dialogue :

Pictures 0458x0294

Cette page semble assez confuse au premier abord. En effet nous y voyons en même temps le bouton permettant l'accès à l'assistant et le bouton de retour au mode manuel. Regardons la même page 1, en masquant la page zéro grâce à l'option « page 0 » du menu contextuel :

Pictures 0453x0364
Pictures 0454x0292

La page est à présent beaucoup plus claire ! Nous retrouvons dans cette page l'ensemble des boutons proposés par le mode manuel. La zone blanche en haut sera réservée à l'éditeur d'états rapides (la zone de plug-in). Comme la taille de la zone de plug-in diffère entre le mode manuel et l'assistant, il est nécessaire de la retailler lors d'un passage d'un mode à l'autre. Ainsi le code suivant permet d'adapter les dimensions lors du chargement du dialogue (le dialogue s'ouvre en mode manuel) :

 
Sélectionnez
1.
2.
3.
   `le dialogue s'ouvre en mode manuel ; on adapte la hauteur en conséquence
LIRE RECT OBJET(nqr_area;$g;$h;$d;$b)
DEPLACER OBJET(nqr_area;$g;$h;$d;213;*)

Ces lignes se trouvent dans la partie Sur chargement de la méthode formulaire.
De la même façon la méthode objet associée au bouton d'appel de l'assistant (bouton avec le chapeau et la baguette de magicien) se charge de changer la taille de la zone :

 
Sélectionnez
1.
2.
3.
   `nous entrons dans l'assistant ; on y adapte la taille de la zone de plugin
LIRE RECT OBJET(nqr_area;$g;$h;$d;$b)
DEPLACER OBJET(nqr_area;$g;$h;$d;$b-110;*)

Ce code est la première étape de l'initialisation de l'assistant et se trouve dans la méthode NQR_MP_Etape_Init.

Enfin lors du retour en mode manuel le code ci-dessous est appelé :

 
Sélectionnez
1.
2.
3.
   `nous revenons au mode manuel ; la taille de la zone de plugin est adaptée
LIRE RECT OBJET(nqr_area;$g;$h;$d;$b)
DEPLACER OBJET(nqr_area;$g;$h;$d;$b+110;*)

Ce code se trouve dans la méthode objet du bouton de la page zéro permettant le retour au mode manuel et symbolisé par une équerre, un crayon et une gomme.

Pour que ce changement dynamique de taille s'opère correctement, il est primordial que la zone de plug-in dessinée en page zéro soit placée au-dessus de tous les autres objets de la page. En effet la zone va recouvrir les autres objets et ainsi les masquer comme cela est désiré.

III. La liste des tables et des champs

Nous avons planté le décor du dialogue. Étudions à présent la liste des tables et des champs qui sont les principaux « acteurs » dans un état rapide.

L'initialisation s'effectue au sein de la méthode nommée NQR_Init_Struct_Description.

Cette méthode commence par la déclaration et le chargement de la liste des tables :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
   `la liste des tables

TABLEAU TEXTE(_nqr_table;0)
TABLEAU ENTIER LONG(_nqr_table_id;0)

LIRE TITRES TABLES(_nqr_table;_nqr_table_id)

L'utilisation de la commande LIRE TITRES TABLES implique que seules les tables désignées via la structure virtuelle sont proposées pour l'état rapide. Pour plus de détail sur les structures virtuelles voir la documentation de la commande FIXER TITRES TABLES.
Si le développeur ne définit pas de structure virtuelle, alors la liste des tables sera constituée des tables de la structure dont l'attribut « invisible » est décoché ; autrement dit les tables invisibles en structure le seront aussi dans le dialogue des états rapides.
Le tableau _nqr_table contiendra les noms des tables tandis que le tableau  _nqr_table_id contiendra le numéro d'ordre de création des tables dans la structure.

La méthode continue avec l'initialisation des tableaux pour les champs :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
   `la liste des champs par table

$nb_table:=Nombre de tables
TABLEAU TEXTE(_nqr_champ;$nb_table;0)

TABLEAU ENTIER LONG(_nqr_champ_id;$nb_table;0)

Ces tableaux sont à deux dimensions. La première dimension contiendra le numéro de la table ; la seconde sera le nom du champ pour  _nqr_champ et son numéro pour  _nqr_champ_id.

L'initialisation se termine par les tableaux de définition des relations entre les tables :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
   `la liste des liens

TABLEAU ENTIER(_nqr_table_depart;0)
TABLEAU ENTIER(_nqr_champ_depart;0)
TABLEAU ENTIER(_nqr_table_arrivee;0)
TABLEAU ENTIER(_nqr_champ_arrivee;0)
TABLEAU BOOLEEN(_nqr_allerauto;0)

TABLEAU BOOLEEN(_nqr_retourauto;0)

Il est maintenant temps d'alimenter les tableaux définis avec les valeurs adéquates :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
   `boucle sur la liste des tables VISIBLES pour charger les champs et les relations

Boucle ($Table;1;Taille tableau(_nqr_table_id);1)

      `recuperer le numéro de la table en cours d'étude
   $id_table:=_nqr_table_id{$Table}

      `lire la liste des champs et la placer dans les tableaux prévu à cet effet
   LIRE TITRES CHAMPS(Table($id_table)->;$_champ;$_id)
   COPIER TABLEAU($_champ;_nqr_champ{$id_table})
   COPIER TABLEAU($_id;_nqr_champ_id{$id_table})

      `recherche des relations partant de la table en cours d'étude
   Boucle ($Champ;1;Nombre de champs($id_table);1)

         `lecture de la nature du lien
      LIRE PROPRIETES LIEN($id_table;$Champ;$table_dest;$champ_dest;$descriminant;$allerauto;$retourauto)

         `si un lien existe
      Si ($champ_dest#0) & ($table_dest#0)
            `lire les propriétés du lien dans le contexte courant du process
         LIRE LIEN CHAMP(Champ($id_table;$Champ)->;$aller2;$retour2;*)
         Au cas ou
            : ($aller2=2)
               $allerauto:=Faux
            : ($aller2=3)
               $allerauto:=Vrai
            Sinon   `code défensif (inutile en principe)
               $allerauto:=Faux
         Fin de cas
         Au cas ou
            : ($retour2=2)
               $retourauto:=Faux
            : ($retour2=3)
               $retourauto:=Vrai
            Sinon   `code défensif (inutile en principe)
               $allerauto:=Faux
         Fin de cas

            `ajout des valeurs dans les tableaux prévus
         AJOUTER A TABLEAU(_nqr_table_depart;$id_table)
         AJOUTER A TABLEAU(_nqr_champ_depart;$Champ)
         AJOUTER A TABLEAU(_nqr_table_arrivee;$table_dest)
         AJOUTER A TABLEAU(_nqr_champ_arrivee;$champ_dest)
         AJOUTER A TABLEAU(_nqr_allerauto;$allerauto)
         AJOUTER A TABLEAU(_nqr_retourauto;$retourauto)
      Fin de si

   Fin de boucle

Fin de boucle

De même que précédemment, la structure virtuelle est récupérée via la commande LIRE TITRES CHAMPS.

4e Dimension 2004 apporte un contrôle beaucoup plus fin des liens avec notamment la possibilité de rendre automatique un lien précis et non plus l'ensemble des liens de la base. Avant d'utiliser le dialogue d'états rapides, un développeur peut décider d'activer tel ou tel lien. Pour en tenir compte nous utilisons la commande LIRE LIEN CHAMP qui permet de connaître précisément l'état du lien. L'utilisation de l'étoile en dernier paramètre précise que nous souhaitons connaître l'état courant au sein du process. Dans ce cas la commande ne retourne que deux valeurs : 2 ou 3 qui signifient respectivement que le lien est manuel ou automatique. Notez que le code prévoit une autre valeur et gère ce cas par une valeur par défaut. C'est normalement impossible, mais comme la commande est récente nous préférons faire du code défensif. En effet nous ne sommes pas à l'abri d'un bogue ou d'une incompréhension de notre part.

Il ne reste plus qu'à trier les noms des tables :

 
Sélectionnez
1.
2.
   `tri des tables par ordre alphabetique
TRIER TABLEAU(_nqr_table;_nqr_table_id)


Cette initialisation est réalisée une fois pour toutes au chargement du dialogue.

IV. Afficher la liste des tables et des champs

Il existe trois façons d'afficher les tables et les champs :

  • liste des champs de la table courante ;
  • liste des champs de la table courante et des tables liées à la table courante ;
  • liste des champs de toutes les tables ;
    (il existe une quatrième option, nous en reparlerons plus tard).

L'option retenue est définie via un popup menu (nommé _nqr_filter). Ces options sont définies via la lecture d'une ressource STR# :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
   `definir les options d'affichage (popup menu)

LISTE DE CHAINES VERS TABLEAU(14905;_nqr_filter)
_nqr_filter:=1
nqr_cb_1:=1
nqr_cb_2:=0
nqr_cb_3:=0
nqr_cb_4:=0
nqr_cb_5:=0

Le choix par défaut est le premier. Nous en profitons également pour définir une série de variables correspondant aux options possibles.
Dans les trois cas principaux, une liste hiérarchique est utilisée pour afficher la liste des champs : nqr_nqr_lh_champ.
Cette liste est alimentée par la méthode NQR_MP_Get_Fields.
Cette méthode commence par supprimer le contenu de la liste en vue de sa reconstruction complète. C'est en effet dans ce cas plus simple de tout refaire que de chercher à mettre à jour !

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
  `suppression des anciennes listes en vue de leur reconstruction complete.

Si (Liste existante(nqr_nqr_lh_champ))
   SUPPRIMER LISTE(nqr_nqr_lh_champ;*)
Fin de si
Si (Liste existante(nqr_nqr_lh_champ2))
   SUPPRIMER LISTE(nqr_nqr_lh_champ2;*)
Fin de si
Si (Liste existante(nqr_nqr_lh_champ3))
   SUPPRIMER LISTE(nqr_nqr_lh_champ3;*)
Fin de si

Comme vous pouvez le constater, nous traitons en fait 3 listes. Ceci est dû au fait que la liste des champs apparaît dans trois pages (page mode manuel, assistant liste, assistant tableau croisé) et qu'un objet liste hiérarchique ne peut être présent que sur une seule page d'un formulaire. Pour passer cette limitation, nous allons cloner la liste nqr_nqr_lh_champ après sa construction en deux autres listes nqr_nqr_lh_champ2 et nqr_nqr_lh_champ3 :

 
Sélectionnez
1.
2.
3.
4.
   `définition des listes clonées

nqr_nqr_lh_champ2:=Copier liste(nqr_nqr_lh_champ)
nqr_nqr_lh_champ3:=Copier liste(nqr_nqr_lh_champ)

La liste est définie par les lignes suivantes :

 
Sélectionnez
1.
2.
3.
4.
   `definition de la liste

nqr_nqr_lh_champ:=Nouvelle liste
CHANGER PROPRIETES LISTE(nqr_nqr_lh_champ;0;0;18;1)

Ensuite le code est un grand Au cas où gérant les 4 cas possibles. Étudions le premier cas : liste des champs de la table courante.
La table courante est définie par la variable _nqr_table (l'index du tableau de même nom). Notez qu'un bout de code défensif permet de s'assurer que la table courante est bien définie.

Voici le code qui construit la liste des champs :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
: (nqr_cb_1=1)   `liste des champs de la table courante

   `recuperer le numéro structurel de la table courante
$Table:=_nqr_table_id{_nqr_table}

   `boucle sur les champs de la table courante
Boucle ($champ_l;1;Taille tableau(_nqr_champ{$Table});1)
      `lire le numero structurel du champ
   $Champ:=_nqr_champ_id{$Table}{$champ_l}
      `Lecture du type du champ
   $Type:=Type(Champ($Table;$Champ)->)

      `recherche de la position du champ dans le tableau des identifiants
   $trouve_table:=Chercher dans tableau(_nqr_table_id;$Table)
   Si ($trouve_table>0)
      $trouve_champ:=Chercher dans tableau(_nqr_champ_id{$Table};$Champ)
   Sinon   `code défensif en cas de pb
      $trouve_champ:=-1
   Fin de si

   Au cas ou
      : ($trouve_table<0)   `pb avec la table ; ne rien faire
      : ($trouve_champ<0)   `pb avec le champ ; ne rien faire
      : ($Type=Est un BLOB )   `c'est un blob ; ne rien faire, car un blob ne peut être dans un etat rapide
      : ($Type=Est une sous table )   `c'est une sous table ; construire la liste des sous-champs

            `lecture de la liste des sous-champs
         LIRE TITRES CHAMPS(Champ($Table;$Champ)->;$_souschamps;$_souschamps_id)   `non supporté,
            `, mais fonctionnel
         $souschamp_liste:=Nouvelle liste
         Boucle ($souschamp;1;Taille tableau($_souschamps);1)
               `contruire la référence du sous-champ.

            $ref_souschamp:=($_souschamps_id{$souschamp} << 24)+($Table << 16)+$Champ
            AJOUTER A LISTE($souschamp_liste;$_souschamps{$souschamp};$ref_souschamp)
            CHANGER PROPRIETES ELEMENT($souschamp_liste;$ref_souschamp;
               Faux;Normal ;1150+Type(Champ($Table;$Champ;$_souschamps_id{$souschamp})->))
         Fin de boucle

            `construire la référence du champ
         $ref_champ:=($Table << 16)+$Champ
            `ajout du champ à la liste
         AJOUTER A LISTE(nqr_nqr_lh_champ;_nqr_champ{$Table}{$trouve_champ};
            $ref_champ;$souschamp_liste;Vrai)
         CHANGER PROPRIETES ELEMENT(nqr_nqr_lh_champ;$ref_champ;
            Faux;Normal ;1150+Type(Champ($Table;$Champ)->))
      Sinon   `c'est un champ valide ; afficher celui-ci dans la liste
            `construire la référence du champ
         $ref_champ:=($Table << 16)+$Champ
            `le champ est-il indexé
         LIRE PROPRIETES CHAMP($Table;$Champ;$Type;$Longueur;$index)
         Si ($index)
            $style:=Gras
         Sinon
            $style:=Normal
         Fin de si
            `ajout du champ à liste
         AJOUTER A LISTE(nqr_nqr_lh_champ;_nqr_champ{$Table}{$trouve_champ};$ref_champ)
         CHANGER PROPRIETES ELEMENT(nqr_nqr_lh_champ;$ref_champ;
            Faux;$style;1150+Type(Champ($Table;$Champ)->))

   Fin de cas

Fin de boucle

Ce code est relativement simple à comprendre. Notons cependant certains détails.
En premier lieu nous pouvons noter que les commandes LIRE TITRES CHAMPS et Champ fonctionnent également pour les sous-champs.

Attention : ceci n'est pas officiellement documenté. En effet si cela fonctionne, ce n'est pas supporté par 4D SA. Vous pouvez donc l'utiliser, mais en connaissance de cause.

Chaque élément au sein d'une liste hiérarchique doit avoir une référence unique associée. Cette référence est obligatoirement un entier long. Dans notre code nous avons choisi de construire cette référence en mixant le numéro de table et le numéro du champ. Ainsi nous pourrons retrouver ces informations par la suite en lisant la référence d'un élément sélectionné de la liste hiérarchique. Comment réaliser le mixage ? En partant d'une constatation simple : un entier long se code sur 32 bits. Le nombre de tables maximum est 255 : cela se code donc sur moins de 8 bits. Le nombre de champs maximum est 511 qui se code lui sur moins de 16 bits. Il y a donc de la place pour coder les deux nombres dans un entier long. Nous utilisons pour cela les opérateurs sur les bits :

 
Sélectionnez
1.
$ref_champ:=($Table << 16)+$Champ

Le numéro de table est décalé de 16 bits sur la gauche afin de laisser les 16 premiers bits libres pour le codage du numéro de champ.
Nous réalisons le même type de codage lorsqu'un sous-champ se présente :

 
Sélectionnez
1.
$ref_souschamp:=($_souschamps_id{$souschamp} << 24)+($Table << 16)+$Champ

Notons que le nombre de sous-champs est ici limité structurellement à 255 ce qui est largement suffisant !
Notons également que nous ne gérons pas les niveaux multiples de sous-tables qui ne sont plus supportés par 4D.

Étudions à présent le deuxième cas ou nous affichons la liste des champs de la table courante et des tables liées. Voici la partie du code qui gère ce cas :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
: (nqr_cb_2=1)   `liste des champs de la table courante et des tables liées à la table courante

      `recuperer le numéro structurel de la table courante
   $Table:=_nqr_table_id{_nqr_table}

      `boucle sur les champs de la table courante
   Boucle ($champ_l;1;Taille tableau(_nqr_champ{$Table});1)
         `lire le numero structurel du champ
      $Champ:=_nqr_champ_id{$Table}{$champ_l}
         `Lecture du type du champ
      $Type:=Type(Champ($Table;$Champ)->)

         `recherche de la position du champ dans le tableau des identifiants
      $trouve_table:=Chercher dans tableau(_nqr_table_id;$Table)
      Si ($trouve_table>0)
         $trouve_champ:=Chercher dans tableau(_nqr_champ_id{$Table};$Champ)
      Sinon   `code défensif en cas de pb
         $trouve_champ:=-1
      Fin de si
      Au cas où
         : ($trouve_table<0)   `pb avec la table ; ne rien faire
         : ($trouve_champ<0)   `pb avec le champ ; ne rien faire
         : ($Type=Est un BLOB )   `c'est un blob ; ne rien faire, car un blob ne peut être dans un etat rapide
         : ($Type=Est une sous table )   `c'est une sous table ; construire la liste des sous-champs
               `lecture de la liste des sous champs
            LIRE TITRES CHAMPS(Champ($Table;$Champ)->;$_souschamps;$_souschamps_id)
            $souschamp_liste:=Nouvelle liste
            Boucle ($souschamp;1;Taille tableau($_souschamps);1)
                  `contruire la référence du sous-champ
               $ref_souschamp:=($_souschamps_id{$souschamp} << 24)+($Table << 16)+$Champ
               AJOUTER A LISTE($souschamp_liste;$_souschamps{$souschamp};$ref_souschamp)
               CHANGER PROPRIETES ELEMENT($souschamp_liste;$ref_souschamp;
                  Faux;Normal ;1150+Type(Champ($Table;$Champ;$_souschamps_id{$souschamp})->))
            Fin de boucle
               `construire la référence du champ
            $ref_champ:=($Table << 16)+$Champ
            AJOUTER A LISTE(nqr_nqr_lh_champ;_nqr_champ{$Table}{$trouve_champ};
            $ref_champ;$souschamp_liste;Vrai)
            CHANGER PROPRIETES ELEMENT(nqr_nqr_lh_champ;$ref_champ;
               Faux;Normal ;1150+Type(Champ($Table;$Champ)->))

         Sinon   `c'est un champ valide;afficher celui-ci dans la liste
               `construire la référence du champ
            $ref_champ:=($Table << 16)+$Champ
            LIRE PROPRIETES CHAMP($Table;$Champ;$Type;$Longueur;$index)
            Si ($index)
               $style:=Gras
            Sinon
               $style:=Normal
            Fin de si

            $sous_liste:=0   `il n'y a pas encore de sous-liste

               `boucle sur les relations mémorisées
            Boucle ($tt;1;Taille tableau(_nqr_table_depart);1)
                  `etude du lien aller
               Si (_nqr_table_depart{$tt}=$Table) & (_nqr_champ_depart{$tt}=$Champ)   `c'est le bon champ de départ
                  Si (_nqr_allerauto{$tt})   `le lien est automatique (le calcul du rapport sera possible)
                        `recherche de la table d'arrivée
                     $trouve_table:=Chercher dans tableau(_nqr_table_id;_nqr_table_arrivee{$tt})
                     Si ($trouve_table>0)
                           `si la liste n'existe pas encore, la créer
                        Si ($sous_liste=0)
                           $sous_liste:=Nouvelle liste
                        Fin de si
                           `la référence choisie est - le numéro de la relation
                        $ref_champ2:=-$tt
                           `ajout de la table liée à la sous-liste
                        AJOUTER A LISTE($sous_liste;"["+_nqr_table{$trouve_table}+"]";$ref_champ2)
                        CHANGER PROPRIETES ELEMENT($sous_liste;$ref_champ2;Faux;Gras ;14930)
                     Fin de si
                  Fin de si
               Fin de si
                  `etude du lien retour
               Si (_nqr_table_arrivee{$tt}=$Table) & (_nqr_champ_arrivee{$tt}=$Champ)   `c'est le bon champ d'arrivée
                  Si (_nqr_retourauto{$tt}) & (nqr_hier=1)   `le lien est automatique (le calcul du rapport sera possible)
                        `et les liens retours sont autorisés
                        `recherche de la table de départ
                     $trouve_table:=Chercher dans tableau(_nqr_table_id;_nqr_table_depart{$tt})
                     Si ($trouve_table>0)
                           `si la liste n'existe pas encore, la créer
                        Si ($sous_liste=0)
                           $sous_liste:=Nouvelle liste
                        Fin de si
                           `la référence choisie est - le numéro de la relation décalée de 16 bits
                        $ref_champ2:=-($tt << 16)
                           `ajout de la table liée à la sous-liste
                        AJOUTER A LISTE($sous_liste;"["+_nqr_table{$trouve_table}+"]";$ref_champ2)
                        CHANGER PROPRIETES ELEMENT($sous_liste;$ref_champ2;Faux;Gras ;14931)
                     Fin de si
                  Fin de si
               Fin de si
            Fin de boucle
               `ajout du champ à la liste avec la sous-liste des relations éventuelle
            AJOUTER A LISTE(nqr_nqr_lh_champ;_nqr_champ{$Table}{$trouve_champ};$ref_champ;$sous_liste;Vrai)
            CHANGER PROPRIETES ELEMENT(nqr_nqr_lh_champ;$ref_champ;
               Faux;$style;1150+Type(Champ($Table;$Champ)->))
      Fin de cas
   Fin de boucle

Ce code est similaire sur de nombreux points a celui étudié précédemment. Dans le présent cas, si le champ est ni un blob ni un sous-champ, nous faisons une boucle sur les relations pour détecter les éventuelles relations partant ou arrivant sur ce champ. Si une relation existe, la table liée est affichée dans une sous liste associée à l'élément.

Ici nous utilisons des références négatives pour référencer les tables ainsi ajoutées au sein de la liste hiérarchique. De plus s'il s'agit d'un lien retour alors le numéro de table est décalé de 16 bits.
Comme vous le savez certainement, les liens se propagent dans 4D tant que le sens des liens de change pas. Ainsi il peut y avoir des tables liées en cascade. Or le code précédent ne gère qu'un niveau de tables liées. Il est donc nécessaire de compléter le code. Pour cela nous allons faire une boucle sur tous les éléments de la liste en y ajoutant au fur et à mesure les cascades de tables possibles :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
   `mise en place des liaisons en cascade

$element:=0   `on commence au début !
   `analyse de tous les éléments de la liste obtenue
Tant que ($element<Nombre elements(nqr_nqr_lh_champ))
   $element:=$element+1   `passer à l'element suivant
      `lire les informations sur l'élément et notamment la sous liste éventuellement associée
   INFORMATION ELEMENT(nqr_nqr_lh_champ;$element;$ref_element;$text;$sous_liste)
   Si ($ref_element<0)   `si la référence est négative alors il peut y avoir une cascade
      Si (((-$ref_element) >> 16)>0)
            `c'est un lien retour
            `lire la table d'arrivée
         $table_arrivee:=_nqr_table_depart{((-$ref_element) >> 16)}
            `boucle sur toutes les relations
         Boucle ($ttt;1;Taille tableau(_nqr_table_arrivee))
            Si (_nqr_table_arrivee{$ttt}=$table_arrivee)   `c'est la bonne table d'arrivée
               Si (_nqr_retourauto{$ttt}) & (nqr_hier=1)   `le lien est automatique (le calcul du rapport sera possible)
                     `et les liens retours sont autorisés
                     `recherche de la table de départ
                  $trouve_table:=Chercher dans tableau(_nqr_table_id;_nqr_table_depart{$ttt})
                  Si ($trouve_table>0)
                        `si la liste n'existe pas encore, la créer
                     Si ($sous_liste=0)
                        $sous_liste:=Nouvelle liste
                     Fin de si
                     $ref_champ3:=-($ttt << 16)
                     AJOUTER A LISTE($sous_liste;"["+_nqr_table{$trouve_table}+"]";$ref_champ3)
                     CHANGER PROPRIETES ELEMENT($sous_liste;$ref_champ3;Faux;Gras ;14931)
                  Fin de si
               Fin de si
            Fin de si
         Fin de boucle
            `modifier l'élément pour lui ajouter l'éventuelle nouvelle sous liste
         CHANGER ELEMENT(nqr_nqr_lh_champ;$ref_element;$text;$ref_element;$sous_liste;Vrai)
      Sinon
            `c'est un lien aller
            `lire la table de départ
         $table_depart:=_nqr_table_arrivee{-$ref_element}
            `boucle sur toutes les relations
         Boucle ($ttt;1;Taille tableau(_nqr_table_depart))
            Si (_nqr_table_depart{$ttt}=$table_depart)   `c'est la bonne table de départ
               Si (_nqr_allerauto{$ttt})   `le lien est automatique (le calcul du rapport sera possible)
                     `recherche de la table d'arrivée
                  $trouve_table:=Chercher dans tableau(_nqr_table_id;_nqr_table_arrivee{$ttt})
                  Si ($trouve_table>0)   `si la liste n'existe pas encore, la créer
                     Si ($sous_liste=0)
                        $sous_liste:=Nouvelle liste
                     Fin de si
                     $ref_champ3:=-$ttt
                     AJOUTER A LISTE($sous_liste;"["+_nqr_table{$trouve_table}+"]";$ref_champ3)
                     CHANGER PROPRIETES ELEMENT($sous_liste;$ref_champ3;Faux;Gras ;14930)
                  Fin de si
               Fin de si
            Fin de si
         Fin de boucle
            ` modifier l'élément pour lui ajouter l'éventuelle nouvelle sous liste
         CHANGER ELEMENT(nqr_nqr_lh_champ;$ref_element;$text;$ref_element;$sous_liste;Vrai)
      Fin de si
   Fin de si
Fin tant que

Notons ici l'astuce principale qui consiste à tirer parti du fait que lors de l'ajout d'un élément dans une liste celui-ci se trouve alors sous l'élément parent (celui en cours d'étude). Ainsi lors du pas suivant de la boucle ce nouvel élément sera de suite étudié pour voir si lui-même ne génère pas une nouvelle cascade.
L'utilisation de la commande CHANGER ELEMENT permet de modifier un élément déjà présent dans la liste pour lui ajouter une sous-liste éventuelle dans notre cas.

Le dernier cas (liste de toutes les tables) est très similaire au niveau de la logique, et une étude est à présent inutile si vous avez compris les études précédentes !

V. Afficher la liste des champs liés

Nous venons de voir que les tables liées s'affichent au sein de la liste des champs. Or ce sont des champs qui vont alimenter l'état rapide. Pour cela vous avez sûrement remarqué qu'un clic sur une telle table fait apparaître une seconde liste. Cette seconde liste contient une liste des champs de la table liée. Cette liste est construite par la méthode NQR_MP_View_Fields. Étudions celle-ci.

La première partie de la méthode initialise les coordonnées et les noms des différentes listes rentrant en jeu en fonction des pages :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
   `choix des coordonnées en fonction de la page

$continuer:=Vrai
Au cas ou
   : (Page formulaire courante=1)
      $liste_champ:=nqr_nqr_lh_champ
      $liste_detail:=nqr_nqr_lh_champ_Detail
      $h1:=162
      $h2:=84
      $h3:=90
      $suffixe:=""

   : (Page formulaire courante=3)
      $liste_champ:=nqr_nqr_lh_champ2
      $liste_detail:=nqr_nqr_lh_champ_Detail2
      $h1:=158
      $h2:=64
      $h3:=70
      $suffixe:="2"

   : (Page formulaire courante=7)
      $liste_champ:=nqr_nqr_lh_champ3
      $liste_detail:=nqr_nqr_lh_champ_Detail3
      $h1:=205
      $h2:=110
      $h3:=116
      $suffixe:="3"

   Sinon   `code défensif
      $continuer:=Faux

Fin de cas

Notez qu'ici les coordonnées sont des hauteurs écrites « en dur », ce qui sous-entend que si vous désirez modifier géographiquement les listes il faudra faire évoluer en conséquence les chiffres.
Ensuite, en fonction de l'élément sélectionné dans la liste principale, le code suivant est exécuté :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
$item:=Elements selectionnes($liste_champ)

INFORMATION ELEMENT($liste_champ;$item;$ref_champ;$text)

Au cas cas
   : ($ref_champ>=0)   `c'est un champ ou un sous-champ
         `lire la position de la liste principale
      LIRE RECT OBJET(*;("nqr_lh_champ"+$suffixe);$g;$h;$d;$b)
         `la liste principale prend l'ensemble de la place
      DEPLACER OBJET(*;("nqr_lh_champ"+$suffixe);$g;$h;$d;$h+$h1;*)
         `la liste secondaire est rejetée hors de la zone visible
      DEPLACER OBJET(*;("nqr_lh_champ_detail"+$suffixe);10000;10000;10000;10000;*)
      SELECTIONNER ELEMENTS PAR POSITION($liste_champ;$item)

   : ($ref_champ<0)   `c'est une table
         `lire la position de la liste principale
      LIRE RECT OBJET(*;("nqr_lh_champ"+$suffixe);$g;$h;$d;$b)
         `adapter la position à la moitié de la place
      DEPLACER OBJET(*;("nqr_lh_champ"+$suffixe);$g;$h;$d;$h+$h2;*)
         `l'autrre moitié est pour la liste secondaire
      DEPLACER OBJET(*;("nqr_lh_champ_detail"+$suffixe);$g;$h+$h3;$d;$h+$h1;*)

      SELECTIONNER ELEMENTS PAR REFERENCE($liste_champ;$ref_champ)

Le principe est de déplacer les objets « listes » en fonction du contexte. Notons que les coordonnées horizontales sont déduites de la position précédente de la liste principale ce qui évite d'avoir à trop coder « en dur » les coordonnées.

Le code ensuite exécuté permet de construire la liste des champs liés suivants des principes et des notations similaires aux codes précédents.

Notons enfin les dernières lignes de la méthode :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
   `mise à jour des informations dans les listes

Au cas ou
   : (Page formulaire courante=1)
      nqr_lh_champ_Detail:=$liste_detail
      ALLER A CHAMP(nqr_lh_champ_Detail)
      REDESSINER LISTE(nqr_lh_champ)
      Si (Liste existante(nqr_lh_champ_detail))
         CHANGER PROPRIETES LISTE(nqr_lh_champ_detail;0;0;18;1)
         REDESSINER LISTE(nqr_lh_champ_detail)
      Fin de si

   : (Page formulaire courante=3)
      nqr_lh_champ_Detail2:=$liste_detail
      ALLER A CHAMP(nqr_lh_champ_Detail2)
      REDESSINER LISTE(nqr_lh_champ2)
      Si (Liste existante(nqr_lh_champ_detail2))
         CHANGER PROPRIETES LISTE(nqr_lh_champ_detail2;0;0;18;1)
         REDESSINER LISTE(nqr_lh_champ_detail2)
      Fin de si

   : (Page formulaire courante=7)
      nqr_lh_champ_Detail3:=$liste_detail
      ALLER A CHAMP(nqr_lh_champ_Detail3)
      REDESSINER LISTE(nqr_lh_champ3)
      Si (Liste existante(nqr_lh_champ_detail3))
         CHANGER PROPRIETES LISTE(nqr_lh_champ_detail3;0;0;18;1)
         REDESSINER LISTE(nqr_lh_champ_detail3)
      Fin de si
Fin de cas

Pourquoi faire un code détaillé ici alors que tout le reste de la méthode est générique ? Cette anomalie est liée a l'utilisation de la commande REDESSINER LISTE qui doit utiliser explicitement le nom de la liste à mettre à jour. En effet, le code suivant n'aurait pas fonctionné :

 
Sélectionnez
1.
REDESSINER LISTE($liste_detail)   ` NE FONCTIONNE PAS !

Utilisation des listes des tables et des champs

Maintenant que les listes de champs sont prêtes à l'emploi, regardons comment elles sont utilisées.
Pour insérer un champ dans un rapport, nous pouvons utiliser soit le glisser-déposer, soit le double-clic sur un champ, soit les boutons dédiés à cette action.

Pour la gestion du glisser-déposer regardons la méthode objet associée à la zone de plug-in « nqr_area ».

Cette méthode objet gère les deux événements de base du glisser-déposer (Sur glisser et Sur déposer) dans une structure Au cas où. Cette méthode objet gère un paramètre de retour $0 pour accepter ou non l'action. Ainsi si lors du glissé, nous estimons que l'objet proposé au traitement ne convient pas nous refusons l'action en forçant $0 à -1. Dans le cas contraire, nous passons la valeur de $0 à zéro.

Pour déterminer la nature de l'objet proposé au traitement, nous analysons la source via les commandes PROPRIETES GLISSER DEPOSER et RESOUDRE POINTEUR. Nous obtenons ainsi le nom de l'objet proposé. Ensuite au sein de celui-ci nous regardons l'élément sélectionné et nous décodons sa référence pour retrouver les numéros structurels de table, de champ et de sous-champ. Ce décodage est effectué grâce aux lignes suivantes :

 
Sélectionnez
1.
2.
3.
$Table:=($ref_champ & 0x00FFFFFF) >> 16   `decodage de la table
$champ:=$ref_champ & 0xFFFF   `decodage du champ
$souschamp:=$ref_champ >> 24   `decodage du sous-champ

Dans le traitement de l'événement Sur déposer, nous traitons en deux cas distincts le rapport en liste et les tableaux croisés. La différence est principalement au niveau du nombre de colonnes qui est fixe (3) pour les tableaux croisés et variable pour les rapports en liste.
Nous testons aussi le type du champ glissé pour refuser les sous-tables qui ne peuvent être des colonnes d'un état.
Dans le cas d'un rapport en liste, le code suivant permet de supprimer les tris sur les colonnes transformées lors du glissé déposé en colonne de type texte, ou image (et sous table par sécurité bien que cela soit a priori inutile) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
Si ($Type=Est un texte ) | ($Type=Est une image ) | ($Type=Est une sous table )   ` ces types ne sont pas triables ; il faut enlever les tris
   QR LIRE TRIS(nqr_area;_nqr_col;_nqr_order)
   $trouve:=Chercher dans tableau(_nqr_col;nqr_current_col)
   Si ($trouve>0)
      SUPPRIMER LIGNES(_nqr_col;$trouve)
      SUPPRIMER LIGNES(_nqr_order;$trouve)
      QR FIXER TRIS(nqr_area;_nqr_col;_nqr_order)
   Fin de si
Fin de si

Dans le cas d'un double-clic sur la liste des champs les commandes QR INSERER COLONNE et QR FIXER INFO COLONNE entrent en jeu. En effet dans le cadre d'un rapport en liste, on ajoute la colonne à la fin du rapport alors que dans le cas d'un tableau croisé le champ est affecté a une des trois colonnes prédéterminées de l'état. Le code ci-dessous de la méthode objet nqr_lh_champ permet de déterminer la colonne ciblée :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
   `dans quelle colonne positionner le champ ?

Au cas ou
   : (nqr_tc_colonne="")  `la colonne 1 est libre
      $colonne:=1
   : (nqr_tc_ligne="")  `la colonne 2 (les lignes) est libre
      $colonne:=2
   : (nqr_tc_cellule="")`la colonne 3 (les cellules) est libre
      $colonne:=3
   Sinon
      $colonne:=3

Fin de cas

Si nous utilisons le bouton permettant d'insérer un champ en tant que critère de tri, la difficulté réside dans la création si nécessaire de la colonne associée. C'est l'objet de la méthode objet du bouton nqr_bOne1.

Dans cette méthode, nous retrouvons les éléments de contrôle déjà évoqué plus haut. Pour savoir si le champ est déjà affecté à une colonne nous utilisons le code suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
   `la colonne pour le critère de tri existe-t-elle déjà ?

$present:=Faux   `initialisation de la réponse
$table_name:=Nom de la table($Table)
$champ_name:=Nom du champ($Table;$Champ)
   `boucle sur l'ensemble des colonnes
Boucle ($i;1;QR Nombre de colonnes(nqr_area);1)
      `lecture des infos pour recuperer le contenu de la colonne
   QR LIRE INFO COLONNE(nqr_area;$i;$title;$field;$hide;$size;$repeat;$format)
      `comparaison du contenu au champ qui va servir de critère de tri
   Au cas ou
      : ($field#("["+$table_name+"]"+$champ_name)
         `ce n'est pas le bon champ ; ne rien faire
      Sinon
         $present:=Vrai   `c'est le bon champ
         $colonne:=$i   `arrêter la boucle
   Fin de cas

Fin de boucle

Dans ce cas, nous sommes obligés de passer par une boucle et une comparaison avec le nom du champ et de la table afin de trouver la réponse.

VI. Utiliser les paramètres avancés

Afin de contrôler le contexte d'utilisation, tout comme dans la commande QR ETAT, vous disposez de quatre variables permettant de définir :

  • si l'assistant est accessible ;
  • si le choix de la table et le dialogue de recherche sont affichés ;
  • si les liens retour sont autorisés ;
  • la table sélectionnée par défaut.

Les variables correspondantes sont respectivement :

  • nqr_wizard (0=masqué ; 1=affiché) ;
  • nqr_recherche (0=masqué ; 1=affiché) ;
  • nqr_hier (0=non autorisé ; 1=autorisé) ;
  • nqr_table_defaut (numéro de la table par défaut ; 0 si pas de table définie : le choix est alors automatiquement réalisé par le dialogue).

Pour mettre en œuvre ces choix, il suffit d'affecter les variables avant l'appel du dialogue comme dans le code suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
nqr_wizard:=1   `afficher l'assistant

nqr_recherche:=1   `afficher la table et les recherches
nqr_hier:=1   `autoriser les liens retours
$ref:=Creer fenetre formulaire([Interface];"NQR_Dialog")
DIALOGUE([Interface];"NQR_Dialog")

FERMER FENETRE

VII. Traduction de l’interface

Comme vous l'avez déjà peut-être remarqué, le dialogue utilise un certain nombre de ressources dont les identifiants commencent à la valeur 14900.
Vous pouvez les utiliser, mais si vous devez les modifier, nous vous conseillons vivement de créer votre propre fichier de ressources et d'utiliser alors une plage de numéro au-delà de 15000 comme le stipulent les règles d'usage en la matière.

VIII. Conclusion

Nous sommes à présent au terme de cette première partie. Vous avez normalement en main tous les outils pour adapter, améliorer ou simplement utiliser le mode manuel de l'éditeur d'états rapides.

Dans la seconde partie, nous aborderons en profondeur l'assistant de création, soit les pages 2 à 15 du dialogue. Il y a donc encore de la lecture en perspective !

IX. Base exemple

Téléchargez la base exemple :

base exemple

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.