Licence Pro RTICPMO Responsable des Technologies d’Information et de la Communication zn Petite et Moyennes Organisations Bases de données ORACLE L’environnement d’Oracle Développement d’applications clientes sous Developer 2000 / Forms Builder Pierre-Jean Charrel et Jean-Marc Thévenin Licence Pro RTICPMO, Université Toulouse 1, P. A. France, 31042 Toulouse Cedex, Secrétariat : tél 05 61 63 35 11, email [email protected] Sommaire PARTIE I : L’environnement d’Oracle .................................................................................... 4 TP 1Connexion des Clients au Serveur Oracle, Pratique de SQL avec l'éditeur SQLWorksheet ............................................................................................................................................ 4 1. Objectifs ..........................................................................................................................................4 2. Environnement ...............................................................................................................................4 2.1. Les outils Oracle ..................................................................................................................................... 4 2.2. La base de données support des TP ........................................................................................................ 5 3. Actions ............................................................................................................................................6 3.1. A propos de l'outil SQL-Net8 Easy Config ............................................................................................. 6 3.2. Lancer l'outil Administration Toolbar ..................................................................................................... 7 3.3. Lancer l'outil SQL Worksheet ................................................................................................................. 7 3.4. Lancer l'outil Admin ODBC ................................................................................................................... 8 3.5. Lancer Microsoft ACCESS ..................................................................................................................... 8 3.6. Traiter les Requêtes SQL suivantes ........................................................................................................ 9 TP 2 Traduction de règles de gestion à l'aide de déclencheurs Bases de Données .............. 10 1. Objectifs ........................................................................................................................................10 2. Environnement .............................................................................................................................10 3. Actions ..........................................................................................................................................10 PARTIE II : Développement d’applications clientes avec Developper 2000 ........................ 14 TP 3 Le concept de formulaire dans Developer 2000 ............................................................ 14 1. Rappel : Les trois principaux objets d’un module ...................................................................14 Bloc (Block)................................................................................................................................................. 14 Elément (Item) ............................................................................................................................................. 15 Canevas (Canvas) ......................................................................................................................................... 15 2. Création d'un formulaire basique ..............................................................................................16 3. Création d’un formulaire comportant un sous formulaire .....................................................16 4. Exercice .........................................................................................................................................18 TP 4 Listes de valeurs (LOV) et déclencheurs d'applications. Création de boutons et de déclencheurs associés à des LOV écrits en PL/SQL ........................................................................ 19 1 Rappel : la gestion des clés étrangères ........................................................................................19 2. création d’un formulaire exploitant une LOV ..........................................................................19 2.1. Exercice : expérimentation des LOV .................................................................................................... 19 2.2. Concepts autour des LOV ..................................................................................................................... 20 3. Améliorer l'utilisation d'une LOV .............................................................................................22 4. Déclencheurs d’application (triggers) ........................................................................................22 4.1. Exemple : contrôler l’affichage d’une LOV dans un formulaire .......................................................... 23 4.2. Déclencheurs d’application et déclencheurs base de données ............................................................... 23 5. Gestion automatique de l’affichage d’une LOV .......................................................................24 Solution 1 : création d’un déclencheur WHEN-MOUSE-DOUBLECLICK pour l’item NJ ....................... 24 Solution 2 : création d’un déclencheur PRE-TEXT-ITEM pour l’élément NJ ............................................ 24 Solution 3 : création d’un bouton avec un déclencheur WHEN-BUTTON-PRESSED ............................... 25 6. Gestion de LOV dynamiques ......................................................................................................25 P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 2 TP 5 Synchronisations entre les Blocs de données (FORMS) et la Base de Données ......... 27 1. Notion de Bloc basé / non basé ...................................................................................................27 1.1. Remplissage du tableau d’un Bloc avec des données stockées dans la Base de données ...................... 27 1.2. Actualisation de la Base de données via le tableau d’un Bloc .............................................................. 27 2. Concevoir l’interface d’un Bloc basé .........................................................................................28 2.1. Utilisation d'alertes................................................................................................................................ 28 2.2. Contrôle du remplissage d’un Bloc ....................................................................................................... 29 2.3. Restriction dynamique du remplissage d’un Bloc ................................................................................. 30 2.4. Afficher des données calculées dans un Elément non basé ................................................................... 30 3. Corrélation entre les Blocs, l’actualisation de la Base de Données et les Déclencheurs d’application ...............................................................................................................................................31 3.1 Création d’un bloc non basé comportant les boutons Valider et Annuler .............................................. 31 3.2. Gestion d’une contrainte d’intégrité au niveau de l’application ............................................................ 32 3.3. Création d’une contrainte sur un élément comparant les valeurs avant maj et après maj ...................... 33 3.4. Question de cours .................................................................................................................................. 34 TP 6 Gestion spécifique des blocs maître/détail lorsqu'il n'y a pas de jointure directe entre les blocs. Gestion du remplissage des blocs détails à l'aide de déclencheurs écrits en PL/SQL .... 35 1 Position du problème ....................................................................................................................35 2. Utiliser la fonction Execut_Query dans un déclencheur déclencheur ON-POPULATEDETAILS ....................................................................................................................................................36 3. Création d’un bloc non basé affichant des données .................................................................37 4. Exercice : Ajout d'éléments calculés à ce formulaire ...............................................................39 TP 7 Compléments .................................................................................................................. 40 1. Les variables globales ..................................................................................................................40 2. Les variables système ..................................................................................................................40 3. Gestion de l’affichage de messages d’erreur .............................................................................41 4. Utilisation de séquences et des sous-programmes PL/SQL .....................................................42 4.1. Objectif des séquences : éviter les problèmes de saisie sur les clés. ..................................................... 42 4.2. Application : génération automatique du n° d'équipe ............................................................................ 42 5. A propos des déclencheurs Pre-Query et Post-Query ..............................................................43 Rappel .......................................................................................................................................................... 44 P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 3 PARTIE I : L’environnement d’Oracle TP 1 Connexion des Clients au Serveur Oracle, Pratique de SQL avec l'éditeur SQL-Worksheet 1. Objectifs - Etablir une connexion entre une machine de la salle de TP et le serveur Oracle (outil SQLNet Easy Config) - Pratique de SQL sur une petite BD - Utilisation de l'éditeur SQL Worksheet d’Oracle 2. Environnement 2.1. Les outils Oracle Tous les outils Oracle sont logés dans le répertoire Programme \ Bases de données \ Oracle. Les produits suivants sont installés pour l’administration d’une instance Oracle,: Administration Toolbar bandeau d'icônes d'outils Oracle Backup Manager outil de sauvegarde / restauration d'une base de données Oracle (Non étudié) Storage Manager définit les paramètres de stockage de la base de données : espaces logique, fichiers de données… Schema Manager équivalent graphique de la partie LDD de SQL qui gère les tables, vues, synonymes SQL Worksheet éditeur SQL plein écran Data Manager permet de transférer des données vers et depuis des bases de données Oracle (Export/Import). Nécessite l'activation de l'outil Enterprise Manager Instance Manager permet de démarrer ou d'arrêter une base de données, de visualiser et de modifier ses paramètres d'initialisation, de créer des configurations de démarrage personnalisées, de gérer les transactions en suspens et les sessions utilisateur Security Manager gestion des utilisateurs (noms, mots de passe, droits…) de toutes les bases de données Oracle SQL Plus éditeur "ligne" de SQL et de son extension SQL + (requêtes SQL P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 4 paramètrées, éditions de tableau plus soignée…) SQL-Net8 Easy Config gestion des connexions entre une machine cliente et les bases de données Oracle 8 installées sur les serveurs accessibles depuis cette machine Admin ODBC gestion d'une liaison entre un logiciel client (ex. ACCESS) et un serveur Oracle qui respecte le protocole ODBC (Open Data Base Connectivity) De plus Programme \ Bases de données \ Oracle donne accès à : - Documentation en ligne : ce répertoire contient les entrées, au format HTML ou autres, vers la documentation en ligne des produits Oracle. - Forms & Reports : regroupe plusieurs outils de développement d'applications, dont Forms Builder. Les écrans de connexion à un outil Oracle ont toujours la forme suivante : Cet écran permet de ce connecter au serveur Oracle. Il permet à l’utilisateur de s’identifier (gestion des droits d’accès). De plus, il permet d’indiquer l’instance (base de données) à laquelle l’utilisateur souhaite se connecter. Cela ce fait au travers d’un service : c'est un alias facile à retenir par l’utilisateur, qui regroupe à la fois un nom de protocole réseau, l’adresse d’un serveur et le nom d’une instance sur ce serveur. Attention, suivant le client (SQL+, SQL Worksheet,…) les libellés des trois zones peuvent changer. Ainsi l’intitulé “Service” peut s’appeler “Chaine de connection”; dans tous les cas, la valeur attendue est le nom d’un service. 2.2. La base de données support des TP Voici le schéma Oracle simplifié de la base de données relatif à la Coupe du Monde de football 1998 : EQUIPES ( NE, NATIONALITE, PALMARES ) JOUEURS ( NJ, NOM, PRENOM, POSITION, MAILLOT, NE ) MATCHS ( NM, LIEU, DATE_R, NE_ RECEVANTE, NE_DEPLACANTE, ENJEU, SCORE_RECEVANTE, SCORE_ DEPLACANTE) PARTICIPANTS (NJ, NM, NB_BUTS, CARTON_JAUNE, CARTON_ROUGE ) Remarques : P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 5 La table JOUEURS précise les noms des joueurs et l’équipe à laquelle ils appartiennent. La table MATCHS enregistre pour chaque match l’enjeu (Finale, 1 /2, 1/4, 1/8 ...), la date, le lieu, les numéros des deux équipes ayant participé et leur score respectif. L’équipe ayant gagné correspond à celle ayant le score le plus élevé. La table PARTICIPANTS enregistre pour chaque match la liste des joueurs ayant participé avec le nombre de buts marqués et deux booléens précisant s’ils ont eu un carton jaune et/ou un carton rouge. 3. Actions 3.1. A propos de l'outil SQL-Net8 Easy Config L'outil SQL-Net8 Easy Config permet de créer, sur une machine cliente, les noms des services nécessaires pour accéder aux serveurs des bases de données. Les noms de services ont été configurés directement à l’aide d’un éditeur dans le fichier TNSNAMES.ora de chaque machine. Le principe de fonctionnement de SQL-Net8 Easy Config est retracé ci-dessous. Les actions décrites par l'enchaînement d'écrans figurant ci-dessous créent un service appelé "IGSI". Se service permet, dans l'environnement Oracle de la machine cliente, de se connecter avec l'une des bases Oracle installées sur le serveur "sip-oracle". Cette base (instance) porte le nom "IGSI". Le protocole de communication utilisé est le standard TCP/IP. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 6 La céation des trois services igsi, said et stat via SQL-Net8 Easy Config a produit le résultat suivant dans le fichier TNSNAMES.ora : IGSI.world = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP) (Host = sip-oracle) (Port = 1521) ) (CONNECT_DATA = (SID = IGSI) nom d’une instance sur le serveur sip-oracle ) ) SIAD. world = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP) (Host = sip-oracle) (Port = 1521) ) (CONNECT_DATA = (SID = = SIAD) ) ) STAT. world = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP) (Host = sip-oracle) (Port = 1521) ) (CONNECT_DATA = (SID = STAT) ) ) 3.2. Lancer l'outil Administration Toolbar Bandeau d'outils Oracle, (personnalisable avec le clic droit) 3.3. Lancer l'outil SQL Worksheet L’écran est découpé en 2 zones : la zone des commandes SQL (partie basse), la zone d’affichage des résultats (partie haute). Les 4 dernières icônes de la palette d’outils de la zone des commandes SQL permettent 1. 2. 3. 4. d’exécuter la commande tapée dans la zone d’afficher l’historique des commandes de la session d’afficher dans la zone la commande précédente d’afficher dans la zone la commande suivante. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 7 3.4. Lancer l'outil Admin ODBC Créer une nouvelle source de données respectant le protocole ODBC sur la machine locale, à laquelle on donnera le nom "igsi oracle". La liaison ODBC sera pilotée par le driver Oracle ODBC driver. Cette liaison utilisera le service "igsi" déjà créé. 3.5. Lancer Microsoft ACCESS Créer une nouvelle BD sous votre compte, que vous enregistrerez sous le nom "mondial" et choisir Fichier \ Données externes \ Lier les tables. Choisir le type de fichier "Bases de données ODBC" (dernier de la liste). Choisir la source de données "igsi oracle" déjà créée. Entrer le compte "mondial" et son mot de passe. Sélectionner les tables de la BD "Coupe du monde" qui apparaîtront comme dans la figue qui suit. Consulter les tables comme à l'accoutumée. Comparer le mode d'interaction avec l'outil SQL Worksheet. On prendra soin de consulter les schémas des tables, sous ACCESS et sous SQLWorkSheet. Dans SQL-WorkSheet, les schémas sont accessibles en consultant les deux vues TAB et COL du dictionnaire des données : Select * from tab Select * from col et en invoquant la requête describe nom_de_table P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 8 3.6. Traiter les Requêtes SQL suivantes A partir de la BD implantée sur le compte "mondial", écrire et tester les requêtes suivantes en SQL. On utilisera l'outil clients Oracle SQL-WorkSheet. Q1) Qui a eu un carton rouge en 1 /2 finale ? Afficher le nom et le prénom. Q2) Nombre total de buts marqués par Zidane ? Q3) Liste des joueurs de l’équipe de France qui n’ont pas joué en finale ? Afficher les noms et les prénoms. Q4) Liste des joueurs appartenant à la même équipe que Ronaldo ? Afficher les noms et les prénoms. Q5) Afficher le nom et le prénom de chaque joueur ainsi que le nombre total de buts marqués ? Q5') Q5 sans les joueurs dont on n'a pas enregistré le nombre de buts marqués Q6) Qui a gagné la finale ? Afficher le pays. Q7) Liste des joueurs qui ont joués les mêmes matchs que Zidane. Q8) Nom(s) du (ou des) joueur(s) ayant marqué le plus de buts en un match ? Q9) Nom(s) du (ou des) meilleur(s) buteur(s) ? Q10) Reculer d'un jour la date des 1 /2 finales. Q11) Ajouter une équipe portant votre nom. Editer la liste des équipes avec le nombre de joueurs qui y sont engagés. Que constatez-vous ? Chercher dans la documentation en ligne de SQL les informations sur le "outer join" et modifier votre requête en conséquence. Q12) Ajouter 2 joueurs, votre binôme et vous, à cette équipe, ajouter un match éliminatoire entre cette équipe et l'équipe du binôme voisin, et inscrire les 4 joueurs dans ce match. Quelle est la suite d'actions à entreprendre pour pouvoir supprimer l'équipe que vous avez créée ? P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 9 TP 2 Traduction de règles de gestion à l'aide de déclencheurs Bases de Données 1. Objectifs Pratique du langage PL/SQL Conception de déclencheurs Bases de Données 2. Environnement Le schéma de la base de données relatif à la Coupe du Monde de football 1998 est légèrement modifié pour prendre en compte les gègles de gestion énnoncées ci-dessous : EQUIPES ( NE, NATIONALITE, PALMARES ) JOUEURS ( NJ, NOM, PRENOM, POSITION, MAILLOT, NE, NB_MATCH_EXPULSE ) MATCHS ( NM, LIEU, DATE_R, NE_ RECEVANTE, NE_DEPLACANTE, ENJEU, SCORE_RECEVANTE, SCORE_ DEPLACANTE, SCORE_VALIDE) PARTICIPANTS (NJ, NM, NB_BUTS, CARTON_JAUNE, CARTON_ROUGE ) Règles de gestion 1. Lorsqu'un joueur obtient 2 cartons jaunes au cours d'un match, il reçoit un carton rouge. 2. Lorsqu'un joueur reçoit un carton rouge, il est expulsé pour les deux matchs à venir. 3. Evidemment, un joueur expulsé ne doit pas pouvoir être sélectionné pour participer à un match. Afin de faire respecter ces règles, les deux attributs suivants ont été rajoutés respectivement dans les tables JOUEURS et MATCHS : NB_MATCH_EXPULSE indique à tout moment pour combien de matchs le joueur est encore expulsé depuis le dernier match effectivement joué (le score est validé) par sont équipe. SCORE_VALIDE vaut 1 si le match a été effectivement joué. 3. Actions Proposez les déclencheurs nécessaires pour faire respecter ces règles de gestion. 1. Créez sous votre compte votre propre base de donnée à l'image du schéma ci-dessus. 2. Utilisez le Schema Manager pour créer vos déclencheurs. Nous vous conseillons d'exécuter la requête SQL générée (create or replace trigger) sous le Worksheet pour détecter les éventuelles erreurs de syntaxe. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 10 Rappel : Syntaxe de la création d’un déclencheur CREATE [OR REPLACE] TRIGGER [<nomSchéma>.]<nomTrigger> (AFTER |BEFORE ) ( INSERT | DELETE | UPDATE [OF <nomAttribut> [,<nomAttribut>]*]) ON [<nomschéma>.]<nomTable> [REFERENCING (OLD |NEW) as (<old_perso>|<new_perso>) [,(OLD |NEW) as (<old_perso>|<new_perso>)] ([FOR EACH ROW] | [FOR EACH STATEMENT]) [WHEN <condition>] <module PL/SQL> ; Avec : <nomSchéma> : nom d’un utilisateur <nomTrigger> : nom que l’on souhaite donner au déclencheur <nomAttribut> : nom d’un attribut <nomTable> : nom d’une table <old_perso> : nom donné par l’utilisateur pour renommer le mot clé OLD <new_perso> : nom donné par l’utilisateur pour renommer le mot clé NEW <module PL/SQL> : module PL/SQL sans entête. Exemple : Faire respecter la règle de gestion suivante : 1. Le nombre de buts marqués par un joueur lors d’un match ne peut diminuer. -> A chaque mise à jour du nombre de buts dans la table participants, avant que la mise à jour ne soit effectuée, on compare l’ancien nombre de buts avec le nouveau, si le nouveau nombre est plus petit, on remet l’ancien nombre de buts et on signale une erreur à l’application. CREATE OR REPLACE TRIGGER "MONDIAL"."AUGMENTER_BUTS" BEFORE UPDATE OF "NB_BUTS" ON "MONDIAL"."PARTICIPANTS" REFERENCING OLD AS O NEW AS N FOR EACH ROW WHEN (n.nb_buts<o.nb_buts) BEGIN :n.nb_buts := :o.nb_buts; raise_application_error(-20100, 'le nombre de buts ne peut diminuer'); END; P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 11 Solution Action 3.1 : Il faut créer quatre déclencheurs. Le premier est associé à la table PARTICIPANTS sur mise à jour du nombre de cartons jaunes. Si ce nombre atteint 2, le nombre de cartons rouges est mis à 1. Le deuxième déclencheur est associé à la table PARTICIPANTS sur mise à jour du nombre de cartons rouges. Si ce nombre atteint 1, l'attribut NB_MATCH_EXPULSE de la table JOUEURS est mis à 3 pour le joueur en question. Le troisième déclencheurs est associé à la table MATCHS. Lorsque l'attribut SCORE_VALIDE prend la valeur 1, on décrémente de 1 l'attribut NB_MATCH_EXPULSE pour tous les joueurs expulsés des deux équipes participantes. Le quatrième déclencheurs est associé à la table PARTICIPANTS. Lorsque l'on tente d'insérer une ligne dans cette table, l'insertion est refusée si le joueur en question est expulsé. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 12 Action 3.2 : CREATE OR REPLACE TRIGGER "MONDIAL"."EXPULSER_jaune" BEFORE INSERT OR UPDATE OF "CARTON_JAUNE" ON "MONDIAL"."PARTICIPANTS" REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW BEGIN IF :new.carton_jaune = 2 THEN :new.carton_rouge := 1; virez_le(:new.nj); /* dbms_output.put_line('joueur '||:o.nj||' expulsé'); */ END IF; END; CREATE OR REPLACE TRIGGER "MONDIAL"."EXPULSER_rouge" BEFORE INSERT OR UPDATE OF "CARTON_ROUGE" ON "MONDIAL"."PARTICIPANTS" REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW WHEN (new.carton_rouge=1) BEGIN update joueurs set NB_MATCH_EXPULSE = 3 where nj = :old.nj; END; CREATE TRIGGER "MONDIAL".Expuls_moins_un AFTER UPDATE OF "SCORE_VALIDE" ON "MONDIAL"."MATCHS" REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW WHEN (new.score_valide=1) begin update joueurs set NB_MATCH_EXPULSE = NB_MATCH_EXPULSE - 1 where NB_MATCH_EXPULSE >= 1 and ne in (:new.ne_deplacante, :new.ne_recevante); end; CREATE or replace TRIGGER "MONDIAL".ne_peut_participer BEFORE INSERT ON "MONDIAL"."PARTICIPANTS" REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW declare virer number := 0 ; begin select 1 into virer from joueurs where nj = :new.nj and NB_MATCH_EXPULSE >= 1; if virer = 1 then raise_application_error(-20101, 'Ce joueur est exclu'); end if; exception WHEN_NO_DATA_FOUND then null; end; P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 13 PARTIE II : Développement d’applications clientes avec Developper 2000 TP 3 Le concept de formulaire dans Developer 2000 1. Rappel : Les trois principaux objets d’un module Un formulaire est un élément interactif qui permet d’insérer, consulter et/ou mettre à jours des nuplets stockés dans une base de données. ORACLE propose un environnement de développement permettant de concevoir de tels formulaires appelé Developer 2000, qui s’appuie lui même sur un environnement de plus bas niveau appelé FORMS. Dans cet environnement, les formulaires sont gérés au travers de modules. Un module peut se définir comme un programme évènementiel capable de prendre en charge les interactions d’un utilisateur avec plusieurs formulaires, au même titre qu’une application écrite en Visual Basic. Chaque module constitue un client du serveur base de données ORACLE. Developer 2000 propose trois outils appelés Form Builder, Form Compiler et Forms Runtime, permettant respectivement de concevoir des formulaires, générer le code permettant d’exécuter ces formulaires dans l’environnement d’un système d’exploitation donné (Windows, Unix, IBM ...) et d’exécuter ce code. Afin de faciliter le développement, Forms Builder permet d’exécuter directement les formulaires en cours de conception, en vue de les tester. Nous utiliserons donc principalement cet outil. Un module est constitué d’objets dont les propriétés permettent de définir le comportement de l’application. Certaines propriétés sont modifiables dynamiquement, permettant de modifier le comportement de l’application en cours d’exécution. Certains objets réagissent à des évènements, permettant de déclencher l’exécution de programmes. Les 3 principaux objets constituant un module sont rapidement décrits ci-dessous : Bloc (Block) Un bloc est une structure logique permettant de manipuler des valeurs stockées dans l’environnement du module. Un bloc est lui même constituée d’éléments (les conténaires des valeurs). Un bloc peut être créé manuellement ou automatiquement par le biais d’assistants (wizzards). Un bloc peut être définit en mono ou en multi-lignes. Une propriété permet de fixer ce nombre de lignes. Ainsi un bloc est assimilable à un tableau temporaire stocké dans l’espace mémoire du module (côté client). Un bloc peut être associée une table ou une vue de la base de données (stockée côté serveur). Dans ce cas, on dit qu’il est basé. Deux propriétés permettent de spécifier si le block est basé ou non, ainsi que la table ou la vue à laquelle il est associé. Un block basé est assimilable à une fenêtre dans laquelle on peut faire défiler côté client des n-uplets stockés dans la base de donnée. Pour un bloc basé, Forms prend en charge toutes les interactions en mise à jour entre les valeurs skockées dans le tableau temporaire et celles stockées dans la base de donnée. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 14 Elément (Item) Un élément sert à gérer des données ou à déclencher des commandes Forms ou des Triggers, en fonction de son type. Par exemple, un élément de type texte permet de stocker et d’afficher une valeur pour le bloc auquel il appartient ; un élément de type bouton permet de déclencher des actions comme passer à la ligne suivante dans le bloc courant. Un élément de type texte possède une propriété garantissant qu’il soit simplement consultable ou consultable et modifiable. De plus, une propriété fixe si l’élément doit être visible ou invisible. Pour être visible, il doit être localisé sur un canevas. Cette localisation est elle même définie par une propriété. Canevas (Canvas) Un canevas est une surface sur laquelle sont positionnés des éléments, graphiques ou textes destinés à être affichés à l’écran. Un canevas peut être découpé en onglets. Un canevas est affiché dans une fenêtre. Un module appelé MODULEMONDIAL est présenté figure 1. Ce module gère un formulaire basique permettant de consulter et modifier les n-uplets de la table JOUEURS. Les objets constituant ce module peuvent être explorés sur la sous-fenêtre de gauche, intitulée Navigateur d’Objets. Ainsi, ce formulaire est constitué d’un bloc appelé JOUEURS basé sur la table JOUEURS. Ce bloc comporte 6 éléments de type texte permettant de stocker localement et d’afficher les attributs d’un joueur. Tous les éléments sont localisés sur le canevas CANEVASMONDIAL, représenté sur la sous-fenêtre de droite. On peut observer que ce canevas est constitué d’un onglet appelé ONGLETJOUEURS. Sur cet onglet, les éléments sont placés dans un cadre appelé CADREJOUEURS. On peut aussi observer que le bloc JOUEURS est mono-ligne car chaque élément apparaît une seule fois dans le cadre. Figure 1 : Un module et la fenêtre du canevas correspondant P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 15 Ce formulaire a été généré automatiquement, mais l’emplacement des éléments peut être modifié directement en cliquant sur les objets représentés dans la fenêtre du canevas. De même les propriétés des éléments du module ainsi généré peuvent être modifiées au travers de la sous-fenêtre intitulée Palette de propriétés. Par exemple pour interdire la mise à jour des N° des joueurs, on peut sélectionner l’élément NJ, puis cliquer sur le bouton de droite de la souris pour faire apparaître les propriétés de cet élément comme illustré figure 2. La propriété Interrogation seulement peut alors être mise à vrai. Figure 2 : propriétés de l’élément NJ 2. Création d'un formulaire basique Le TP utilise la base de donnée Mondial. Vous pouvez consulter son schéma à l’aide du Schema Manager ou dans le Navigateur d’objets (section Objets de la base de données) si vous êtes connectés.. Créer le formulaire permettant de consulter les joueurs et de les modifier. Lancer Oracle/Forms and Reports/Form Builder et utiliser les assistants. Vous devez au passage cliquer sur le bouton parcourir pour sélectionner la table JOUEURS. Cette action va déclencher une connexion à ORACLE pour contrôler que vous avez accès au schéma de la base de données. Choisissez les colonnes qui vous intéressent puis demandez à utiliser l’assistant de présentation. Lors de la création du canevas, demandez un canevas avec onglet. Cliquer sur le bouton feu vert pour générer et tester le code du nouveau module (client). Cliquer sur le bouton exécuter l’interrogation pour visualiser les joueurs. Modifier les propriétés de l’élément NOM pour interdire la mise à jour du nom des joueurs. Jouer avec la fenêtre et le canevas. 3. Création d’un formulaire comportant un sous formulaire Créer le formulaire permettant d’afficher les équipes avec leurs joueurs respectifs. Le module correspondant sera composé de deux blocs dont un bloc maître et un bloc esclave (voir figure 3). Le bloc maître est le bloc EQUIPES. Il pilote l’affichage des n-uplets dans le bloc esclave qui est le bloc JOUEURS. En effet, le bloc JOUEURS ne doit afficher que les joueurs de l’équipe en cours dans le bloc EQUIPES. Ainsi les deux blocs sont reliés par une relation dont le but est d’exprimer la condition de jointure entre les deux tables : EQUIPES.NE = JOUEURS.NE. Le bloc EQUIPES P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 16 est un bloc de type formulaire qui affiche un n-uplet à la fois, tandis que le bloc JOUEURS est un bloc de type tabulaire. On fera en sorte que ce dernier affiche 11 joueurs et possède un ascenseur. Créer ce formulaire en utilisant les assistants comme suit. Créer tout d’abord un bloc basique pour la table EQUIPES comme précédemment. Ajouter ensuite un bloc pour la table joueurs : cliquer sur l’élément blocs de données pour le sélectionner, puis cliquer sur le bouton « + » pour ajouter un bloc et demander à utiliser l’assistant. N’oubliez pas de sélectionner la colonne NE à la création du bloc Joueurs sans quoi la jointure avec le bloc Equipes ne pourrait pas être réalisée. Par contre il n’est pas nécessaire d’afficher cette colonne puisqu’elle est déjà renseignée dans le bloc EQUIPES. Si la liaison entre les deux blocs ne s’est pas faite automatiquement, double cliquez sur le composant relation du bloc EQUIPES, sélectionnez le bloc joueurs comme bloc esclave et entrez la condition de jointure comme sur la figure 4. Figure 3 : Formulaire avec un bloc maître et un bloc détail Figure 4 : Création de la relation entre 2 blocs Remarquez la création de deux déclencheurs destinés à la synchronisation de l’affichage dans les deux blocs (voir figure 5). Figure 5 : Déclencheurs et relation permettant de gérer la synchronisation entre 2 blocs Remarques : 1. Le déclencheur ON-POPULATE-DETAILS remplit le bloc détail à chaque changement de ligne dans le bloc maître. Si le code de ce déclencheur est altéré, la synchronisation entre les deux blocs ne se fait plus. Dans ce cas, nous suggérons de supprimer la relation (ce qui a pour effet de supprimer les déclencheurs associés), et de la recréer. 2. Le déclencheur PRE-DELETE exécute un code redondant avec la contrainte d’intégrité référentielle déclarée dans la base de données pour l’attribut NE de la table JOUEURS. Vous pouvez le supprimer. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 17 4. Exercice Concevoir des blocs simples de gestion pour chaque relation de votre base Mondial et de gestion pour chaque relation de la base Mondial du compte Mondial. Vous disposerez ainsi de moyens personnels pour consulter et mettre à jour facilement les deux bases. Concevoir des blocs Maîtres Détails - Equipes Joueurs (de chaque équipe) Participants (de chaque joueur) - Matchs Participants (de chaque match) - Equipes Matchs (que les équipes soient recevantes ou déplaçantes) Participants (de chaque match de chaque équipe) Remarque : le dernier formulaire comporte une double jointure entre EQUIPES et MATCHS sur ne_recevante et ne_deplacante. Il n’est pas possible de spécifier ce type de jointure dans une relation (maitre/detail). Avec l’expérience, vous apprendrez à gérer ce genre de problème (voir TP6). P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 18 TP 4 Listes de valeurs (LOV) et déclencheurs d'applications. Création de boutons et de déclencheurs associés à des LOV écrits en PL/SQL 1 Rappel : la gestion des clés étrangères Les clés étrangères sont utilisées pour relier les tables entre elles. Les clés étrangères posent généralement deux problèmes à l’utilisateur final : 1- elles ne peuvent contenir que des valeurs existant dans la table référencée (contrainte d’intégrité référentielle). En effet une clé étrangère contenant une valeur qui n’existe pas dans la table référencée n’a pas de sens. Par exemple : si l’attribut NE de la table Joueurs contient une valeur qui n’existe pas dans la table Equipes, c’est équivalent à dire que ce joueur appartient à une équipe inexistante. 2- les clés sont souvent peu parlantes pour l’utilisateur final. Par exemple : quelle est la nationalité de l’équipe 3 ? Dans le cas d’un formulaire avec sous formulaire ces problèmes sont généralement contournés. Par exemple, le formulaire Equipes_Joueurs créé au TP précédent constitue le moyen idéal pour saisir une équipe avec tous ses joueurs. La clé étrangère NE de la table JOUEURS est renseignée automatiquement en fonction de la clé de l’équipe courante dans le formulaire principal. Dans le cas d’un formulaire simple, ces deux problèmes peuvent être résolus en utilisant un objet "liste de valeurs" (LOV pour List Of Values) dans le module : Une LOV est une interrogation permettant d’afficher l’ensemble des valeurs possibles et d’en sélectionner une. De plus, une LOV permet d’associer à ces valeurs des information plus parlantes qu’une valeur de clé (par exemple, nationalité de l’équipe). 2. création d’un formulaire exploitant une LOV 2.1. Exercice : expérimentation des LOV 1. Créer un formulaire simple pour la table PARTICIPANTS au moyen des assistants. Cette table contient deux clés étrangères : NJ et NM. 2. Création d’une LOV pour NJ : Double cliquez sur l’objet LOV de votre module pour créer la LOV et sélectionnez l’assistant. Au travers de cet assistant, demandez à ce que cette LOV permette d’afficher NJ, NOM, PRENOM pour chaque JOUEUR (nom et prénom sont affichés à titre d’information). L’assistant doit générer une requête du type : SELECT NJ, NOM, PRENOM FROM JOUEURS ; Toujours au travers de l’assistant, précisez ensuite que la valeur NJ ramenée par cette LOV devra être assignée à l’élément NJ du bloc PARTICIPANT. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 19 Renommez la LOV créée en ‘LOV_NJ’ 3. Tester le module. Lorsque l’on clique sur l’élément NJ, la barre d’état indique qu’une liste de valeurs est disponible (voir figure 1). Pour afficher cette liste appuyez sur la touche fonction F9. Cette barre d’état indique que l’on est positionné sur le 1 er N-uplet et que l’élément en cours peut être instancié via une liste de valeurs Figure 1 : barre d’état Remarque Pour connaître les touches fonction de Forms et leur rôle, activez le menu Aides/Touches. 2.2. Concepts autour des LOV Dans Developper 2000, une LOV est un objet permettant de faire afficher une liste de valeurs (table) dans une fenêtre et de récupérer une valeur choisie par l’utilisateur dans cette liste, pour la stocker dans un élément d’un bloc. La liste des valeurs affichée correspond au résultat d’une requête SQL. Cette requête est elle même stockée dans un autre objet appelé record group ou plus simplement GROUPE. Le même GROUPE (la même requête) peut être associé à plusieurs LOV. Par contre, une LOV est spécifique à un ELEMENT dans un BLOC, pour lequel elle ramène une valeur. La propriété correspondance de colonne d’une LOV à pour objet de désigner cet élément récepteur. Enfin, l’ELEMENT récepteur d’une LOV comporte une propriété appelée LOV permettant de désigner la LOV qui lui est associée. Ces trois aspects sont détaillés ci-dessous. 2.2.1. Record Group Le rôle d’un record group est de stocker une requête associée à une ou plusieurs LOV. Cette requête permet de construire dynamiquement les valeurs de cette liste lorsque la LOV est invoquée. La figure 2 illustre la création d’un groupe d’enregistrement. La requête associée sélectionne les n° des joueurs de la table JOUEURS, mais aussi les noms et prénoms des joueurs, qui sont plus parlants pour l’utilisateur final. Une fois le GROUP créé, la requête est stockée dans la propriété Query de ce dernier. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 20 Figure 2 : La requête associée à une LOV 2.2.2. Correspondance de Colonne La spécification des colonnes de la requête qui seront affectées à un élément d’un bloc, se fait au moyen de la propriété correspondance de colonne de l’objet LOV en cours de création. Vous pouvez consulter ou modifier cette spécification en cliquant sur le bouton Suite de cette propriété. Dans notre cas, il faut associez la colonne NJ du record group de la LOV à l’élément NJ du bloc PARTICIPANTS (voir figure 3). Au travers de cette propriété, il est en fait possible de spécifier un récepteur pour chaque colonne résultat de la requête du record group. Cela est principalement utile lorsqu’une LOV est définie pour une clé étrangère comportant plusieurs attributs. (Cela pourrait aussi être utilisé pour renseigner des éléments non-basés, mais ce genre d’utilisation conduit à des formulaires avec des anomalies de comportement.) Figure 3 : Association de la colonne NJ de la liste à l’élément NJ du bloc 2.2.3. Association d’une LOV à un Elément La propriété LOV d’un élément permet d’indiquer qu’une LOV lui est associée et donc de remonter cette information à l’utilisateur dans la barre d’état du formulaire. Elle permet en plus, d’identifier la LOV qu’il faut aller chercher lorsque l’utilisateur appuie sur la touche F9. Dans notre cas, la propriété LOV de l’élément NJ a été modifiée pour signaler qu’une LOV est associée à cet élément, il s’agit de la LOV ‘LOV4’ (voir figure 4). Figure 4 : affecter une LOV à un Élément P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 21 3. Améliorer l'utilisation d'une LOV Vous pouvez remarquer que la LOV créée pour NJ n'est pars facile à exploiter par l'utilisateur final. En effet, pour sélectionner un joueur par son nom, il faut faire défiler toute la liste avec un ascenseur. Or, la fenêtre d'affichage d'une LOV comporte un outil de recherche. Cet outil s'applique exclusivement à la première colonne affichée dans la LOV et nécessite que cette colonne soit triée. Ainsi, il serait souhaitable de réécrire la requête associée à la LOV afin d'afficher en premier le nom du joueur. La requête modifiée et la LOV associée sont affichées ci-dessous. Attention, il est nécessaire de trier le résultat, pour que les outils de recherche fonctionnent. SELECT ALL JOUEURS.NOM, JOUEURS.PRENOM, JOUEURS.NE, JOUEURS.NJ FROM JOUEURS ORDER BY 1 Exercice : Améliorez la LOV LOV_NJ. Expérimentez-la. Créez une LOV pour NM. 4. Déclencheurs d’application (triggers) Il existe plusieurs solutions pour contrôler l’affichage de la LOV associée à un élément (élément), dans de but d’éviter à l’utilisateur non averti d’avoir à utiliser la touche F9. Ces solutions utilisent toutes des déclencheurs provoquant l’exécution d’un programme écrit en PL/SQL lorsqu’un événement intervient au niveau de l’application. Un déclencheur d’application (trigger) est définit par trois principaux composants : - son type (évènement associé) ; son code (bloc PL/SQL); son niveau de définition (objet de l’interface auquel il est associé). Les objets auxquels il est possible d’associer des déclencheurs sont les modules de type "application forms", les blocs et les éléments des blocs (items). Les évènements permettant d’activer un déclencheur d’application varient en fonction du type d’objet de l’interface auquel est associé le déclencheur (module, bloc ou élément). Ces évènements sont classés en cinq catégories : - PRE- se déclenche avant une action forms ; POST- se déclenche après une action forms ; P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 22 - WHEN- se déclenche au même moment qu’une action forms (permet d’augmenter les fonctionnalités de forms) ; ON- se déclenche à la place d’une action forms ; KEY- se déclenche à la place d’une touche fonction standard. Pour obtenir la liste de ces événements avec leur signification précise utiliser la commande de l’aide de developper/2000 Aide/Form builder Rubrique d’aide/Référence PL/SQL /Déclencheurs. Vous pouvez aussi passer par la commande rechercher du menu Aide/Form builder Rubrique d’aide. Rappelons que la structure d’un bloc PL/SQL est la suivante : [DECLARE] - variables, constantes, curseurs, expressions utilisateurs BEGIN - Ordres SQL - Instructions de contrôle PL/SQL [EXCEPTIONS] - Traitements à effectuer lors des erreurs END ; Chaque instruction à l’intérieurs du bloc se termine par un point virgule. Comme indiqué ci-dessus, un bloc PL/SQL comporte 3 sections (DECLARE, BEGIN et EXCEPTIONS) parmi lesquelles seule la section BEGIN est obligatoire. Dans le cas ou les blocs DECLARE et EXCEPTIONS sont omis, les lignes BEGIN et END sont facultatives. 4.1. Exemple : contrôler l’affichage d’une LOV dans un formulaire DECLARE rep BOOLEAN; BEGIN IF :participants.nj is null THEN bell; rep := show_lov ('LOV_NJ'); END IF; END; 4.1.1 Préfixe ":" pour désigner les éléments des blocs Dans le code ci-dessus, rappelons que dans l’expression :participants.nj le préfixe ‘:’ permet de désigner l’élément NJ du bloc PARTICIPANTS par opposition à l’attribut NJ de la table PARTICIPANTS (lui même désigné par l’expression participants.nj). 4.1.2. Fonctions utiles pour les application écrites en PL/SQL est une commande très utile de PL/SQL. Il en existe bien d’autres de ce type. Il est impossible de les connaître toutes par cœur. Pour les retrouver et apprendre à les utiliser, exploitez la commande d’aide Aide/Form builder Rubrique d’aide/Référence PL/SQL /Packages intégrés de Form Builder/Liste alphabétique. show_lov 4.2. Déclencheurs d’application et déclencheurs base de données Deux aspect fondamentaux différencient les déclencheurs d’application des déclencheurs base de données : P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 23 1. Qui contrôle les évènements déclencheurs ; 2. Où les code est-il exécuté. Dans le cas des déclencheurs d’application, les évènements sont contrôlés par l’application cliente et le code PL/SQL est exécuté sur la machine cliente. Dans le cas des déclencheurs base de données les évènements sont contrôlés par le serveur base de données et le code PL/SQL est exécuté sur la machine serveur. 5. Gestion automatique de l’affichage d’une LOV Trois solutions vous sont proposées pour afficher la LOV associée à NJ. comportent des inconvénients. Certaines Solution 1 : création d’un déclencheur WHEN-MOUSE-DOUBLECLICK pour l’item NJ Pour créer ce déclencheur cliquez sur le + de l’élément NJ afin de faire apparaître l’objet déclencheur. Double cliquez sur cet objet puis choisissez l’événement voulu dans la fenêtre du déclencheur (WHEN-MOUSE-DOUBLECLICK). Un éditeur PL/SQL s’ouvre automatiquement. Saisissez le code ci-dessous. Cliquez sur le bouton compiler de la fenêtre du déclencheur pour tester la syntaxe de votre code. DECLARE rep BOOLEAN; BEGIN bell; rep := show_lov ('LOV_NJ'); END; /* LOV_NJ est le nom de la LOV */ Testez ce déclencheur. L’inconvénient de cette solution est que l’utilisateur doit connaître la convention qui consiste à double cliquer sur l’élément pour faire apparaître la LOV. Solution 2 : création d’un déclencheur PRE-TEXT-ITEM pour l’élément NJ Procédez comme précédemment, en exploitant en événement de type PRE-TEXT-ITEM et en utilisant le code ci-dessous. declare recstat VARCHAR2(20) := :System.record_status; rep boolean; BEGIN IF ( recstat = 'NEW' or recstat = 'INSERT' ) THEN rep:=show_lov('LOV_NJ'); END IF; END; Un événement de type PRE-TEXT-ITEM se déclenche chaque fois que l’élément en question devient actif (par le biais de la touche tabulation, suite à un click souris, suite au passage à la ligne suivante, suite à l’ouverture du module …). Ainsi, afficher une LOV systématiquement alors que l’utilisateur peut exploiter le formulaire simplement pour consulter une table, peut devenir un élément perturbateur. Examinez la condition ci-dessus. Elle consulte une variable système (nom de variable préfixé par ‘ :’) indiquant l’état de l’enregistrement en cours. S’il n’est pas en mode insertion (l’utilisateur P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 24 vient de créer une nouvelle ligne), le déclencheur ne fait rien. Cette solution pallie l’inconvénient mentionné ci-dessus. Par contre, la LOV ne s’affichera qu’en mode création et pas en mode mise à jour. Respecter les majuscules dans 'NEW' et dans 'INSERT'. Solution 3 : création d’un bouton avec un déclencheur WHEN-BUTTON-PRESSED Ajoutez un nouvel élément au bloc PARTICIPANTS. Modifier la propriété type d’élément de ce nouvel élément, choisissez bouton de commande. Dans la propriété libellé de cet élément, introduisez la chaîne '\/' (back-slach, slash). Dans la propriété canevas, mentionnez le canevas de votre formulaire. Renommez cet élément Liste_Joueurs. Ajoutez le déclencheur à l’élément avec le même code que dans la solution 1. Modifiez le canevas pour que le bouton prenne l’aspect d’une boîte liste à côté de NJ. Attention, pour que ce bouton puisse être utilisé, il faut que son élément soit placé en premier dans la liste des éléments du bloc. Observez les anomalies d’utilisation de ce bouton. Ces anomalies peuvent être évitées en positionnant la propriété obligatoire de NJ à faux. 6. Gestion de LOV dynamiques En fonction du contexte de l’application, il peut être utile qu’une LOV n’affiche que les données pertinentes d’une table. Par exemple, une fois le numéro de match saisi dans une ligne du formulaire de la table participants, il serait souhaitable que la LOV permettant de saisir un numéro de joueur restreigne l’affichage aux joueurs d’une des équipes participant au match choisi. Ainsi, la requête associée à cette LOV doit être modifiée automatiquement, à chaque appel de la LOV, afin de prendre en compte le numéro de match saisi dans l’élément NM courant du bloc ( :PARTICIPANTS.NM). Pour cela, nous allons utiliser la fonction POPULATE_GROUP_WITH_QUERY décrite ci-dessous. POPULATE_GROUP_WITH_QUERY : Lorsque vous utilisez cette procédure interne, l'interrogation indiquée devient l'interrogation par défaut du groupe, et s'exécute chaque fois que la procédure interne POPULATE_GROUP est appelée. Si l'instruction SELECT échoue, Form Builder retourne un numéro d'erreur ORACLE. Si l'interrogation réussit, cette procédure interne retourne 0 (zéro). Les lignes extraites remplacent les lignes du groupe d'enregistrements. Syntaxe FUNCTION POPULATE_GROUP_WITH_QUERY (id_groupeenreg RecordGroup, interrogation VARCHAR2); FUNCTION POPULATE_GROUP_WITH_QUERY (nom_groupeenreg VARCHAR2, interrogation VARCHAR2); P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 25 Modifiez le code du déclencheur associé au bouton de la LOV LOV_NJ comme suit : declare rep boolean; -- retour show_lov myQuery varchar2(250); -- texte de la requete acqQuery Integer; -- retour de l'execution de la requete begin myQuery := 'Select nom, prenom, nj from joueurs, matchs where nm =:participants1.nm and ne in (ne_recevante, ne_deplacante) order by nom, prenom'; -- redéfinition du groupe conformément à la requete SQL "myQuery" acqQuery := populate_group_with_query('RG_LOV_NJ', myQuery); /* 0 --> tt c'est bien passe */ if(acqQuery = 0) then rep := show_lov('LOV_NJ'); else raise form_trigger_failure; end if; end; Autre version : declare rep boolean; begin /* L'exemple ci-dessous montre comment remplacer l’interrogation ** d’un groupe d'enregistrements lors de l'exécution : */ IF (Populate_Group_With_Query ( 'LOV_NJ', 'SELECT NOM, PRENOM, NE, NJ FROM JOUEURS where ne in (select ne_recevante from matchs where nm = :participants.nm union select ne_deplacante from matchs where nm = :participants.nm) order by 1 ' ) ) <> 0 THEN RAISE form_trigger_failure; END IF; rep:=show_lov('LOV_NJ'); END; P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 26 TP 5 Synchronisations entre les Blocs de données (FORMS) et la Base de Données 1. Notion de Bloc basé / non basé Un Bloc basé est destiné à permettre la consultation et/ou la mise à jours de données issues d'une table stockée dans la base de données ou d'une vue. Le nom de la source de données est une des propriétés Base de données du Bloc. Au niveau du client Forms, le bloc se comporte comme un tableau de lignes appelées enregistrements (records), capables d'accueillir des n-uplets issues de la source de données. La structure de ces n-uplets est décrite par les éléments basés du bloc, ceux dont la propriété Elément base de données est positionnée à la valeur Oui. 1.1. Remplissage du tableau d’un Bloc avec des données stockées dans la Base de données Au démarrage de l'application cliente, ce tableau d'enregistrements est vide. La procédure prédéfinie "EXECUTE_QUERY" permet d'extraire les données de la source pour remplir le tableau du bloc actif. Cette procédure peut être activée par l'utilisateur en utilisant l'un des moyens suivants : la commande du menu Interrogation/Exécuter ; le bouton exécuter interrogation ; la touche F8. Elle peut aussi être appelée dans un déclencheur d'application. Par défaut, l'interrogation permettant d'instancier le tableau d'un bloc a la structure suivante : SELECT INTO FROM ALL E1, E2,…,En :E1, :E2,…, :En source ; où :E1, :E2,…, :En sont les noms des Eléments basés du Bloc et source est le nom de la source de données (table ou vue) du Bloc. Cette requête porte sur une seule table. Elle peut comporter une clause WHERE si l'utilisateur a utilisé l'un des moyens suivants : la commande du menu Interrogation/Entrer ; le bouton Entrer Interrogation ou la touche F7, avant d'exécuter l'interrogation. Il est aussi possible de spécifier une clause WHERE par défaut pour un Bloc, via la propriété Base de données du bloc nommée DEFAULT_WHERE. Cette propriété peut être modifiée par programmation dans un déclencheur en utilisant la procédure SET_BLOCK_PROPERTY spécifiée ci-dessous : SET_BLOCK_PROPERTY ('NOM_DU_BLOC', DEFAULT_WHERE, 'prédicat'); Il peut arriver que l’action EXECUTE_QUERY retourne le message d'erreur suivant : "Exécution de l'interrogation impossible". Cela peut être du au fait que l'un des Eléments basés du Bloc porte un nom ne correspondant pas à un attribut de la source de données. Cela peut aussi être du à une erreur dans la clause WHERE. Pour voir le texte de la requête qui a été envoyée au serveur base de données, appeler la commande du menu "Aide/Afficher erreur". 1.2. Actualisation de la Base de données via le tableau d’un Bloc Par défaut, l’utilisateur a le droit de modifier, insérer ou supprimer des lignes dans le tableau correspondant à un Bloc de données. Il est possible de restreindre les droits de mise à jour d’un utilisateur, au niveau de l’application Forms, via les propriétés Base de données Insertion autorisée, P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 27 Mise à jour autorisée et Suppression autorisée. Ces propriétés peuvent être réglées au niveau du Bloc ou de ses Eléments. Si l'application lui en donne l'autorisation, l'utilisateur peut insérer, modifier des lignes dans le tableau d'un bloc. La procédure prédéfinie COMMIT_FORM permet de transférer les données modifiées du tableau du bloc vers la source de données (dans la base de données). Cette procédure peut être activée par l'utilisateur en utilisant l'un des moyens suivants : la commande du menu Action/Enregister ; le bouton Enregister ; la touche F10. Elle peut aussi être appelée dans un déclencheur d'application. Etant donné que les mises à jours dans le tableau du bloc, sont effectuées indépendamment de la Base de données, la commande transactionnelle ROLLBACK ne peut pas fonctionner pour annuler les mises à jour effectuées au travers d'une application Forms. La seule solution pour annuler les mises à jour, consiste à vider le tableau du Bloc puis à ré-exécuter l’interrogation du Bloc. Forms Builder propose les procédures suivantes permettant respectivement de vider tous les Blocs, vider le Bloc actif, vider l’enregistrement actif, vider l’Elément actif : CLEAR_FORM (mode_commit) ; CLEAR_BLOCK (mode_commit) ; CLEAR_RECORD ; CLEAR_ITEM ; mode_commit admet plusieurs options (cf. documentation). L’option NO_VALIDATE permet de vider le ou les blocs sans transmettre les mises à jour à la base de données. 2. Concevoir l’interface d’un Bloc basé 2.1. Utilisation d'alertes Une alerte est un objet rattaché à un module, permettant d'afficher un message dans une boîte de dialogue. Ainsi, une alerte est dotée de la propriété message destinée à recevoir le message qui doit être affiché dans la boîte de dialogue. Vous pouvez changer un message d'alerte lors de l'exécution en lançant la procédure interne SET_ALERT_PROPERTY. Cette option vous permet de réutiliser le même objet alerte, mais d'afficher un message différent chaque fois qu'il est appelé. Enfin, la fonction ci-dessous permet de d'afficher la boîte de dialogue d'une alerte : reponse numerique */ := Show_Alert ('NOM_DE_L_ALERTE'); /* reponse est de type Ouvrir l’application Equipes_Joueurs comportant un bloc maître pour la table EQUIPES et un bloc esclave pour la table JOUEURS (cf. TP précédents). Créer une alerte générique appelée ALERTE_GENERIQUE. Y associer le message 'Coucou'. Créer la fonction Msg_Box via le Navigateur d’objets (section Unités de programme) avec le code ci_dessous : P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 28 FUNCTION Msg_Box (titre varchar2, message varchar2) RETURN boolean IS rep integer; BEGIN set_alert_property('ALERTE_GENERIQUE', TITLE, titre); set_alert_property('ALERTE_GENERIQUE', ALERT_MESSAGE_TEXT, message); rep := show_alert('alerte_generique'); if rep = ALERT_BUTTON1 then return true; else return false; end if; END; Créer un bouton dans le bloc EQUIPES permettant d'afficher le message ‘Vous avez cliqué sur le bouton message’ dans une fenêtre intitulée ‘ceci est un message’ . declare rep boolean; begin rep:= Msg_Box ('ceci est un message','Vous avez cliqué sur le bouton message'); end 2.2. Contrôle du remplissage d’un Bloc Créer un déclencheur WHEN-NEW-FORM-INSTANCE affecté au module. Ce bloc contiendra le code suivant : BEGIN EXECUTE_QUERY; /* REMARQUE ** Il aurait été possible de Simuler l'action sur la touche F8 en ** utilisant la procédure interne : DO_KEY. ** Exemple : ** Do_Key('EXECUTE_QUERY'); */ END; Exécuter la forme. Que se passe-t-il ? Il se peut que le Bloc actif ne soit pas le bloc EQUIPES au moment ou l'application Forms démarre. Le Bloc actif à l’ouverture de l’application est le premier Bloc qui apparaît dans la liste des Blocs de données du module Forms. Deux solutions sont possibles pour activer en premier le Bloc EQUIPES : a) Le mettre en premier dans l’élément Blocs de données en le faisant glisser avec la souris. b) Ajouter dans le déclencheur WHEN-NEW-FORM-INSTANCE la commande : Go_Block('EQUIPES'); avant la commande : EXECUTE_QUERY; Tester les deux solutions. A titre d’exercice, mettez un prédicat erroné (ex : ‘trurlututu’) dans la propriété Clause WHERE ou Clause ORDER BY d’un Bloc, puis observez la requête SQL envoyée au serveur via la commande du menu "Aide/Afficher erreur". P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 29 2.3. Restriction dynamique du remplissage d’un Bloc Objectif : restreindre dynamiquement les données chargées dans le bloc PARTICIPANTS. Assurez-vous que votre module possède un onglet permettant d’afficher les participants d’un match. Si ce n’est pas le cas, ouvrez le module PARTICIPANTS créé au cours du TP concernant les LOV. Créez un bloc non basé appelé RESTRICTION comportant un Elément (Text Item) permettant à l’utilisateur de saisir un n° de match et deux boutons RESTREINDRE et TOUT_AFFICHER permettant respectivement, sur un simple clic, de n’afficher que les participants du match choisi par l’utilisateur ou de tout afficher. La marche à suivre est détaillée ci-dessous. Créez un bloc appelé RESTRICTION sans utiliser les assistants. Signaler qu’il ne s’agit pas d’un bloc basé sur la base de données (cf. figure 1). Y ajouter un Elément (Text Item) et deux boutons. N’oubliez pas d’afficher l’élément et les boutons dans le canevas (ou l’onglet) courant. Figure 1 : Création d’un bloc non basé Renommez l’Elément de saisie "NM". Affectez aux boutons des libellés appropriés. Ajoutez au bouton "RESTREINDRE" un déclencheur WHEN-BUTTON-PRESSED avec le code ci-dessous : declare rep boolean; begin if ltrim(rtrim(:restriction.nm)) is null then bell; rep := Msg_Box('message', 'Vous n''avez pas sélectionné de match !'); else SET_BLOCK_PROPERTY ('PARTICIPANTS', DEFAULT_WHERE, 'NM = '||:restriction.nm); Go_Block ('PARTICIPANTS'); Execute_Query; end if; end; Associez au bouton TOUT_AFFICHER le code ci-dessous : begin SET_BLOCK_PROPERTY ('PARTICIPANTS', DEFAULT_WHERE,''); Go_Block ('PARTICIPANTS'); /* remarquez que la clause WHERE est vide */ Execute_Query; end; 2.4. Afficher des données calculées dans un Elément non basé Ajouter au bloc JOUEURS un nouvel Elément non basé (i.e. avec la propriété "Elément de base de données" positionnée à FAUX). Renommer cet Elément "CALCULE" et initialiser correctement ses propriétés Canevas et Page d'onglet. Placer cet élément au bon endroit dans le canevas et lui affecter une étiquette. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 30 Affecter à cet élément un déclencheur WHEN_MOUSE_CLICK avec le code suivant : select sum(nb_buts) into :joueurs.calcule from participants where nj = :joueurs.nj; Pensez à ajouter des buts dans la table PARTICIPANTS pour tester votre code. Problème : Malheureusement, avec ce déclencheur, il faut cliquer sur l’élément calculé pour afficher son contenu. Pour que toutes les instances de l'Elément CALCULE se remplissent automatiquement, il faut exécuter l’instruction SELECT pour chaque enregistrement du bloc JOUEURS. A cette fin, on pourrait utiliser le code suivant : Go_Block ('JOUEURS'); First_Record; -- se positionner sur la première ligne du bloc WHILE :System.Last_Record <> 'TRUE' LOOP -- tant que les lignes select sum(nb_buts) into :joueurs.calcule -- contiennent des valeurs … from participants where nj = :joueurs.nj; Next_Record; -- passer à la ligne suivante END LOOP; Comment exécuter ce code automatiquement ? Solution : déclencheur Post-Query La solution la plus simple consiste à utiliser le déclencheur Post-Query dont la spécification est rappelée ci-dessous : Lorsqu'une interrogation est ouverte dans le bloc, le déclencheur PostQuery est activé chaque fois que Form Builder extrait un enregistrement d'un bloc. Il est donc activé une fois par enregistrement placé dans le tableau des enregistrements du bloc. Pour cet exemple, le déclencheur Post-Query doit être déclaré au niveau du bloc EQUIPES avec le même code que pour le déclencheur WHEN_MOUSE_CLICK. La boucle n’est plus nécessaire ! 3. Corrélation entre les Blocs, l’actualisation de la Base de Données et les Déclencheurs d’application 3.1 Création d’un bloc non basé comportant les boutons Valider et Annuler Objectif : Comprendre pourquoi une bouton ANNULER dans l’application ne peut pas se contenter d’exécuter l’ordre SQL ROLLBACK. Application : créer deux boutons VALIDER et ANNULER pour les blocs imbriqués EQUIPES et JOUEURS. Créer un bloc appelé "COMMANDES" sans utiliser les assistants. Signaler qu’il ne s’agit pas d’un bloc basé sur la base de données (cf. figure 1). Y ajouter deux boutons dotés chacun d'un déclencheur WHEN_BUTTON_PRESSED permettant respectivement de valider une transaction avec ses mises à jour ou d’abandonner la transaction. Affecter à ces boutons des libellés appropriés. Utiliser la commande d’aide pour trouver les instructions PL/SQL que le bouton VALIDER doit exécuter. N’oubliez pas d’afficher ces boutons dans le canevas (ou l’onglet) courant. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 31 Le code que doit exécuter le bouton ANNULER est le suivant : message('abandon des mises à jour'); --rollback; /* ne marche pas dans un déclencheur d’application */ --raise form_trigger_failure; /* ne marche pas dans ce cas */ Go_Block ('EQUIPES'); Clear_Block(No_validate); Go_Block ('EQUIPES'); Execute_Query; Remarque 1 : La commande MESSAGE permet d'afficher un message à l'utilisateur. L'inconvénient de cette commande est le suivant : selon le contexte, le message s'affiche soit en bas de l'écran dans la barre d'états, soit dans une fenêtre spécifique avec un bouton OK. La deuxième solution est préférable, mais on ne peut pas l'imposer. C’est pourquoi il vaut mieux utiliser les alertes. Remarque 2 : Après une mise à jour dans le Bloc JOUEURS, le bouton Annuler génère un message "Voulez-vous enregistrer les mises à jours". Ce message provient du fait des synchronisations maître/détail avec le Bloc EQUIPES. Le même message s'affiche après une mise à jour dans le Bloc JOUEURS puis un changement d'équipe dans le Bloc maître. Pour éviter cet inconvénient, il faut utiliser l'instruction Clear_Form qui ne contraint pas l'application à revenir dans le Bloc EQUIPES. Par contre, cette instruction a pour effet de vider aussi les Blocs des autres onglets. Clear_form (no_validate); /* vide le bloc */ Go_Block('EQUIPES'); /* se positionnne sur le bloc maître*/ Execute_Query; 3.2. Gestion d’une contrainte d’intégrité au niveau de l’application Objectif : Comprendre les corrélations qu’il peut y avoir entre l’actualisation des blocs et la mise en œuvre de contraintes d’intégrité au niveau de l’application. Dans ce but, nous allons introduire une contrainte qui interdit l’insertion de plus de 14 joueurs pour une équipe. Ouvrez l’application EQUIPES_JOUEURS. Créez deux déclencheurs associés au module, le premier de type PRE-COMMIT et le second de type POST-FORMS-COMMIT. Leur associer le code : Declare V_ne V_nbj reponse number (11); number; number; Begin V_ne := :EQUIPES.NE; select count(nj) into V_nbj from joueurs where ne = V_ne; Set_Alert_Property('ALERTE_GENERIQUE',ALERT_MESSAGE_TEXT, 'Précomit : '||V_nbj); reponse := Show_Alert('ALERTE_GENERIQUE'); End ; Exécutez le module, insérer 2 joueurs à une équipe, validez et observez les messages : P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 32 les joueurs ne sont pas encore insérés dans la BD au moment de l'exécution du "précommit". Modifiez le déclencheur POST-FORMS-COMMIT pour qu’il exécute le code suivant : Declare V_ne number (11); V_nbj number; trop_joueurs Exception; Begin V_ne := :EQUIPES.NE; /* Numéro de l’équipe actuelle */ select into from where count(nj) V_nbj joueurs ne = V_ne; /* combien y a-t-il de joueurs */ /* pour cette équipe ? */ If v_nbj > 14 Then /* si ce nombre est > 14 on */ raise trop_joueurs; /* lève une exception */ End If; Exception When trop_joueurs Then /* cette exception affiche un message */ message ('plus de 14 joueurs'); bell; bell; --ROLLBACK; /* Rappel : pas possible ici. */ Raise form_trigger_failure; End; Remarques : a) Avec ce code, les joueurs insérés en trop restent affichés. L’utilisateur peut supprimer quelques joueurs, puis tenter de revalider sa transaction. b) Si l'on insère des joueurs dans une équipe, puis on essaie de changer d’équipe pour insérer d’autres joueurs, Forms force la validation de la transaction. Le code ci-dessus est correct. En effet, si la transaction n’était pas validée à chaque changement d’équipe, le comptage du nombre de joueurs pour l’équipe en cours ne serait pas suffisant. 3.3. Création d’une contrainte sur un élément comparant les valeurs avant maj et après maj Objectif : Exploiter le fait que les valeurs mises à jours dans les formulaires ne sont pas actualisées dans la BD tant que la transaction n’a pas validé. Exemple : introduction une contrainte qui interdit de diminuer le nombre de buts marqués par un joueur. Ouvrir l’application PARTICIPANTS. Créer un déclencheur associé à l’élément NB_BUTS de type WHEN-VALIDATE-ITEM et lui associer le code : Declare old_nb_buts number; Begin Select nb_buts Into old_nb_buts /* la BD n’a pas encore été mise à jour */ From participants Where nj = :participants.nj and nm = :participants.nm; If :participants.nb_buts < old_nb_buts Then message('le nombre de buts doit augmenter'); raise form_trigger_failure; End If; End; P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 33 Tester si la nouvelle contrainte est bien appliquée. 3.4. Question de cours Peut-on remplacer les deux contraintes d'application créées dans les exercices VI et VII par des contraintes d'intégrité déclarées au niveau du schéma de la base de données ? Quels sont les avantages et inconvénients des deux solutions ? P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 34 TP 6 Gestion spécifique des blocs maître/détail lorsqu'il n'y a pas de jointure directe entre les blocs. Gestion du remplissage des blocs détails à l'aide de déclencheurs écrits en PL/SQL 1 Position du problème On souhaite produire le formulaire de la figure 1, qui présente chaque match avec les deux équipes impliquées et la liste des participants de chaque équipe : Figure 1 : un formulaire non standard Ce formulaire est constitué de 5 Blocs de données et de 2 Relations Maître-Détails : le bloc MATCHS est basé sur la table Matchs, les blocs EQUIPE_REC et EQUIPE_DEP sont basés sur la table Equipes et sont en relation Maître Détails avec MATCHS avec les conditions respectives : "Matchs.ne_recevante=Equipe_rec.ne" et "Matchs.ne_deplacante=Equipe_dep.ne". les blocs PARTICIPANTS_REC et PARTICIPANTS_DEP sont basés sur la table Participants et ne sont en relation avec aucun autre bloc. Le remplissage dans des blocs PARTICIPANTS_REC et PARTICIPANTS_DEP ne peut être géré par une Relation Maître-Détails car il n'y a pas de condition de jointure directe entre le bloc MATCHS et ces 2 blocs. Il n'est pas non plus possible de créer une relation Détails-Détails entre les blocs EQUIPE_REC et PARTICIPANTS_REC (resp EQUIPE_DEP et PARTICIPANTS_DEP). P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 35 2. Utiliser la fonction Execut_Query dans un déclencheur déclencheur ONPOPULATE-DETAILS La solution consiste à remplir ces 2 blocs par programmation dans le déclencheur ONPOPULATE-DETAILS du block MATCHS, en le complétant de la façon suivante : -- Note : le code en italique correspond au code généré par Form_Builder pour les -- deux relations : 'MATCHS.MATCHS_EQUIPE_REC' et 'MATCHS.MATCHS_EQUIPE_DEP' --- Begin EQUIPES_REC detail program section -IF ( (:MATCHS.NE_RECEVANTE is not null) ) THEN rel_id := Find_Relation('MATCHS.MATCHS_EQUIPE_REC'); Query_Master_Details(rel_id, 'EQUIPE_REC'); --- Début du Code à ajouter au déclencheur -- Set_Block_Property('PARTICIPANTS_REC',DEFAULT_WHERE, 'nj in (select nj from joueurs where ne = ' ||:MATCHS.NE_RECEVANTE ||') and nm = ' ||:MATCHS.NM); go_block('PARTICIPANTS_REC'); execute_query; --- Fin du Code à ajouter au déclencheur -- END IF; --- End EQUIPES_REC detail program section ---- Begin EQUIPES_DEP detail program section -IF ( (:MATCHS.NE_DEPLACANTE is not null) ) THEN rel_id := Find_Relation('MATCHS.MATCHS_EQUIPE_DEP'); Query_Master_Details(rel_id, 'EQUIPE_DEP'); --- Début du Code à ajouter au déclencheur -- Set_Block_Property('PARTICIPANTS_DEP',DEFAULT_WHERE, 'nj in (select nj from joueurs where ne = ' ||:MATCHS.NE_DEPLACANTE ||') and nm = ' ||:MATCHS.NM); go_block('PARTICIPANTS_DEP'); execute_query; --- Fin du Code à ajouter au déclencheur -- END IF; --- End EQUIPES_DEP detail program section -- REMARQUES : Cette solution est basée sur l'utilisation de l'instruction EXECUTE_QUERY pour remplir les éléments des blocs PARTICIPANTS_REC et PARTICIPANTS_DEP avec les valeurs stockées dans la base de données. Dans la logique de Form Builder, c'est la seule solution possible P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 36 pour remplir des blocs basés. En effet toute affectation d’un élément (même par programme) est considérée comme une mise à jour. 3. Création d’un bloc non basé affichant des données Une autre solution consiste à débaser les blocs PARTICIPANTS_REC et PARTICIPANTS_DEP et à remplir ces blocs par programmation en envoyant des requêtes SQL à la Base de données et en affectant les résultats des requête aux éléments des blocs. Attention, les blocs étant non-basés, les mises à jours réalisées par l'utilisateur ne sont pas prises en compte automatiquement par Forms. Cette solution est moins élégante, mais le code correspondant est instructif. A titre d'illustration, ce code est le suivant : --- Begin default relation declare section -DECLARE recstat VARCHAR2(20) := :System.record_status; -- état du record courant startitm VARCHAR2(61) := :System.cursor_item; -- élément "actif"coutant rel_id Relation; --- Début des déclarations des nouvelles variables pour le remplissage des -- blocs PARTICIPANTS_REC et PARTICIPANTS_DEP CURSOR c_rec is SELECT p.nj, nb_buts, carton_jaune, carton_rouge FROM participants p, joueurs j where p.nj=j.nj and j.ne= :matchs.ne_recevante and p.nm = :matchs.nm ORDER BY p.nj ; CURSOR c_dep is SELECT p.nj, nb_buts, carton_jaune, carton_rouge FROM participants p, joueurs j where p.nj=j.nj and j.ne= :matchs.ne_deplacante and p.nm = :matchs.nm ORDER BY p.nj ; --- Fin des déclarations de variables ajoutées au déclencheur --- End default relation declare section ---- Begin default relation program section -BEGIN IF ( recstat = 'NEW' or recstat = 'INSERT' ) THEN RETURN; END IF; --- Begin EQUIPES detail program section -IF ( (:MATCHS.NE_RECEVANTE is not null) ) THEN rel_id := Find_Relation('MATCHS.MATCHS_EQUIPES'); Query_Master_Details(rel_id, 'EQUIPES'); --- Début du Code à ajouter au déclencheur pour afficher PATCICIPANTS -- Go_Block ('PARTICIPANTS_REC'); Clear_Block; First_Record; P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 37 OPEN c_rec; Fetch c_rec into :participants.nj, :participants_rec.nb_buts, :participants_rec.carton_jaune, :participants_rec.carton_rouge; While c_rec%found Loop Next_Record; Fetch c_rec into :participants_rec.nj, :participants_rec.nb_buts, :participants_rec.carton_jaune, :participants_rec.carton_rouge; End Loop; Close c_rec; --- Fin du Code à ajouter au déclencheur -- END IF; --- End EQUIPES detail program section ---- Begin EQUIPES1 detail program section -IF ( (:MATCHS.NE_DEPLACANTE is not null) ) THEN rel_id := Find_Relation('MATCHS.MATCHS_EQUIPES1'); Query_Master_Details(rel_id, 'EQUIPES1'); --- Début du Code à ajouter au déclencheur pour afficher PATCICIPANTS1 -- Go_Block ('PARTICIPANTS1'); Clear_Block; First_Record; Open c_dep; -- message('ne = '||:matchs.ne_recevante); -- message('nm = '||:matchs.nm); Fetch c_dep into :participants1.nj, :participants_dep.nb_buts, :participants_dep.carton_jaune, :participants_dep.carton_rouge; While c_dep%found Loop Next_Record; Fetch c_dep into :participants_dep.nj, :participants_dep.nb_buts, :participants_dep.carton_jaune, :participants_dep.carton_rouge; End Loop; Close c_dep; --- Fin du Code à ajouter au dévlencheur -- END IF; --- End EQUIPES1 detail program section -IF ( :System.cursor_item <> startitm ) THEN Go_Item(startitm); Check_Package_Failure; END IF; END; --- End default relation program section -- P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 38 4. Exercice : Ajout d'éléments calculés à ce formulaire Modifier le module pour que : 1. le module présente les noms des joueurs comme sur la figure 2, 2. on puisse inscrire de nouveaux joueurs dans chacune des 2 équipes, en utilisant une LOV sur chaque bouton "inscrire". On modifiera la propriété Visible des éléments à masquer et on ajoutera le code suivant à un déclencheur POST-QUERY attaché aux 2 blocs PARTICIPANTS_DEP et PARTICIPANTS_REC -- pour le bloc PARTICIPANTS_DEP select nom into :participants_dep.nomj -– "nomj" présente les noms des joueurs from joueurs where :participants_dep.nj=nj; P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 39 Figure 2 : formulaire permettant de saisir "intelligemment" les participants d'un match TP 7 Compléments 1. Les variables globales Une variable globale est une variable Forms Builder dont la valeur est accessible aux déclencheurs et aux sous-programmes de tout module qui est actif pendant la session en cours. Une variable globale stocke une chaîne d'un maximum de 255 caractères. Les variables globales ne sont pas déclarées formellement comme les variables locales PL/SQL. Vous initialisez une variable globale la première fois que vous lui attribuez une valeur, par exemple : :GLOBAL.my_var := TO_CHAR(:order.total * .85); Pour faire référence à une variable globale, faites précéder son nom du mot GLOBAL et du caractère ":". L'exemple ci-dessous indique une variable globale transmise sous la forme d'un paramètre réel à un appel de procédure : calculate_discount(TO_NUMBER(:GLOBAL.my_var)); Faire référence à une variable globale qui n'a pas été initialisée par une affectation entraîne la génération d'une erreur d'exécution. Pour détruire une variable globale et libérer la place qu'elle occupe en mémoire, utilisez la procédure interne ERASE. : Erase(':GLOBAL.my_var'); 2. Les variables système Pour certains déclencheurs, il est utile de connaître l’état du formulaire. Par exemple, le déclencheur WHEN-NEW-RECORD-INSTANCE est activé à chaque fois que l’on change de ligne dans un bloc. Il peut alors être utile de savoir si l’on est en mode "mise à jour" ou en mode "interrogation". En effet, c’est le seul moyen de savoir si l’on est effectivement en train de créer une nouvelle ligne, pour déclencher une action dans ce cas. On peut ainsi tester l’état du bloc avec la variable système :System.record_status comme suit : DECLARE recstat VARCHAR2(20) := :System.record_status; BEGIN IF ( recstat = 'NEW') THEN -- action à faire en cas de création de n-uplet END IF; END ; La liste les variables système, avec des exemples d’utilisation, peut être consulté via la commande "Aide" de FormsBuilder sous la rubrique : Génération et exécution d’une application Forms Ajout de PL/SQL à une application Forms Utilisation de PL/SQL dans Form Builder A propos de l’utilisation de PL/SQL dans Form Builder P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 40 A propos des variables système de Form Builder Cette liste est la suivante : SYSTEM.BLOCK_STATUS SYSTEM.COORDINATION_OPERATION SYSTEM.CURRENT_BLOCK SYSTEM.BLOCK_STATUS, SYSTEM.COORDINATION_OPERATION, SYSTEM.CURRENT_BLOCK, SYSTEM.CURRENT_DATETIME, SYSTEM.CURRENT_FORM, SYSTEM.CURRENT_ITEM, SYSTEM.CURRENT_VALUE, SYSTEM.CURSOR_BLOCK, SYSTEM.CURSOR_ITEM, SYSTEM.CURSOR_RECORD, SYSTEM.CURSOR_VALUE, SYSTEM.DATE_THRESHOLD*, SYSTEM.EFFECTIVE_DATE*, SYSTEM.EVENT_WINDOW, SYSTEM.FORM_STATUS, SYSTEM.LAST_QUERY, SYSTEM.LAST_RECORD, SYSTEM.MASTER_BLOCK, SYSTEM.MESSAGE_LEVEL*, SYSTEM.MODE, SYSTEM.MOUSE_BUTTON_PRESSED, SYSTEM.MOUSE_BUTTON_SHIFT_STATE, SYSTEM.MOUSE_ITEM, SYSTEM.MOUSE_CANVAS, SYSTEM.MOUSE_X_POS, SYSTEM.MOUSE_Y_POS, SYSTEM.MOUSE_RECORD, SYSTEM.MOUSE_RECORD_OFFSET, SYSTEM.RECORD_STATUS , SYSTEM.SUPPRESS_WORKING*, SYSTEM.TAB_NEW_PAGE, SYSTEM.TAB_PREVIOUS_PAGE , SYSTEM.TRIGGER_BLOCK, SYSTEM.TRIGGER_ITEM , SYSTEM.TRIGGER_RECORD. Les eules variables système auxquelles vous pouvez affecter explicitement des valeurs sont les quatre suivies d'une astérisque (*).Toutes les autres ne peuvent qu'être lues. Exemple : utilisation de la variable système SYSTEM.CURSOR_BLOCK : Supposons que vous vouliez créer, au niveau application Forms, un déclencheur KeyNXTBLK dont la navigation dépend du bloc en cours. Cette fonction est exécutée par le déclencheur décrit ci-dessous, à l'aide de la variable SYSTEM.CURSOR_BLOCK stockée dans une variable locale. DECLARE cour_bloc VARCHAR2(30); BEGIN cour_bloc := :System.Cursor_Block; IF cour_bloc = 'MATCHS' THEN Go_Block('EQUIPES'); ELSIF cour_bloc = 'EQUIPES' THEN Go_Block('PARTICIPANTS'); ELSIF cour_bloc = 'PARTICIPANTS' THEN Go_Block('MATCHS'); END IF; END; 3. Gestion de l’affichage de messages d’erreur Le problème : les messages d’erreur affichés par Oracle en cas d’erreur sur une mise à jour effectuée au travers d’un formulaire. Par exemple, lorsque l’on insère une valeur de clé déjà existante dans une table, oracle affiche le message d’erreur suivant : FRM-40508: Erreur ORACLE : insertion (insert) d’enregistrement impossible Il est possible de changer ce message en utilisant le déclencheur ON-ERROR : Ouvrir le module EQUIPES_JOUEURS. Associer à ce module un déclencheur ON-ERROR. Générer le code suivant pour ce déclencheur : DECLARE code_erreur BEGIN NUMBER := ERROR_CODE; P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 41 IF (code_erreur = 40508) THEN Message ('Duplication de la valeur d''une clé'); -- attention à '' END IF; END; Il est possible d’affiner le message d’erreur en associant des déclencheurs identiques aux blocs. Associer deux déclencheurs aux blocs EQUIPES et JOUEURS. Les messages d’erreur deviennent respectivement : 'Insertion d''un n° d''équipe en double' et 'Insertion d''un n° de joueur en double'. Il n’est pas nécessaire de supprimer le déclencheur du module. En effet, un déclencheur de bloc est prioritaire sur un déclencheur de module, de même qu’un déclencheur d’élément est prioritaire sur un déclencheur de bloc. 4. Utilisation de séquences et des sous-programmes PL/SQL 4.1. Objectif des séquences : éviter les problèmes de saisie sur les clés. Exemple 1 : saisie d’une valeur de clé qui existe déjà. Exemple 2 : deux utilisateurs saisissent une nouvelle valeur de clé en même temps (arrivée d’un nouveau client). Ce deuxième exemple nous ramène au problème de l’exemple 1. Une séquence est un objet de la base de données (comme une table ou une vue), qui permet de générer automatiquement des numéros croissant en évitant les problèmes de concurrence d’accès. Il s’agit d’une table à une ligne et une colonne. On dispose de deux commandes pour manipuler une séquence : - CURRVAL retourne la valeur courante de la séquence, - NEXTVAL génère une nouvelle valeur et la retourne. 4.2. Application : génération automatique du n° d'équipe Appeler le Schema Manager, se positionner sur l’objet "séquences" et cliquer sur le bouton ‘+’. Créer une séquence de nom SEQ_NE et donner la valeur de départ (valeur minimum) pour générer le n° d’équipe. Créer un déclencheur WHEN-BUTTON-PRESSED associé à l’élément NE du bloc EQUIPES, avec le code suivant : DECLARE FUNCTION Numero_suivant RETURN NUMBER IS CURSOR next_seq IS SELECT seq_equipes.NEXTVAL FROM DUAL; tmp NUMBER; BEGIN OPEN next_seq; FETCH next_seq INTO tmp; CLOSE next_seq; RETURN tmp; END; BEGIN :equipes.ne := Numero_suivant; END; P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 42 L’inconvénient de ce déclencheur est que l’utilisateur doit cliquer de lui même sur l’élément pour générer un nouveau n° d’équipe. Pour générer automatiquement un n° d’équipe, deux solutions sont possibles. a) générer un déclencheur WHEN-VALIDATE-ITEM avec le même code, associé à l’élément NE. Attention : ce déclencheur n'est activé que si l'élément a une valeur (même pour tester la contrainte not null) => mettre une valeur par défaut dans la propriété valeur initiale de l'élément (ex : 9999999999). b) générer un déclencheur WHEN-NEW-RECORD-INSTANCE avec le code suivant : DECLARE recstat VARCHAR2(20) := :System.record_status; BEGIN IF ( recstat = 'NEW') THEN :equipes.ne := Nouveau_NE -- action à faire en cas de création de tuple END IF; END ; Remarque : Nouveau_NE est le nom d’une fonction créée au niveau de l’objet "Unités de programmes" du module en cours, qui a le code suivant : FUNCTION Nouveau_NE RETURN NUMBER IS CURSOR next_seq IS SELECT seq_equipes.NEXTVAL FROM DUAL; tmp NUMBER; BEGIN OPEN next_seq; FETCH next_seq INTO tmp; CLOSE next_seq; RETURN tmp; END; 5. A propos des déclencheurs Pre-Query et Post-Query Les déclencheurs Pre-Query et Post-Query vous permettent de contrôler le traitement des interrogations. Ils peuvent être définis au niveau application Forms ou bloc. Dans la plupart des cas, vous les attacherez à des blocs spécifiques pour contrôler les fonctions d'interrogation de ces blocs. Le déclencheur Pre-Query est activé immédiatement avant que FormsRuntime transmette l'instruction SELECT à la base de données, lorsque l'utilisateur a défini l'enregistrement, par exemple en entrant des critères de recherche en mode Saisie interrogation. Dans un déclencheur Pre-Query, l'enregistrement exemple défini par les critères d'interrogation est l'enregistrement en cours, ce qui signifie que votre code de déclencheur peut lire et définir les valeurs des éléments de l'enregistrement exemple en utilisant la syntaxe standard :nom_bloc.nom_élément. Un déclencheur Pre-Query peut être utilisé pour désactiver les conditions d'interrogation qui pourraient ne pas être valides. Lorsqu'une application Forms est en mode Saisie interrogation, la validation normale est suspendue et aucun déclencheur de validation n'est activé, comme il le serait P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 43 en mode Normal. Le déclencheur Pre-Query vous permet donc de vous assurer que les valeurs entrées par l'utilisateur sont des conditions d'interrogation valides. Lorsque l'utilisateur a entré des conditions d'interrogation non valides, vous pouvez interrompre une interrogation en générant l'exception intégrée FORM_TRIGGER_FAILURE dans le déclencheur Pre-Query. Vous pouvez également appeler SET_BLOCK_PROPERTY pour modifier les clauses WHERE et ORDER BY du bloc à partir du déclencheur Pre-Query, pour limiter ou ordonner davantage les enregistrements qui seront extraits par l'interrogation. Rappel Le déclencheur Post-Query est activé après l'exécution de l'interrogation, lorsque FormsRuntime lit des enregistrements dans l'application. Post-Query est activé 1 fois après l'extraction de chaque enregistrement par l'application, et avant leur affichage dans le bloc. P.-J. Charrel & J.-M. Thévenin, Université Toulouse 1, Licence Pro RTICPMO page 44