1. Introduction▲
Cet outil permet de gérer toutes les LOV d'une application Oracle Forms9i de façon centralisée, sans manipulation des sources, ni compilation ni déploiement.
L'écran de LOV générique permet de restreindre la liste des valeurs à chaque caractère frappé selon la colonne de recherche indiquée par l'utilisateur
Un mode recherche globale permet de rechercher une valeur dans toutes les colonnes de la table liée
Générique parce qu'il s'adapte à toutes les LOV possibles de 2 à 9 colonnes
Dynamique car entièrement paramétré
- Position X et Y de l'écran
- Titre de la LOV
- Auto sélection des valeurs
- Libellé des colonnes
- Position des colonnes
- Largeur des colonnes
Personnalisable par l'utilisateur en lui permettant les actions suivantes
- Permutation des colonnes entre elles
- Redimensionnement des colonnes
- Choix de la colonne de recherche
- Enregistrement des préférences (une même LOV peut donc être représentée différemment selon l'utilisateur)
Le développeur dispose d'un écran Forms pour gérer les LOV de son application, permettant d'ajouter, modifier ou supprimer toute LOV du projet sans aucune intervention dans les modules sources.
Celui-ci affiche les modules, blocs et items de toute l'application.
Sur chaque item il est possible de définir une LOV avec les caractéristiques suivantes :
- Titre
- Clause Select
- Clause From
- Clause Where
- Clause Order by
- Position X, Y
- Auto affichage des lignes ramenées
- Lov pour Validation de l'item
Et pour chaque colonne de la LOV
- Position de la colonne
- Nom de la colonne
- Libellé de la colonne
- Largeur d'affichage de la colonne
- Nom de l'item de retour
- Clause restrictive sur la colonne qui peut contenir une référence à un autre item de la forme
Liste des concepts étudiés
-
Utilisation de Groupe d'enregistrements (Record group) et de LOV native
Ou comment à partir d'un unique groupe d'enregistrements et d'une unique LOV simuler n'importe quelle LOV de 2 à 9 colonnes avec adaptation des titres et largeur des colonnes.
- Création dynamique d'un groupe d'enregistrements
- Alimentation du groupe d'enregistrements
- Comptage des enregistrements
- Assignation du groupe d'enregistrements à une LOV native
- Adaptation des titres de colonne de la LOV
- Adaptation des largeurs de colonne de la LOV
- Affichage de la LOV de contrôle
-
Modification dynamique de l'affichage
- Affichage/masquage des objets
- Positionnement des objets (Items, Canvas, Windows)
- Redimensionnement des objets (Items, Canvas, Windows)
- Utilisation d'un Timer
- Gestion d'un menu POPUP
- Gestion d'un bloc basé sur une clause FROM
- Appel d'écran (CALL_FORM) avec passage de paramètres
- Navigation à l'intérieur des items d'un bloc
- Mécanisme de validation de l'item par LOV
- SQL dynamique natif pour alimenter la liste des valeurs depuis la procédure stockée ainsi que pour la validation des items
Remerciements▲
Chaleureux remerciements à Developpez.com et à l'équipe SGBD.
2. L'écran de LOV générique▲
Le titre de la LOV est affiché dans le libellé de la fenêtre
Le champ de saisie des caractères en haut à gauche a toujours le focus, permettant à l'utilisateur d'ajouter des lettres à chaque frappe au clavier et de réduire la liste
La colonne sur laquelle s'effectue la recherche est matérialisée par un fond gris (Colonne Nom dans l'exemple ci-dessus)
La navigation dans la liste de valeurs se fait soit avec la souris dans une des lignes affichées, soit en activant l'ascenseur vertical, soit avec les touches du clavier (UP, DOWN, SCROLL_UP, SCROLL_DOWN)
La sélection d'une ligne dans la liste se fait soit par un double clic sur la ligne, soit via le bouton OK
Possibilités de personnalisation
- Redimensionnement des colonnes
Pour redimensionner une colonne, il faut d'abord la sélectionner par un clic gauche sur la colonne.
Ensuite, un clic droit fait apparaître un menu popup
Dans ce menu, choisir l'option Dimensions
Sous la colonne sélectionnée apparaissent alors deux boutons permettant de réduire (-) ou d'augmenter (+) la largeur de la colonne
Les tailles minimum et maximum pour une colonne sont respectivement de 20 à 700 points.
Une fois la bonne dimension réglée, un clic sur n'importe quelle région de l'écran fait disparaître les deux boutons
- Changement de la colonne de recherche
Il est possible de sélectionner n'importe quelle colonne de la LOV pour effectuer la recherche.
La colonne de recherche est soulignée par un fond gris.
Pour changer la colonne de recherche, sélectionnez la colonne désirée par un clic gauche puis afficher le menu popup avec un clic droit
Clic gauche sur la colonne Fonction puis clic droit
Clic sur le sous-menu Colonne de recherche
La colonne de recherche est désormais Fonction.
D'ailleurs la frappe du caractère C dans le champ de saisie ramène bien les lignes dont la colonne Fonction commence par C
- Permutation des colonnes
Pour permuter les colonnes entre elles, sélectionner la colonne par un clic gauche puis afficher le menu popup avec un clic droit.
Sélectionner l'option de menu Déplacement, puis la sous-option Déplacer à droite ou Déplacer à gauche.
Dans l'exemple, nous choisissons de déplacer la colonne Fonction vers la gauche
La colonne Fonction a été permutée sur la gauche avec la colonne Nom
Le mode Recherche globale
Si le développeur a pris soin de définir une table principale sur cette LOV, la case à cocher Recherche Multicolonne est activable.
Ce mode permet de rechercher une occurrence dans toutes les colonnes de la table définie par le développeur (à l'exception des colonnes de type LONG, LONG RAW, RAW, BLOB et BFILE)
Pour activer le mode Recherche globale il suffit de cocher la case Recherche Multicolonne, de saisir l'occurrence dans le champ de saisie et de valider par la touche Tab ou Enter
Ce mode est pratique lorsque les colonnes affichées ne permettent pas à l'utilisateur d'identifier la ligne voulue, alors que ce dernier " se souvient " par contre d'une information saisie dans une autre colonne
Dans cet exemple (peu vraisemblable, mais c'est pour l'exemple) l'utilisateur a " oublié " le nom mais se souvient que le salaire est de 1215 euros.
Il clique donc sur la case Recherche Multicolonne, saisi 1215 dans le champ de saisie et presse la touche Tab du clavier.
Enregistrement des réglages
Si l'utilisateur souhaite conserver les réglages qu'il vient d'effectuer, afin de les retrouver automatiquement à la prochaine ouverture de cette LOV, il clique sur le bouton Sauver réglages
Suivant le même principe que la gestion des couleurs, ces nouveaux réglages ne sont valables que pour cet utilisateur. Tout autre utilisateur n'ayant pas modifié ses préférences verra cette LOV affichée avec les réglages par défaut.
Comment ça marche ?
Ouvrons le fichier source de l'écran et analysons le contenu
- Le canvas
Contenu du bloc associé au canvas
9 colonnes sont définies dans le bloc BLOC2 afin de supporter toute LOV possible de 2 à 9 colonnes.
Les items COL1 à COL9 sont de type Elément texte Char d'une longueur maxi de 512 caractères
Au chargement de la LOV seuls les items nécessaires seront affichés et disposés dans la fenêtre
Afin de pouvoir gérer la sélection des données depuis n'importe quelle requête dynamique, le bloc BLOC2 est basé sur une Interrogation de clause FROM
Voyons comment le bloc BLOC2 est configuré pour être alimenté par une Interrogation de clause FROM
La propriété Type de source de données est renseignée à Interrogation de clause FROM
La propriété Nom de source de données est valorisée avec la requête virtuelle : select '1','2','3','4','5','6','7','8' from dual
La propriété Colonnes de source de données permet de définir les items de réception
Le champ de saisie (P1), la boite à cocher (Recherche Multicolonne) ainsi que tous les boutons sont contenus dans le bloc CTRL
Le champ P1 reçoit la frappe de l'utilisateur
Le champ RECHERCHE permet de basculer en mode Recherche globale
Le bouton BT_OK valide la sélection dans la liste, retourne les valeurs et quitte la forme
Le bouton BT_QUITTER quitte la forme sans retour de valeur
Le bouton BT_SAUVE permet de sauvegarder en base les réglages de l'utilisateur
Les boutons PLUS et MOINS permettent le redimensionnement des colonnes
Comment faire défiler avec le clavier la liste des enregistrements ramenés alors que le focus est constamment sur le champ de saisie P1 ?
Regardons le contenu des triggers KEY-DOWN, KEY-UP, KEY-SRCDOWN et KEY-SCRUP définis sur l'item P1 et analysons le premier
-- Trigger KEY-DOWN --
Go_Block( 'BLOC2' ) ; -- déplacement sur le bloc BLOC2
Down ;
Go_Item( 'CTRL.P1' ) ; -- retour dans l'item :CTRL.P1Simple, non ?
Mécanisme général du fonctionnement de l'écran GEN_LOV
La procédure Init_form() est appelée depuis le trigger WHEN-NEW-FORM-INSTANCE dès le chargement de l'écran
Elle commence par masquer les 9 items du bloc BLOC2
-- Masquage des champs --
For I in 1..9 Loop
Set_Item_Property( 'BLOC2.COL' || Ltrim( To_char( I ) ), VISIBLE, PROPERTY_FALSE ) ;
End loop ;- Positionne l'indicateur de modification à zéro
-- Indicateur de modification --
:CTRL.MODIF := 0 ;- Désactive la case à cocher de la Recherche globale si aucune table n'est transmise via le paramètre : PARAMETER.PM$TABLE
-- Table en paramètre pour recherche globale ? --
If :PARAMETER.PM$TABLE Is null Then
Set_Item_Property( 'CTRL.RECHERCHE', ENABLED, PROPERTY_FALSE ) ;
End if ;- Positionne la fenêtre en coordonnées X,Y selon les paramètres fournis
-- Position X,Y --
If Nvl( :PARAMETER.POSX, 0 ) > 0 Then
Set_Window_Property( 'FENETRE1', X_POS, :PARAMETER.POSX ) ;
End if ;
If Nvl( :PARAMETER.POSY, 0 ) > 0 Then
Set_Window_Property( 'FENETRE1', Y_POS, :PARAMETER.POSY ) ;
End if ;- Lit dans la table LOV_ELEMENT_COLONNE les informations nécessaires à la gestion de la LOV
-- Lecture des colonnes de la LOV en table --
init_bloc_bl_col ;Cette procédure permet de retrouver les colonnes, leur libellé, position et largeur
- Dimensionne, ordonne et affiche les colonnes nécessaires
-- Affichage des colonnes --
dessine_colonnes ;PROCEDURE Dessine_colonnes IS
LN$PosX PLS_INTEGER := :CTRL.POS_X ;
LN$Long PLS_INTEGER ;
LC$Item VARCHAR2(30);
LN$TCan PLS_INTEGER := 0;
LN$Max PLS_INTEGER ;
LN$larg PLS_INTEGER ;
LN$Taille PLS_INTEGER ;
BEGIN
----------------------------------
-- Affiche les colonnes de la lov
-- et dimensionne la fenêtre
----------------------------------
LN$TCan := 325 ; -- Taille minimum du canvas
LN$Max := LN$PosX ; -- Largeur maxi du canvas
go_block( 'BL_COL' ) ;
First_record ; -- première colonne
Loop -- pour chaque colonne
LC$ITEM := 'BLOC2.' || :BL_COL.COLONNE ;
LN$Long := :BL_COL.Taille ; -- largeur de la colonne
LN$Max := LN$Max + LN$Long ; -- largeur canvas avec colonne
-- Ajuster la largueur du Canvas ? --
If LN$Max > LN$TCan Then
LN$TCan := LN$Max + 20 ;
Set_Canvas_Property( 'CV_LOV', WIDTH, LN$TCan ) ; -- agrandissement du canvas
End if ;
Set_Item_Property(LC$Item, VISIBLE, PROPERTY_TRUE); -- affichage de la colonne
Set_Item_Property(LC$Item, ENABLED, PROPERTY_TRUE);
Set_Item_Property(LC$Item, NAVIGABLE, PROPERTY_TRUE);
Set_Item_Property(LC$Item, UPDATE_ALLOWED, PROPERTY_TRUE);
Set_Item_Property(LC$Item, WIDTH, :BL_COL.Taille); -- largeur de la colonne
Set_Item_Property(LC$Item, NEXT_NAVIGATION_ITEM, :BL_COL.Suivant); -- item suivant
Set_Item_Property(LC$Item, PREVIOUS_NAVIGATION_ITEM, :BL_COL.Precedent); -- item précédent
-- Coordonnée X de la colonne sur le canvas --
Set_Item_Property(LC$Item, X_POS, LN$PosX);
LN$PosX := LN$PosX + LN$Long ;
If :system.last_record = 'TRUE' Then
exit ;
End if ;
Next_record ;
End loop ;
If LN$Tcan < 325 Then
LN$Larg := 325 ;
Elsif LN$Tcan > 600 Then
LN$Larg := 600 ;
Else
LN$Larg := LN$Tcan ;
End if ;
-- Adaptation de la largeur de la vue --
Set_View_Property( 'CV_LOV', WIDTH, LN$TCan ) ;
Set_View_Property( 'CV_LOV', VIEW_SIZE, LN$TCan, 324 ) ;
-- Adaptation de la largeur de la fenêtre (maxi 600 points) --
If LN$TCan < 600 Then
Set_Window_Property( 'FENETRE1', WIDTH, LN$TCan ) ;
Else
Set_Window_Property( 'FENETRE1', WIDTH, LN$Larg ) ;
End if ;
END;- Met en relief la colonne de recherche
-- Mise en relief de la colonne de recherche --
Colore_colonne ;PROCEDURE COLORE_COLONNE IS
BEGIN
-- Colore la colonne de recherche --
If :GLOBAL.ANCCOLONNE is not null Then
-- dé-grise l'ancienne colonne de recherche --
Set_Item_Property( :GLOBAL.ANCCOLONNE, VISUAL_ATTRIBUTE, 'VA_COL_NOSELECT' );
End if ;
-- grise la nouvelle colonne de recherche --
Set_Item_Property( :CTRL.COL_RECH, VISUAL_ATTRIBUTE, 'VA_COL_SELECT' );
END;La colorisation des items s'effectue via la définition d'un attribut visuel
- Active le temporisateur
-- Init du timer --
Init_timer ;Le temporisateur est utilisé pour lancer l'exécution de la requête (execute_query) à un intervalle de temps exprimée en millisecondes, lue depuis la variable de package (PKG_GESTION_LOV.GN$Duree_Timer) afin de pouvoir être adaptée au plus juste de votre configuration.
Par défaut elle est de 800 millisecondes, ce qui permet à l'utilisateur de saisir plusieurs caractères avant le déclenchement du query
Le timer reste actif tant que le focus se trouve dans le champ de saisie (:CTRL.P1) et désactivé dès que l'on en sort
Lorsque le temporisateur est actif, le trigger de niveau forme WHEN-TIMER-EXPIRED se déclenche et exécute le code suivant :
Begin
If :CTRL.P1 is not null Then
if :CTRL.P1 <> nvl(:CTRL.PSAUV,'_') Then
:CTRL.PSAUV := :CTRL.P1;
Execute_recherche ;
End if ;
End if ;
End;Pour lancer la recherche on vérifie que le champ de saisie n'est pas NULL et que sa valeur a été modifiée depuis le précédent query
Il faut noter que lorsque que l'on enclenche le mode Recherche globale, le temporisateur est désactivé puisque le query est déclenché manuellement par l'utilisateur lors de la frappe de la touche Tab ou Enter
Voici le code du trigger (WHEN-CHECKBOX-CHANGED) attaché à la boite à cocher :
If :CTRL.RECHERCHE = 1 Then
-- recherche multicolonne --
Init_timer( FALSE ) ; -- désactivation du timer --
:CTRL.P1 := '' ; -- on vide le champ de saisie --
:CTRL.ACTION := 'W' ; -- word index --
Else
-- recherche monocolonne --
:CTRL.P1 := :CTRL.PSAUV ; -- on réalimente le champ de saisie avec l'ancienne valeur--
:CTRL.ACTION := 'N' ; -- recherche approchée --
Init_timer( TRUE ) ; -- réactivation du timer --
End if ;
go_item('CTRL.P1');- Prérenseigne le champ de saisie en fonction du paramètre transmis et se positionne
-- Item de sélection des valeurs --
If :PARAMETER.AUTO_SELECT = 'O' Then
:CTRL.P1 := '%' ;
End if ;
Go_item('CTRL.P1');Si le développeur a configuré cette LOV pour afficher la sélection dès l'apparition de l'écran, on prérenseigne le champ de saisie avec le caractère %, sinon l'écran attend que l'utilisateur frappe un caractère pour lancer la recherche
Dimentionnement d'une colonne
Le choix de l'option Dimension du menu popup exécute la fonction Redim_colonne().
Celle-ci positionne (Set_Item_Property( …, X_POS/Y_POS) )et rend visible les boutons + et - en bas de la colonne concernée (Set_Item_Property ( …, VISIBLE, PROPERTY_TRUE ) ;)
Si l'utilisateur décide d'agrandir la colonne, un clic sur le bouton + exécute le code suivant :
Declare
LN$T PLS_INTEGER := Get_Item_Property( :GLOBAL.COLONNE, WIDTH );
LC$Col VARCHAR2(30);
Begin
-- redimensionnement de la colonne --
-- plus 10 pixels --
If LN$T < 300 Then
LN$T := LN$T + 10 ;
Set_Item_Property( :GLOBAL.COLONNE, WIDTH, LN$T ) ;
LC$Col := Substr( :GLOBAL.COLONNE, instr( :GLOBAL.COLONNE, '.' ) + 1, 30 ) ;
Maj_Taille_Colonne( LC$Col, LN$T ) ;
Dessine_colonnes ;
End if ;
End ;La taille de l'item est augmentée de 10 points, la procédure Maj_Taille_Colonnes() est invoquée pour mettre à jour les infos de la colonne sauvegardées dans le bloc caché BL_COL, puis l'écran est redessiné via la procédure Dessine_colonnes()
Lorsque l'utilisateur clique à l'extérieur d'un des deux boutons, ceux-ci sont masqués via le trigger de niveau forme WHEN-MOUSE-CLICK
-- Effacer les boutons de dimensionnement --
If Nvl(:system.mouse_item,'_') not in ('CTRL.MOINS','CTRL.PLUS') Then
Set_Item_Property( 'CTRL.PLUS', VISIBLE, PROPERTY_FALSE ) ;
Set_Item_Property( 'CTRL.MOINS', VISIBLE, PROPERTY_FALSE ) ;
End if ;Sélection d'une valeur et retour à l'écran d'appel
Pour sélectionner une valeur, l'utilisateur clique sur la ligne de la liste puis sur le bouton Ok ou double clique sur la ligne, déclenchant l'appel de la procédure Selection_valeur(), dont voici le code :
PROCEDURE Selection_valeur IS
LR_COL_LOV PKG_GESTION_LOV.TYPE_REC_LOV ;
Begin
-- Ligne selectionnee --
Go_block( 'BLOC2' ) ;
If :system.record_status = 'QUERY' Then -- si la ligne n'est pas vide
-- RAZ des variables de retour --
PKG_GESTION_LOV.RAZ_Valeurs_LOV ;
LR_COL_LOV.COl1 := :BLOC2.COL1 ;
LR_COL_LOV.COl2 := :BLOC2.COL2 ;
LR_COL_LOV.COl3 := :BLOC2.COL3 ;
LR_COL_LOV.COl4 := :BLOC2.COL4 ;
LR_COL_LOV.COl5 := :BLOC2.COL5 ;
LR_COL_LOV.COl6 := :BLOC2.COL6 ;
LR_COL_LOV.COl7 := :BLOC2.COL7 ;
LR_COL_LOV.COl8 := :BLOC2.COL8 ;
LR_COL_LOV.COl9 := :BLOC2.COL9 ;
-- Mise à jour des variables de retour --
PKG_GESTION_LOV.MAJ_Valeurs_LOV( LR_COL_LOV ) ;
Exit_form( NO_VALIDATE ) ;
Else
Go_Item( 'CTRL.P1' ) ;
End if ;
End ;Les variables de retour, stockées dans le package sont d'abord effacées, puis valorisées avec celles de la ligne sélectionnée, permettant à l'écran d'appel de les traiter.
3. L'écran de gestion des LOV (INIT_LOV.FMB)▲
Il présente la liste des modules, blocs et items de votre application
Ces informations sont automatiquement enregistrées en table pour chacun de vos modules grâce à l'appel de procédure Init_referentiel_lov() qui se trouve en commentaire dans chaque trigger WHEN-NEW-FORM-INSTANCE
Pour renseigner l'ensemble de ces informations, il suffit de retirer le commentaire, exécuter le module, quitter et remettre le commentaire
En regard de chaque composant : module, bloc et item, vous pouvez saisir un libellé
Le canvas inférieur contient 3 onglets
Un onglet Items qui affiche la liste des items du bloc et du module sélectionné, un libellé libre ainsi qu'un rappel du format appliqué sur l'item
Un onglet LOV qui permet de définir les propriétés de la LOV attachée à l'item sélectionné
Nom table permet de définir le nom d'une table qui servira à la recherche globale
Validation permet de définir si la LOV doit servir a valider l'item en saisie
Auto select permet de définir si la LOV affichera la liste de sélection dès son affichage
PosX et PosY, facultatifs, permettent de positionner la LOV
Titre de la LOV permet d'indiquer le titre
Select reçoit la partie Select de la requête (sans le mot clé Select)
From reçoit la partie From de la requête (sans le mot clé From)
Where (facultatif) reçoit la partie Where de la requête (sans le mot clé Where)
Order by (facultatif) reçoit la partie Order by de la requête (sans le mot clé Order by)
La case à cocher Ok non modifiable indique si la LOV est valide et donc accessible à l'utilisateur
Tant que cette LOV n'est pas valide, elle ne sera ni visible ni utilisable pour l'utilisateur
Le bouton Afficher l'ordre select permet de visualiser la requête complète

Le bouton Contrôle de la syntaxe permet de vérifier la cohérence de la requête, d'alimenter les colonnes de l'onglet Colonnes LOV

Tant que vous n'avez pas la certitude que votre LOV s'exécutera correctement, il est inutile de répondre Oui pour la reconstruction des correspondances de colonne
(Cette opération remplace les définitions de colonnes de cette LOV dans la table LOV_ELEMENT_COLONNE et par conséquent les réglages utilisateurs éventuellement sauvegardés)
Et d'afficher dans une LOV native le résultat de la requête
Nous n'avons utilisé pour cela qu'un unique groupe d'enregistrement et une unique LOV native !
Pourtant nous pouvons représenter n'importe quelle LOV de 2 à 9 colonnes…
Dans cet exemple, nos 3 colonnes sont correctement affichées, dimensionnées et titrées
L'onglet Colonnes LOV permet de configurer chaque colonne de la LOV
Ordre permet d'ordonner l'affichage par défaut des colonnes
Colonne contient le nom de la colonne
Item de réception reçoit l'item qui devra recevoir la valeur de cette colonne
Rech permet de définir la colonne de recherche par défaut
Val permet de définir la colonne qui servira à valider l'item
(Les LOV natives de Forms ne permettent d'utiliser que la première colonne pour la validation des items. Ici, vous pouvez sélectionner n'importe laquelle)
Taille permet de définir la largeur d'affichage en caractères
Libellé colonne LOV permet de définir le libellé tel qu'il apparaîtra dans la LOV
Clause permet de définir une clause restrictive sur la colonne. Cette clause peut contenir une référence à un autre item de la forme (Placeholder). Dans ce cas, il doit se conformer à la syntaxe Forms :bloc.item (par exemple >2 ou bien = :bloc.item, ou n'importe quel ordre sql pouvant apparaître dans la clause Where)
Valeur de test permet de saisir la valeur de remplacement du placeholder (uniquement pour la validation de la LOV)
Rentrons dans le détail
La procédure Init_referentiel_lov(),présente dans le trigger WHEN-NEW-FORM-INSTANCE permet d'initialiser pour chaque forme les items qui la compose (cette procédure se trouve dans la librairie Forms LOV.PLL)
Le nom du module (Name_in( 'system.current_form' )) est enregistré dans la table LOV_MODULE
Begin
Insert into LOV_MODULE
(
NOM_MODULE
,LIBELLE
)
Values
(
LC$Module
,Null
);
Exception
When DUP_VAL_ON_INDEX then
Null ;
End;Le traitement de l'exception DUP_VAL_ON_INDEX permet de faire l'économie d'un Select pour vérifier si l'item existe déjà.
On sélectionne le premier bloc de la forme avec l'instruction :
LC$Block := Get_Form_Property( LC$Module, FIRST_BLOCK ) ;Et l'on boucle sur tous les blocs
While LC$Block is not null Loop -- pour chaque block --Le nom du bloc est enregistré dans la table LOV_BLOCK
Begin
Insert into LOV_BLOCK
(
MOD_NOM_MODULE
,NOM_BLOCK
)
Values
(
LC$Module
,LC$Block
);
Exception
When DUP_VAL_ON_INDEX then
Null ;
End;On se positionne sur le premier item du bloc avec l'instruction suivante :
LC$Item := Get_Block_Property( LC$BLOCK, FIRST_ITEM ) ;Puis l'on boucle sur tous les items de ce bloc
While LC$Item is not null Loop -- Pour chaque itemOn rejette les items de type BUTTON ou de type LONG
If Get_Item_Property( LC$Bitem, ITEM_TYPE ) <> 'BUTTON' Then
LC$Type := Get_Item_Property( LC$Bitem, DATATYPE ) ;
If LC$Type <> 'LONG' ThenLe libellé de l'item est issu de son prompt
LC$Prompt := Substr( Get_Item_Property( LC$Bitem, PROMPT_TEXT ), 1, 80 ) ;Le format est également sauvegardé
LC$Format := Get_Item_Property( LC$Bitem, FORMAT_MASK ) ;Il servira à mettre en conformité de format la valeur de retour de la LOV
Et l'on enregistre chaque item dans la table LOV_ITEM
Begin
Insert into LOV_ITEM
(
MODULE
,BLOC
,ITEM
,LIBELLE
,ID
,ITEM_TYPE
,FORMAT
)
Values
(
LC$Module
,LC$Block
,LC$Item
,LC$Prompt
,SEQ_ITEM.NEXTVAL
,Decode( LC$Type, 'CHAR', 'C', 'DATE', 'D', 'N' )
,LC$Format
);
Exception
When DUP_VAL_ON_INDEX then
Null ;
End;
Un seul groupe d'enregistrement et une seule LOV native pour simuler n'importe quelle LOV de 2 à 9 colonnes ?
Comment cela fonctionne-t-il ?
Pour cela nous allons utiliser un groupe d'enregistrement construit dynamiquement
Si l'on peut construire dynamiquement un groupe d'enregistrement, il n'en va pas de même pour une LOV native
Celle-ci doit exister physiquement dans la forme
Puisque nous nous autorisons à gérer des LOV de 2 à 9 colonnes, il faut donc disposer d'une LOV native composée de 9 colonnes à laquelle nous associerons ensuite le groupe d'enregistrements, qui sera lui-même composé de 9 colonnes
Une LOV native LV_SYNTAXE est donc créée dans la forme composée de 9 colonnes COL1, COL2,...,COL9
Si la requête fait référence à moins de 9 colonnes, des colonnes NULL seront ajoutées au groupe d'enregistrement pour coïncider avec la LOV native de contrôle d'une part, et la procédure stockée qui renseigne également 9 colonnes d'autre part
Ouvrons la procédure Check_sql_order() du module INIT_LOV.FMB
Définissons une variable de type record_group
rg_name VARCHAR2(40) := 'RG_SYNTAXE2';
rg_id RecordGroup;Reconstituons l'ordre SQL complet et jetons-le en pâture au paquetage Oracle DBMS_SQL
------------------------------------------
-- Verifie que l'ordre SQL est conforme --
------------------------------------------
--
LC$Req := 'Select ' || CHR(10) || :BL_LOV.ORDRE || CHR(10)
|| ' From ' || CHR(10) || :BL_LOV.FROM ;
If :BL_LOV.FILTRE is not null Then
LC$Req := LC$Req || CHR(10) || ' Where ' || CHR(10) || :BL_LOV.FILTRE ;
End if ;
If :BL_LOV.T_ORDERBY is not null Then
LC$Req := LC$Req || CHR(10) || ' Order BY ' || CHR(10) || :BL_LOV.T_ORDERBY ;
End if ;
LC$Req := Rtrim( LC$Req, ';' ) ;
Begin
c := dbms_sql.open_cursor;
dbms_sql.parse(c,LC$Req, 1);
d := dbms_sql.execute(c);
Exception
When others Then
dbms_sql.close_cursor(c);
:BL_LOV.OK := 'N' ;
Set_Alert_Property( 'AL_ERREUR', TITLE, 'Contrôle de l''ordre SQL' ) ;
Set_Alert_Property( 'AL_ERREUR', ALERT_MESSAGE_TEXT, 'Ordre SQL incorrect' ) ;
LN$Cpt := Show_Alert( 'AL_ERREUR' ) ;
Raise form_trigger_failure ;
End ;
dbms_sql.describe_columns2(c, col_cnt, rec_tab);
dbms_sql.close_cursor(c);Si la requête ne franchit pas cette étape, c'est qu'elle est incorrecte
Nous rejetons également les colonnes de type LONG, LONGRAW, RAW et les LOBs
LB$Ret := TRUE ;
For i IN rec_tab.first .. rec_tab.last Loop
If rec_tab(i).col_type IN (8,23,24,113,114) Then
Set_Alert_Property( 'AL_ERREUR', TITLE, 'Contrôle de l''ordre SQL' ) ;
Set_Alert_Property( 'AL_ERREUR', ALERT_MESSAGE_TEXT, rec_tab(i).col_name
|| ' Colonnes LONG, LONGRAW, RAW, BLOB, BFILE non autorisées' ) ;
LN$Cpt := Show_Alert( 'AL_ERREUR' ) ;
LB$Ret := FALSE ;
End if ;
End loop ;
If not LB$Ret Then
Raise form_trigger_failure ;
End if ;Nous n'acceptons pas non plus les LOV de moins de 2 colonnes ou de plus de 9 colonnes
If col_cnt < 2 or col_cnt > 9 Then
Set_Alert_Property( 'AL_ERREUR', TITLE, 'Contrôle de l''ordre SQL' ) ;
Set_Alert_Property( 'AL_ERREUR',
ALERT_MESSAGE_TEXT, 'l''ordre SQL doit contenir entre 2 et 9 colonnes' ) ;
LN$Cpt := Show_Alert( 'AL_ERREUR' ) ;
:BL_LOV.OK := 'N' ;
Raise form_trigger_failure ;
End if ;Nous ajoutons ensuite à la clause where les clauses éventuellement définies au niveau de chaque colonne de l'onglet Colonnes LOV
L'extraction des colonnes de la partie Select de l'ordre, ainsi que l'ajout éventuel des colonnes NULL manquantes sont réalisés par la procédure Build_Select_line()
Puis nous construisons notre groupe d'enregistrements dynamique à l'aide de la requête stockée dans la variable LC$Req
rg_id := Find_Group( rg_name );
IF Not Id_Null(rg_id) THEN
Delete_group( rg_id ) ;
End if;
rg_id := Create_Group_From_Query( rg_name, LC$Req ) ;
errcode := Populate_Group( rg_id );Si cette étape est encore franchie et que nous avons répondu Oui à la demande de reconstruction des colonnes de la LOV, l'onglet Colonnes LOV est pré alimenté
Enfin nous associons à notre LOV native le groupe d'enregistrement fraîchement créé
Set_Lov_Property( 'LV_SYNTAXE', GROUP_NAME, rg_name ) ;Nous réglons les titre et largeur par défaut des colonnes
-- Mise a jour titre et largeur des colonnes --
For i IN rec_tab.first..rec_tab.last Loop
-- Titre --
Set_Lov_Column_Property( 'LV_SYNTAXE', i, TITLE, rec_tab(i).col_name ) ;
-- Largeur --
LN$Pos := 30 ;
If rec_tab(i).col_type IN (1,96) Then
If rec_tab(i).col_max_len > 30 Then
LN$Pos := 30 ;
Else
LN$Pos := rec_tab(i).col_max_len ;
End if ;
Elsif rec_tab(i).col_type = 2 Then
If rec_tab(i).col_precision = 0 Then
LN$Pos := 10 ;
Else
LN$Pos := rec_tab(i).col_precision ;
End if ;
Elsif rec_tab(i).col_type = 12 Then
LN$Pos := 12 ;
Elsif rec_tab(i).col_type IN (112,113) Then
LN$Pos := 30 ;
End if ;
LN$Pos := LN$Pos * 6 ;
Set_Lov_Column_Property( 'LV_SYNTAXE', i, WIDTH, LN$Pos ) ;
End loop ;Masquons les colonnes inutiles (NULL) en positionnant leur largeur à 0
-- Masquage des colonnes non renseignées --
If rec_tab.last < 9 Then
For i IN rec_tab.last + 1..9 Loop
Set_Lov_Column_Property( 'LV_SYNTAXE', i, WIDTH, 0 ) ;
End loop ;
End if ;Et affichons la LOV native
LB$Ret := Show_Lov('LV_SYNTAXE') ;Gestion des LOV
Nous avons défini une LOV sur notre item et vérifié que sa syntaxe est correcte
Que se passe-t-il lorsque l'utilisateur utilise son application ?
Lorsqu'une LOV native est présente sur un item de saisie, la barre d'état affiche l'indication "Liste de valeur…"
Lorsque cette LOV native est surchargée par une LOV générique, elle est automatiquement désactivée car devenue inutile.
Dans ce cas la barre d'état n'indique plus à l'utilisateur la présence d'une LOV disponible !
C'est pourquoi, au lancement de chaque forme, la procédure Init_item_lov() stockée dans la librairie LOV.PLL, est appelée depuis le trigger WHEN-NEW-FORM-INSTANCE
Cette procédure recherche pour l'écran en cours la liste des LOV génériques que vous avez définis, inhibe les éventuelles LOV natives concurrentes en fixant la propriété LOV_NAME à NULL
-- Désactivation de l'éventuelle LOV native --
Set_Item_Property( LC$Item, LOV_NAME, '' ) ;
Set_Item_Property( LC$Item, VALIDATE_FROM_LIST, PROPERTY_FALSE ) ;Et indique à l'utilisateur la présence de la LOV en ajoutant (LOV) à la fin du Hint…
C'est la raison pour laquelle il est impératif que la propriété Aide automatique de vos items soit positionnée à OUI !
Lorsqu'il s'agit d'une LOV de validation, l'indicateur (LOVV) est ajouté
L'utilisateur entre dans le champ de saisie et demande l'affichage de la LOV avec les touches Ctrl+L
Le trigger de niveau forme KEY-LISTVAL se déclenche et appelle la procédure Display_lov() stockée dans la librairie LOV.PLL
Celle-ci commence par vérifier qu'une LOV générique a été définie pour cet item
Si elle n'en trouve pas, elle vérifie qu'une LOV native existe et l'exécute. (ce système permet de conserver le fonctionnement des LOV natives que vous n'aurez pas jugé bon de surcharger)
Si elle trouve une LOV générique, elle récupère dans les tables les informations nécessaires, reconstruit la clause where à partir des clauses de colonne éventuellement définies dans l'onglet Colonnes LOV et appelle l'écran de LOV générique GEN_LOV via la procédure Call_gen_lov()
------------------------------
-- Appel de l'écran GEN_LOV --
------------------------------
PROCEDURE CALL_GEN_LOV
(
PN$Item IN NUMBER,
PC$Table IN VARCHAR2,
PC$Select IN VARCHAR2,
PC$From IN VARCHAR2,
PC$Where IN VARCHAR2,
PC$Orderby IN VARCHAR2,
PC$Affiche IN VARCHAR2,
PN$PosX IN NUMBER,
PN$PosY IN NUMBER,
PC$Titre IN VARCHAR2
)
IS
pl_id ParamList;
pl_name VARCHAR2(10) := 'gen_lov';
BEGIN
-- Création de la liste de paramètres --
pl_id := Get_Parameter_List(pl_name);
IF NOT Id_Null(pl_id) THEN
Destroy_Parameter_List(pl_id);
End if ;
pl_id := Create_Parameter_List(pl_name);
IF Id_Null(pl_id) THEN Message('Erreur de création de liste de paramètres '||pl_name);
RAISE Form_Trigger_Failure;
END IF;
-- Ajout des paramètres à la liste --
Add_Parameter(pl_id,'UTI_ID' ,TEXT_PARAMETER, Name_in('PARAMETER.UTI_ID'));
Add_Parameter(pl_id,'ITEM_ID' ,TEXT_PARAMETER, PN$Item);
Add_Parameter(pl_id,'PM$TABLE' ,TEXT_PARAMETER, PC$Table);
Add_Parameter(pl_id,'P_SELECT' ,TEXT_PARAMETER, PC$Select);
Add_Parameter(pl_id,'P_FROM' ,TEXT_PARAMETER, PC$From);
Add_Parameter(pl_id,'P_WHERE' ,TEXT_PARAMETER, PC$Where);
Add_Parameter(pl_id,'P_ORDERBY' ,TEXT_PARAMETER, PC$Orderby);
Add_Parameter(pl_id,'TITRE' ,TEXT_PARAMETER, PC$Titre);
Add_Parameter(pl_id,'AUTO_SELECT',TEXT_PARAMETER, PC$Affiche);
Add_Parameter(pl_id,'POSX' ,TEXT_PARAMETER, PN$PosX);
Add_Parameter(pl_id,'POSY' ,TEXT_PARAMETER, PN$PosY);
-- Appel de l'écran LOV générique --
CALL_FORM('GEN_LOV', NO_HIDE, DO_REPLACE, QUERY_ONLY, pl_id);
END;GEN_LOV est le nom de la forme appelée
NO_HIDE indique de ne pas masquer la forme appelante
DO_REPLACE remplace le menu de la forme appelante
QUERY_ONLY place l'écran appelé en mode non modifiable
pl_id est le nom de la liste des paramètres
Au retour, elle place les valeurs sélectionnées dans les différents items définis également dans l'onglet Colonne LOV
------------------------------
-- Récupération des valeurs --
------------------------------
T_VALEURS := PKG_GESTION_LOV.GET_Valeurs_LOV ;
-- Valorisation des items de retour --
If Tab_col.first is not null Then
For i IN Tab_col.first..Tab_col.last Loop
If Tab_col(i).bloc_item_retour is not null Then
Case i
When 1 Then LC$Valeur := T_VALEURS.col1 ;
When 2 Then LC$Valeur := T_VALEURS.col2 ;
When 3 Then LC$Valeur := T_VALEURS.col3 ;
When 4 Then LC$Valeur := T_VALEURS.col4 ;
When 5 Then LC$Valeur := T_VALEURS.col5 ;
When 6 Then LC$Valeur := T_VALEURS.col6 ;
When 7 Then LC$Valeur := T_VALEURS.col7 ;
When 8 Then LC$Valeur := T_VALEURS.col8 ;
When 9 Then LC$Valeur := T_VALEURS.col9 ;
End case ;
If LC$Valeur is not Null Then
-- Copie de la valeur dans l'item --
Copy( LC$Valeur, Tab_col(i).bloc_item_retour ) ;
End if ;
End if ;
End loop ;
End if ;4. L'écran de test TEST_LOV.FMB▲
Une LOV générique a été définie sur l'item Date entrée
Pour tester la LOV et le retour des valeurs nous allons appeler la LOV depuis le champ Date entrée du premier enregistrement (CLARK)
Et sélectionner la ligne correspondant à Duboudin dont nous devrions récupérer la date d'entrée
Ce qui est bien le cas.
LOV de validation
Pour le cas particulier de l'utilisation des LOV génériques pour la validation des items, c'est cette fois le trigger de niveau forme WHEN-VALIDATE-ITEM qui entre en jeu
Ce trigger vérifie qu'une LOV générique est définie puis exécute la procédure Validate_Lov()
If Instr( Get_Item_Property( :system.trigger_item, HINT_TEXT ), '(LOVV)' ) > 0 Then
Validate_Lov ;
End if ;La requête est constituée de la manière suivante :
-- Constitution du Select --
LC$Ordre := 'Select 1 From DUAL Where exists(Select ' ;
If Tab_col.first is not null Then
For i IN Tab_col.first..Tab_col.last Loop
If Tab_col(i).fl_val = 'O' Then -- Colonne de validation --
LC$Colonne := Tab_col(i).colonne ;
LN$ColType := Tab_col(i).col_type ;
LC$Format := Trim(Tab_col(i).format) ;
LC$Ordre := LC$Ordre || Tab_col(i).colonne ;
exit ;
End if ;
End loop ;
End if ;Mise en conformité de format de la valeur à contrôler
-- Valeur du champ a controler --
LC$Champ := Name_In( Name_In( 'system.cursor_item' ) ) ;
If LN$Coltype = 12 Then -- Type DATE --
If LC$Format is null Then
LC$Champ := '''' || LC$Champ || '''' ;
Else -- Format défini au niveau de l'item --
forms_ddl('ALTER SESSION SET NLS_DATE_FORMAT = ''' || LC$Format || '''');
LC$Champ := '''' || To_char( To_date( LC$Champ, 'DD/MM/YYYY HH24:MI:SS') ,LC$Format ) || '''' ;
End if ;
Elsif LN$Coltype In (1,96) Then -- Type CHAR --
LC$Champ := '''' || LC$Champ || '''' ;
End if ;Ajout des clauses finales
-- Constitution du filtre --
If LC$Where is not null Then
LC$Filtre := ' Where ' || LC$Where ;
End if ;
IF LC$Clause is not null Then
LC$Filtre := LC$Filtre || ' ' || LC$Clause ;
End if ;
If Instr( Upper(LC$Filtre), 'WHERE' ) > 0 Then
LC$Filtre := LC$Filtre || ' And ' || LC$Colonne || ' = ' || LC$Champ ;
Else
LC$Filtre := LC$Filtre || ' Where ' || LC$Colonne || ' = ' || LC$Champ ;
End if ;
LC$Ordre := LC$Ordre || ' FROM ' || LC$From || LC$Filtre || ')';Et enfin soumission à la procédure stockée PKG_GESTION_LOV.Controle_Valeur
-- Test de la valeur --
If PKG_GESTION_LOV.Controle_Valeur( LC$Ordre ) = 0 Then
-- Valeur non trouvée appel de la LOV--
Affiche_lov ;
End if ;Un retour à zéro indique qu'aucune correspondance n'est trouvée et la LOV générique est affichée
Attention. Le trigger de niveau forme WHEN-VALIDATE-ITEM ne se déclenchera qu'après un trigger identique de niveau plus bas (bloc ou item).Si vous avez défini ce type de trigger sur l'item, il sera peut-être nécessaire de définir sa propriété Ordre d'exécution à la valeur Après (After)
5. Le paquetage PKG_GESTION_LOV▲
Il est constitué des variables globales et fonctions publiques suivantes
CREATE OR REPLACE PACKAGE Pkg_Gestion_Lov
AUTHID CURRENT_USER IS
/*----------------------------------------*/
/* TYPES */
/*----------------------------------------*/
-- based-block record type
TYPE TYPE_REC_LOV IS RECORD (
col1 VARCHAR2(512),
col2 VARCHAR2(512),
col3 VARCHAR2(512),
col4 VARCHAR2(512),
col5 VARCHAR2(512),
col6 VARCHAR2(512),
col7 VARCHAR2(512),
col8 VARCHAR2(512),
col9 VARCHAR2(512)
);
-- records collection
TYPE TYPE_TAB_LOV IS TABLE OF TYPE_REC_LOV INDEX BY BINARY_INTEGER;
-- return values record --
GR_COL_LOV TYPE_REC_LOV ;
-- Record type LOV --
TYPE TYPE_COL_LOV IS RECORD (
LOV_ID LOV_ELEMENT_COLONNE.ID%TYPE,
LOV_ITEM LOV_ELEMENT_COLONNE.NOM_ITEM%TYPE,
LOV_COD_UTIL LOV_ELEMENT_COLONNE.UTL_ID%TYPE,
LOV_COL_TAB LOV_ELEMENT_COLONNE.NOM_COLONNE%TYPE,
LOV_POS LOV_ELEMENT_COLONNE.ORDRE%TYPE,
LOV_POS_INIT LOV_ELEMENT_COLONNE.ORDRE_INIT%TYPE,
LOV_PRECEDENT LOV_ELEMENT_COLONNE.NOM_ITEM_P%TYPE,
LOV_SUIVANT LOV_ELEMENT_COLONNE.NOM_ITEM_S%TYPE,
LOV_RECH LOV_ELEMENT_COLONNE.FL_RECH%TYPE,
LOV_TAILLE LOV_ELEMENT_COLONNE.TAILLE%TYPE
);
-- table de record type LOV --
TYPE TYPE_TAB_COL_LOV IS TABLE OF TYPE_COL_LOV INDEX BY BINARY_INTEGER;
-- Durée d'attente du timer en millisecondes --
GN$Duree_timer NUMBER(5) := 800 ;
-- Help screens path --
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --
-- Replace with your own machine/port/virtual directory --
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --
GC$Help_Path VARCHAR2(256) := 'http://machine:port/forms90/html' ;
-- procedure d enregistrement des colonnes LOV --
PROCEDURE P_Sauv_Col_LOV( PT_COL_LOV IN TYPE_TAB_COL_LOV ) ;
-- Procedure de RAZ des valeurs de reception LOV --
PROCEDURE RAZ_Valeurs_LOV ;
-- Procedure de MAJ des valeurs de reception LOV --
PROCEDURE MAJ_Valeurs_LOV( PR_COL_LOV IN TYPE_REC_LOV ) ;
-- Fonction de recuperation des valeurs de reception LOV --
FUNCTION GET_Valeurs_LOV RETURN TYPE_REC_LOV ;
-- Test de l'existence d'une valeur pour la validation par LOV --
FUNCTION Controle_valeur ( PC$Select IN VARCHAR2 ) RETURN PLS_INTEGER ;
FUNCTION Get_Duree_Timer RETURN NUMBER ;
FUNCTION Get_Help_Path RETURN VARCHAR2 ;
END;Le type TYPE_TAB_LOV définit une table d'enregistrements composés de 9 champs de type Varchar2(512) qui alimentera l'écran GEN_LOV
La procédure P_Sauv_col_Lov permet d'enregistrer les préférences utilisateur
La procédure RAZ_Valeurs_Lov efface le tableau des valeurs de retour
La procédure MAJ_Valeurs_LOV renseigne le tableau des valeurs de retour
La procédure GET_Valeurs_LOV lit le tableau des valeurs de retour
La fonction Controle_Valeur permet de valider un item en vérifiant que la valeur de celui-ci appartient bien à la LOV
La fonction Get_Duree_Timer permet à l'écran GEN_LOV de récupérer le temps d'attente en millisecondes pour le timer.( Vous pouvez donc régler ce délai en fonction de votre environnement dans les spécifications du package sans intervenir dans l'écran GEN_LOV)
La fonction Get_Help_Path permet à l'écran GEN_LOV de récupérer le chemin d'accès aux fichiers d'aide en ligne
6. Liste et mise en place du matériel fourni▲
Copier et décompresser le fichierforms_lov.zip (790 Ko) dans un répertoire de votre station de travail
!!! Lisez le fichier install.htm !!!

























