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

Olivier DESCHANELS

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. ♪

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

- Auteur : Olivier DESCHANELS

- Parution : 1 juillet 2004

- Licence : 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.

Sommaire

- I. Introduction

- II. Le dialogue principal

- III. La liste des tables et des champs

- IV. Afficher la liste des tables et des champs

- V. Afficher la liste des champs liés

- Utilisation des listes des tables et des champs

- VI. Utiliser les paramètres avancés

- VII. Traduction de l’interface

- VIII. Conclusion

- IX. Base exemple

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.

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 :

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 :

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) :

1. `le dialogue s'ouvre en mode manuel ; on adapte la hauteur en conséquence

2. LIRE RECT OBJET(nqr_area;$g;$h;$d;$b)

3. 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 :

1. `nous entrons dans l'assistant ; on y adapte la taille de la zone de plugin

2. LIRE RECT OBJET(nqr_area;$g;$h;$d;$b)

3. 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é :

1. `nous revenons au mode manuel ; la taille de la zone de plugin est adaptée

2. LIRE RECT OBJET(nqr_area;$g;$h;$d;$b)

3. 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 :

1. `la liste des tables

3. TABLEAU TEXTE(_nqr_table;0)

4. TABLEAU ENTIER LONG(_nqr_table_id;0)

6. 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 :

1. `la liste des champs par table

3. $nb_table:=Nombre de tables

4. TABLEAU TEXTE(_nqr_champ;$nb_table;0)

6. 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 :

1. `la liste des liens

3. TABLEAU ENTIER(_nqr_table_depart;0)

4. TABLEAU ENTIER(_nqr_champ_depart;0)

5. TABLEAU ENTIER(_nqr_table_arrivee;0)

6. TABLEAU ENTIER(_nqr_champ_arrivee;0)

7. TABLEAU BOOLEEN(_nqr_allerauto;0)

9. TABLEAU BOOLEEN(_nqr_retourauto;0)

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

1. `boucle sur la liste des tables VISIBLES pour charger les champs et les relations

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

5. `recuperer le numéro de la table en cours d'étude

6. $id_table:=_nqr_table_id{$Table}

8. `lire la liste des champs et la placer dans les tableaux prévu à cet effet

9. LIRE TITRES CHAMPS(Table($id_table)->;$_champ;$_id)

10. COPIER TABLEAU($_champ;_nqr_champ{$id_table})

11. COPIER TABLEAU($_id;_nqr_champ_id{$id_table})

13. `recherche des relations partant de la table en cours d'étude

14. Boucle ($Champ;1;Nombre de champs($id_table);1)

16. `lecture de la nature du lien

17. LIRE PROPRIETES LIEN($id_table;$Champ;$table_dest;$champ_dest;$descriminant;$allerauto;$retourauto)

19. `si un lien existe

20. Si ($champ_dest#0) & ($table_dest#0)

21. `lire les propriétés du lien dans le contexte courant du process

22. LIRE LIEN CHAMP(Champ($id_table;$Champ)->;$aller2;$retour2;*)

23. Au cas ou

24. : ($aller2=2)

25. $allerauto:=Faux

26. : ($aller2=3)

27. $allerauto:=Vrai

28. Sinon   `code défensif (inutile en principe)

29. $allerauto:=Faux

30. Fin de cas

31. Au cas ou

32. : ($retour2=2)

33. $retourauto:=Faux

34. : ($retour2=3)

35. $retourauto:=Vrai

36. Sinon   `code défensif (inutile en principe)

37. $allerauto:=Faux

38. Fin de cas

40. `ajout des valeurs dans les tableaux prévus

41. AJOUTER A TABLEAU(_nqr_table_depart;$id_table)

42. AJOUTER A TABLEAU(_nqr_champ_depart;$Champ)

43. AJOUTER A TABLEAU(_nqr_table_arrivee;$table_dest)

44. AJOUTER A TABLEAU(_nqr_champ_arrivee;$champ_dest)

45. AJOUTER A TABLEAU(_nqr_allerauto;$allerauto)

46. AJOUTER A TABLEAU(_nqr_retourauto;$retourauto)

47. Fin de si

49. Fin de boucle

51. 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 :

1. `tri des tables par ordre alphabetique

2. 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# :

1. `definir les options d'affichage (popup menu)

3. LISTE DE CHAINES VERS TABLEAU(14905;_nqr_filter)

4. _nqr_filter:=1

5. nqr_cb_1:=1

6. nqr_cb_2:=0

7. nqr_cb_3:=0

8. nqr_cb_4:=0

9. 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 !

1. `suppression des anciennes listes en vue de leur reconstruction complete.

3. Si (Liste existante(nqr_nqr_lh_champ))

4. SUPPRIMER LISTE(nqr_nqr_lh_champ;*)

5. Fin de si

6. Si (Liste existante(nqr_nqr_lh_champ2))

7. SUPPRIMER LISTE(nqr_nqr_lh_champ2;*)

8. Fin de si

9. Si (Liste existante(nqr_nqr_lh_champ3))

10. SUPPRIMER LISTE(nqr_nqr_lh_champ3;*)

11. 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 :

1. `définition des listes clonées

3. nqr_nqr_lh_champ2:=Copier liste(nqr_nqr_lh_champ)

4. nqr_nqr_lh_champ3:=Copier liste(nqr_nqr_lh_champ)

La liste est définie par les lignes suivantes :

1. `definition de la liste

3. nqr_nqr_lh_champ:=Nouvelle liste

4. 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 :

1. : (nqr_cb_1=1)   `liste des champs de la table courante

3. `recuperer le numéro structurel de la table courante

4. $Table:=_nqr_table_id{_nqr_table}

6. `boucle sur les champs de la table courante

7. Boucle ($champ_l;1;Taille tableau(_nqr_champ{$Table});1)

8. `lire le numero structurel du champ

9. $Champ:=_nqr_champ_id{$Table}{$champ_l}

10. `Lecture du type du champ

11. $Type:=Type(Champ($Table;$Champ)->)

13. `recherche de la position du champ dans le tableau des identifiants

14. $trouve_table:=Chercher dans tableau(_nqr_table_id;$Table)

15. Si ($trouve_table>0)

16. $trouve_champ:=Chercher dans tableau(_nqr_champ_id{$Table};$Champ)

17. Sinon   `code défensif en cas de pb

18. $trouve_champ:=-1

19. Fin de si

21. Au cas ou

22. : ($trouve_table<0)   `pb avec la table ; ne rien faire

23. : ($trouve_champ<0)   `pb avec le champ ; ne rien faire

24. : ($Type=Est un BLOB )   `c'est un blob ; ne rien faire, car un blob ne peut être dans un etat rapide

25. : ($Type=Est une sous table )   `c'est une sous table ; construire la liste des sous-champs

27. `lecture de la liste des sous-champs

28. LIRE TITRES CHAMPS(Champ($Table;$Champ)->;$_souschamps;$_souschamps_id)   `non supporté,

29. `, mais fonctionnel

30. $souschamp_liste:=Nouvelle liste

31. Boucle ($souschamp;1;Taille tableau($_souschamps);1)

32. `contruire la référence du sous-champ.

34. $ref_souschamp:=($_souschamps_id{$souschamp} << 24)+($Table << 16)+$Champ

35. AJOUTER A LISTE($souschamp_liste;$_souschamps{$souschamp};$ref_souschamp)

36. CHANGER PROPRIETES ELEMENT($souschamp_liste;$ref_souschamp;

37. Faux;Normal ;1150+Type(Champ($Table;$Champ;$_souschamps_id{$souschamp})->))

38. Fin de boucle

40. `construire la référence du champ

41. $ref_champ:=($Table << 16)+$Champ

42. `ajout du champ à la liste

43. AJOUTER A LISTE(nqr_nqr_lh_champ;_nqr_champ{$Table}{$trouve_champ};

44. $ref_champ;$souschamp_liste;Vrai)

45. CHANGER PROPRIETES ELEMENT(nqr_nqr_lh_champ;$ref_champ;

46. Faux;Normal ;1150+Type(Champ($Table;$Champ)->))

47. Sinon   `c'est un champ valide ; afficher celui-ci dans la liste

48. `construire la référence du champ

49. $ref_champ:=($Table << 16)+$Champ

50. `le champ est-il indexé

51. LIRE PROPRIETES CHAMP($Table;$Champ;$Type;$Longueur;$index)

52. Si ($index)

53. $style:=Gras

54. Sinon

55. $style:=Normal

56. Fin de si

57. `ajout du champ à liste

58. AJOUTER A LISTE(nqr_nqr_lh_champ;_nqr_champ{$Table}{$trouve_champ};$ref_champ)

59. CHANGER PROPRIETES ELEMENT(nqr_nqr_lh_champ;$ref_champ;

60. Faux;$style;1150+Type(Champ($Table;$Champ)->))

62. Fin de cas

64. 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 :

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 :

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 :

1. : (nqr_cb_2=1)   `liste des champs de la table courante et des tables liées à la table courante

3. `recuperer le numéro structurel de la table courante

4. $Table:=_nqr_table_id{_nqr_table}

6. `boucle sur les champs de la table courante

7. Boucle ($champ_l;1;Taille tableau(_nqr_champ{$Table});1)

8. `lire le numero structurel du champ

9. $Champ:=_nqr_champ_id{$Table}{$champ_l}

10. `Lecture du type du champ

11. $Type:=Type(Champ($Table;$Champ)->)

13. `recherche de la position du champ dans le tableau des identifiants

14. $trouve_table:=Chercher dans tableau(_nqr_table_id;$Table)

15. Si ($trouve_table>0)

16. $trouve_champ:=Chercher dans tableau(_nqr_champ_id{$Table};$Champ)

17. Sinon   `code défensif en cas de pb

18. $trouve_champ:=-1

19. Fin de si

20. Au cas où

21. : ($trouve_table<0)   `pb avec la table ; ne rien faire

22. : ($trouve_champ<0)   `pb avec le champ ; ne rien faire

23. : ($Type=Est un BLOB )   `c'est un blob ; ne rien faire, car un blob ne peut être dans un etat rapide

24. : ($Type=Est une sous table )   `c'est une sous table ; construire la liste des sous-champs

25. `lecture de la liste des sous champs

26. LIRE TITRES CHAMPS(Champ($Table;$Champ)->;$_souschamps;$_souschamps_id)

27. $souschamp_liste:=Nouvelle liste

28. Boucle ($souschamp;1;Taille tableau($_souschamps);1)

29. `contruire la référence du sous-champ

30. $ref_souschamp:=($_souschamps_id{$souschamp} << 24)+($Table << 16)+$Champ

31. AJOUTER A LISTE($souschamp_liste;$_souschamps{$souschamp};$ref_souschamp)

32. CHANGER PROPRIETES ELEMENT($souschamp_liste;$ref_souschamp;

33. Faux;Normal ;1150+Type(Champ($Table;$Champ;$_souschamps_id{$souschamp})->))

34. Fin de boucle

35. `construire la référence du champ

36. $ref_champ:=($Table << 16)+$Champ

37. AJOUTER A LISTE(nqr_nqr_lh_champ;_nqr_champ{$Table}{$trouve_champ};

38. $ref_champ;$souschamp_liste;Vrai)

39. CHANGER PROPRIETES ELEMENT(nqr_nqr_lh_champ;$ref_champ;

40. Faux;Normal ;1150+Type(Champ($Table;$Champ)->))

42. Sinon   `c'est un champ valide;afficher celui-ci dans la liste

43. `construire la référence du champ

44. $ref_champ:=($Table << 16)+$Champ

45. LIRE PROPRIETES CHAMP($Table;$Champ;$Type;$Longueur;$index)

46. Si ($index)

47. $style:=Gras

48. Sinon

49. $style:=Normal

50. Fin de si

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

54. `boucle sur les relations mémorisées

55. Boucle ($tt;1;Taille tableau(_nqr_table_depart);1)

56. `etude du lien aller

57. Si (_nqr_table_depart{$tt}=$Table) & (_nqr_champ_depart{$tt}=$Champ)   `c'est le bon champ de départ

58. Si (_nqr_allerauto{$tt})   `le lien est automatique (le calcul du rapport sera possible)

59. `recherche de la table d'arrivée

60. $trouve_table:=Chercher dans tableau(_nqr_table_id;_nqr_table_arrivee{$tt})

61. Si ($trouve_table>0)

62. `si la liste n'existe pas encore, la créer

63. Si ($sous_liste=0)

64. $sous_liste:=Nouvelle liste

65. Fin de si

66. `la référence choisie est - le numéro de la relation

67. $ref_champ2:=-$tt

68. `ajout de la table liée à la sous-liste

69. AJOUTER A LISTE($sous_liste;"["+_nqr_table{$trouve_table}+"]";$ref_champ2)

70. CHANGER PROPRIETES ELEMENT($sous_liste;$ref_champ2;Faux;Gras ;14930)

71. Fin de si

72. Fin de si

73. Fin de si

74. `etude du lien retour

75. Si (_nqr_table_arrivee{$tt}=$Table) & (_nqr_champ_arrivee{$tt}=$Champ)   `c'est le bon champ d'arrivée

76. Si (_nqr_retourauto{$tt}) & (nqr_hier=1)   `le lien est automatique (le calcul du rapport sera possible)

77. `et les liens retours sont autorisés

78. `recherche de la table de départ

79. $trouve_table:=Chercher dans tableau(_nqr_table_id;_nqr_table_depart{$tt})

80. Si ($trouve_table>0)

81. `si la liste n'existe pas encore, la créer

82. Si ($sous_liste=0)

83. $sous_liste:=Nouvelle liste

84. Fin de si

85. `la référence choisie est - le numéro de la relation décalée de 16 bits

86. $ref_champ2:=-($tt << 16)

87. `ajout de la table liée à la sous-liste

88. AJOUTER A LISTE($sous_liste;"["+_nqr_table{$trouve_table}+"]";$ref_champ2)

89. CHANGER PROPRIETES ELEMENT($sous_liste;$ref_champ2;Faux;Gras ;14931)

90. Fin de si

91. Fin de si

92. Fin de si

93. Fin de boucle

94. `ajout du champ à la liste avec la sous-liste des relations éventuelle

95. AJOUTER A LISTE(nqr_nqr_lh_champ;_nqr_champ{$Table}{$trouve_champ};$ref_champ;$sous_liste;Vrai)

96. CHANGER PROPRIETES ELEMENT(nqr_nqr_lh_champ;$ref_champ;

97. Faux;$style;1150+Type(Champ($Table;$Champ)->))

98. Fin de cas

99. 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 :

1. `mise en place des liaisons en cascade

3. $element:=0   `on commence au début !

4. `analyse de tous les éléments de la liste obtenue

5. Tant que ($element<Nombre elements(nqr_nqr_lh_champ))

6. $element:=$element+1   `passer à l'element suivant

7. `lire les informations sur l'élément et notamment la sous liste éventuellement associée

8. INFORMATION ELEMENT(nqr_nqr_lh_champ;$element;$ref_element;$text;$sous_liste)

9. Si ($ref_element<0)   `si la référence est négative alors il peut y avoir une cascade

10. Si (((-$ref_element) >> 16)>0)

11. `c'est un lien retour

12. `lire la table d'arrivée

13. $table_arrivee:=_nqr_table_depart{((-$ref_element) >> 16)}

14. `boucle sur toutes les relations

15. Boucle ($ttt;1;Taille tableau(_nqr_table_arrivee))

16. Si (_nqr_table_arrivee{$ttt}=$table_arrivee)   `c'est la bonne table d'arrivée

17. Si (_nqr_retourauto{$ttt}) & (nqr_hier=1)   `le lien est automatique (le calcul du rapport sera possible)

18. `et les liens retours sont autorisés

19. `recherche de la table de départ

20. $trouve_table:=Chercher dans tableau(_nqr_table_id;_nqr_table_depart{$ttt})

21. Si ($trouve_table>0)

22. `si la liste n'existe pas encore, la créer

23. Si ($sous_liste=0)

24. $sous_liste:=Nouvelle liste

25. Fin de si

26. $ref_champ3:=-($ttt << 16)

27. AJOUTER A LISTE($sous_liste;"["+_nqr_table{$trouve_table}+"]";$ref_champ3)

28. CHANGER PROPRIETES ELEMENT($sous_liste;$ref_champ3;Faux;Gras ;14931)

29. Fin de si

30. Fin de si

31. Fin de si

32. Fin de boucle

33. `modifier l'élément pour lui ajouter l'éventuelle nouvelle sous liste

34. CHANGER ELEMENT(nqr_nqr_lh_champ;$ref_element;$text;$ref_element;$sous_liste;Vrai)

35. Sinon

36. `c'est un lien aller

37. `lire la table de départ

38. $table_depart:=_nqr_table_arrivee{-$ref_element}

39. `boucle sur toutes les relations

40. Boucle ($ttt;1;Taille tableau(_nqr_table_depart))

41. Si (_nqr_table_depart{$ttt}=$table_depart)   `c'est la bonne table de départ

42. Si (_nqr_allerauto{$ttt})   `le lien est automatique (le calcul du rapport sera possible)

43. `recherche de la table d'arrivée

44. $trouve_table:=Chercher dans tableau(_nqr_table_id;_nqr_table_arrivee{$ttt})

45. Si ($trouve_table>0)   `si la liste n'existe pas encore, la créer

46. Si ($sous_liste=0)

47. $sous_liste:=Nouvelle liste

48. Fin de si

49. $ref_champ3:=-$ttt

50. AJOUTER A LISTE($sous_liste;"["+_nqr_table{$trouve_table}+"]";$ref_champ3)

51. CHANGER PROPRIETES ELEMENT($sous_liste;$ref_champ3;Faux;Gras ;14930)

52. Fin de si

53. Fin de si

54. Fin de si

55. Fin de boucle

56. ` modifier l'élément pour lui ajouter l'éventuelle nouvelle sous liste

57. CHANGER ELEMENT(nqr_nqr_lh_champ;$ref_element;$text;$ref_element;$sous_liste;Vrai)

58. Fin de si

59. Fin de si

60. 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 :

1. `choix des coordonnées en fonction de la page

3. $continuer:=Vrai

4. Au cas ou

5. : (Page formulaire courante=1)

6. $liste_champ:=nqr_nqr_lh_champ

7. $liste_detail:=nqr_nqr_lh_champ_Detail

8. $h1:=162

9. $h2:=84

10. $h3:=90

11. $suffixe:=""

13. : (Page formulaire courante=3)

14. $liste_champ:=nqr_nqr_lh_champ2

15. $liste_detail:=nqr_nqr_lh_champ_Detail2

16. $h1:=158

17. $h2:=64

18. $h3:=70

19. $suffixe:="2"

21. : (Page formulaire courante=7)

22. $liste_champ:=nqr_nqr_lh_champ3

23. $liste_detail:=nqr_nqr_lh_champ_Detail3

24. $h1:=205

25. $h2:=110

26. $h3:=116

27. $suffixe:="3"

29. Sinon   `code défensif

30. $continuer:=Faux

32. 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é :

1. $item:=Elements selectionnes($liste_champ)

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

5. Au cas cas

6. : ($ref_champ>=0)   `c'est un champ ou un sous-champ

7. `lire la position de la liste principale

8. LIRE RECT OBJET(*;("nqr_lh_champ"+$suffixe);$g;$h;$d;$b)

9. `la liste principale prend l'ensemble de la place

10. DEPLACER OBJET(*;("nqr_lh_champ"+$suffixe);$g;$h;$d;$h+$h1;*)

11. `la liste secondaire est rejetée hors de la zone visible

12. DEPLACER OBJET(*;("nqr_lh_champ_detail"+$suffixe);10000;10000;10000;10000;*)

13. SELECTIONNER ELEMENTS PAR POSITION($liste_champ;$item)

15. : ($ref_champ<0)   `c'est une table

16. `lire la position de la liste principale

17. LIRE RECT OBJET(*;("nqr_lh_champ"+$suffixe);$g;$h;$d;$b)

18. `adapter la position à la moitié de la place

19. DEPLACER OBJET(*;("nqr_lh_champ"+$suffixe);$g;$h;$d;$h+$h2;*)

20. `l'autrre moitié est pour la liste secondaire

21. DEPLACER OBJET(*;("nqr_lh_champ_detail"+$suffixe);$g;$h+$h3;$d;$h+$h1;*)

23. 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 :

1. `mise à jour des informations dans les listes

3. Au cas ou

4. : (Page formulaire courante=1)

5. nqr_lh_champ_Detail:=$liste_detail

6. ALLER A CHAMP(nqr_lh_champ_Detail)

7. REDESSINER LISTE(nqr_lh_champ)

8. Si (Liste existante(nqr_lh_champ_detail))

9. CHANGER PROPRIETES LISTE(nqr_lh_champ_detail;0;0;18;1)

10. REDESSINER LISTE(nqr_lh_champ_detail)

11. Fin de si

13. : (Page formulaire courante=3)

14. nqr_lh_champ_Detail2:=$liste_detail

15. ALLER A CHAMP(nqr_lh_champ_Detail2)

16. REDESSINER LISTE(nqr_lh_champ2)

17. Si (Liste existante(nqr_lh_champ_detail2))

18. CHANGER PROPRIETES LISTE(nqr_lh_champ_detail2;0;0;18;1)

19. REDESSINER LISTE(nqr_lh_champ_detail2)

20. Fin de si

22. : (Page formulaire courante=7)

23. nqr_lh_champ_Detail3:=$liste_detail

24. ALLER A CHAMP(nqr_lh_champ_Detail3)

25. REDESSINER LISTE(nqr_lh_champ3)

26. Si (Liste existante(nqr_lh_champ_detail3))

27. CHANGER PROPRIETES LISTE(nqr_lh_champ_detail3;0;0;18;1)

28. REDESSINER LISTE(nqr_lh_champ_detail3)

29. Fin de si

30. 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é :

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 :

1. $Table:=($ref_champ & 0x00FFFFFF) >> 16   `decodage de la table

2. $champ:=$ref_champ & 0xFFFF   `decodage du champ

3. $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) :

1. 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

2. QR LIRE TRIS(nqr_area;_nqr_col;_nqr_order)

3. $trouve:=Chercher dans tableau(_nqr_col;nqr_current_col)

4. Si ($trouve>0)

5. SUPPRIMER LIGNES(_nqr_col;$trouve)

6. SUPPRIMER LIGNES(_nqr_order;$trouve)

7. QR FIXER TRIS(nqr_area;_nqr_col;_nqr_order)

8. Fin de si

9. 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 :

1. `dans quelle colonne positionner le champ ?

3. Au cas ou

4. : (nqr_tc_colonne="")  `la colonne 1 est libre

5. $colonne:=1

6. : (nqr_tc_ligne="")  `la colonne 2 (les lignes) est libre

7. $colonne:=2

8. : (nqr_tc_cellule="")`la colonne 3 (les cellules) est libre

9. $colonne:=3

10. Sinon

11. $colonne:=3

13. 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 :

1. `la colonne pour le critère de tri existe-t-elle déjà ?

3. $present:=Faux   `initialisation de la réponse

4. $table_name:=Nom de la table($Table)

5. $champ_name:=Nom du champ($Table;$Champ)

6. `boucle sur l'ensemble des colonnes

7. Boucle ($i;1;QR Nombre de colonnes(nqr_area);1)

8. `lecture des infos pour recuperer le contenu de la colonne

9. QR LIRE INFO COLONNE(nqr_area;$i;$title;$field;$hide;$size;$repeat;$format)

10. `comparaison du contenu au champ qui va servir de critère de tri

11. Au cas ou

12. : ($field#("["+$table_name+"]"+$champ_name)

13. `ce n'est pas le bon champ ; ne rien faire

14. Sinon

15. $present:=Vrai   `c'est le bon champ

16. $colonne:=$i   `arrêter la boucle

17. Fin de cas

19. 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 :

1. nqr_wizard:=1   `afficher l'assistant

3. nqr_recherche:=1   `afficher la table et les recherches

4. nqr_hier:=1   `autoriser les liens retours

5. $ref:=Creer fenetre formulaire([Interface];"NQR_Dialog")

6. DIALOGUE([Interface];"NQR_Dialog")

8. 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