Oracle Forms 10g release 2 : Demos, Tips and Techniques
1. Introduction
Here
is a collection of Oracle Forms 10g sample dialogs extracted from a complete
Oracle Forms tutorial.
This
tutorial has been written in French and it would be too time consuming to
translate it all in English.
This
is the reason why I have only extracted and translated these sample dialogs.
The
purpose of this article is not to teach how to build a new form from scratch.
It intends to show some of the main advanced features of the product.
To
clearly understand in details how these dialogs work, you will have to open
them in the Forms Builder and examine them thoroughly.
However,
in this article, I am going to explain the best I can the most important
mechanisms of each sample.
These sample have been tested with an Oracle Forms 10g release 10.1.2.0.2 on an Oracle Database 10g Express Edition Release 10.2.0.1.0.
2. Description of the sample dialogs
2.1
Main screen

TUTO_FORMS.FMB
This
dialog is the entry point of the samples. Click on a button to start the
corresponding dialog.
2.2
Canvases

TEST_CANVAS.FMB
This
sample shows three types of canvas :
§
The content canvas (light green)
§
The stacked canvas (white)
§
The tab canvas (dark green)
Stacked
canvas
A stacked canvas is displayed atop—or stacked on—the content canvas assigned to the current window. Stacked canvases obscure some part of the underlying content canvas, and often are shown and hidden programmatically. You can display more than one stacked canvas in a window at the same time
A
stacked canvas allows to scroll a big canvas in a delimited window.
In
this sample, the stacked canvas (CV_EMP_1) is twice bigger than its viewport.
The <Down>
button allows to move the stacked canvas programatically:
Set_View_Property('CV_EMP_1',
VIEWPORT_Y_POS_ON_CANVAS, 140) ;

Here
are the properties of this stacked canvas:
Viewport X position 77
Viewport Y position 11
Viewport
Width 212
Viewport
Height 138
Viewport
X position on canvas 0
Viewport
Y position on canvas 0
Width 212
Height 324
The
second Stacked canvas (CV_EMP_3) demonstrates how to integrate in a Tab canvas:
2.3
Blocks
2.3.1
Block based on a complex view

TEST_BLOC_VUE.FMB
This
sample show how you can base a block on a view that aggregate the columns of
several tables (in this case, DEPT and EMP) and when you can insert, update and
delete from the target tables from this view.
The code
that handles the target table is located in a program unit, called from the
three block-level triggers:
§
ON-INSERT
§
ON-UPDATE
§
ON-LOCK
This
is the code of the ins_upd_emp_dept procedure:
PROCEDURE ins_upd_emp_dept IS
LN$Dummy PLS_INTEGER := 0 ;
BEGIN
-- Table DEPT --
Begin
Select 1
Into LN$Dummy
From DUAL
Where exists(
select deptno from dept where deptno = :EMP_DEPT.DEPTNO ) ;
-- Found ->
update --
Message('Update DEPT table');
UPDATE DEPT
SET
DNAME = :EMP_DEPT.DNAME
WHERE
DEPTNO = :EMP_DEPT.DEPTNO ;
Exception
When no_data_found Then
-- Not found -> insert --
Message('Insert into DEPT table');
INSERT INTO DEPT ( DEPTNO, DNAME )
VALUES ( :EMP_DEPT.DEPTNO, :EMP_DEPT.DNAME
) ;
End ;
-- Table EMP --
Begin
Select 1
Into LN$Dummy
From DUAL
Where exists(
select empno from emp where empno = :EMP_DEPT.EMPNO ) ;
-- Found -> update --
Message('Update EMP table');
UPDATE EMP
SET
ENAME = :EMP_DEPT.ENAME
WHERE
EMPNO = :EMP_DEPT.EMPNO ;
Exception
When no_data_found Then
-- Not found -> insert --
Message('Insert into EMP table');
INSERT INTO EMP ( EMPNO, ENAME )
VALUES ( :EMP_DEPT.EMPNO, :EMP_DEPT.ENAME )
;
End ;
END;
2.3.2
Block based on stored procedures

TEST_BLOC_PROC.FMB
This
sample demonstrates how a block can be based on stored procedures.
This
feature can be implemented in two ways:
§
A REF CURSOR
§
A PL/SQL table
In addition
to that, there are also two different locations where to implement this
functionality:
§
The ON-xxx triggers
§
The transactionnal triggers
In
this sample, the top block uses a REF CURSOR with the ON-xxx triggers
The
bottom block uses a collection with the standard transactionnal triggers.
The
stored procedures are located in the PKG_EMP package shipped with
the scripts.
The Query
Data Source Type is set to Procedure and the Query Data Source
Name indicates the name of the query procedure.
The
insert, update, delete and lock orders are managed in the corresponding On-xxx
triggers:
Example
of ON-INSERT trigger:
DECLARE
LR$Emp
emp_pkg.emp_rec;
BEGIN
LR$Emp.empno := :emp.empno;
LR$Emp.ename
:= :emp.ename;
LR$Emp.job := :emp.job;
LR$Emp.sal := :emp.sal;
LR$Emp.comm := :emp.comm;
emp_pkg.emp_insert(
LR$Emp );
END;
The
variable used as parameter is of type of emp_pkg.emp_rec
TYPE emp_rec
IS RECORD(
empno emp.empno%TYPE,
ename emp.ename%TYPE,
job emp.job%TYPE,
sal emp.sal%TYPE,
comm emp.comm%TYPE);
Then
the emp_pkg.emp_insert() stored procedure:
------------
--
Insert --
------------
PROCEDURE
emp_insert(r IN emp_rec) IS
BEGIN
INSERT INTO
emp (empno, ename, job, sal, comm)
VALUES(r.empno,
r.ename, r.job, r.sal, r.comm);
END
emp_insert;

This
block uses a collection of records with the emp.pkg.emp_query procedure.
TYPE emptab
IS TABLE OF emp_rec INDEX BY BINARY_INTEGER;
PROCEDURE
emp_query(emp_data IN OUT emptab) IS
ii NUMBER;
CURSOR
empselect IS
SELECT
empno, ename, job, sal, comm FROM emp
ORDER BY ename ;
BEGIN
OPEN empselect;
ii := 1;
LOOP
FETCH empselect INTO
emp_data( ii ).empno,
emp_data( ii ).ename,
emp_data( ii ).job,
emp_data( ii ).sal,
emp_data( ii ).comm;
EXIT WHEN
empselect%NOTFOUND;
ii := ii
+ 1;
END LOOP;
END
emp_query;
The
collection of records is an IN OUT parameter, read from the database and
returned to Forms.
The
insert, update,delete and lock orders are also managed by stored procedures.
(see
them in detail in the EMP_PKG package)

Have
also a look at the Query Data Source Column property that manages the
relation between the columns of the collection and the items of the block.

2.3.3
Block based on a relational table that contains a collection

TEST_COLLECTION.FMB
In this
dialog, we can see how to handle a table that contains a nested table
(collection).
There
is no standard buit-in to base a block on a collection, but we will see how
easy it is to work with this sort of object via the ON-xxx triggers.
Here
is the description of the table (ARTICLES)
CREATE TABLE
ARTICLES
( CODE
VARCHAR2(20 BYTE),
LIBELLE VARCHAR2(100 BYTE),
PRIX NUMBER(8,2),
QTETOT NUMBER(8,0),
CASES TAB_TYP_CASE
)
NESTED TABLE
CASES STORE AS CASES_NT
RETURN AS
VALUE;
TAB_TYP_CASE is a table of objects of type : TYP_CASE
create or replace TYPE TYP_CASE AS OBJECT
(
EMP VARCHAR2(10),
QTE NUMBER
)
In this sample, the first block (ARTICLES) displays the standard columns of the ARTICLE table and the second block (detail) displays the columns of its nested table.
Populate
the detail block (nested table)
The
detail block (CASES) is dynamically populated each time a master record change
in a When-New-Record-Instance of the master block:
Declare
LC$Req Varchar2(256) ;
Begin
If
:ARTICLES.CODE Is not null Then
-- Dynamic query of
secondary block --
LC$Req := '(SELECT cases.EMP, cases.QTE FROM
TABLE ( SELECT cases FROM articles WHERE code = ''' || :ARTICLES.CODE || ''')
cases)' ;
Go_Block('CASES' );
Clear_Block ;
Set_Block_Property( 'CASES',
QUERY_DATA_SOURCE_NAME, LC$Req ) ;
-- populate the
block --
Execute_Query ;
Go_Block('ARTICLES') ;
Else
Go_Block('CASES' );
Clear_Block ;
Go_Block('ARTICLES') ;
End if ;
End ;
Because
it is not possible to create a new article with a NULL collection, we have to
handle the insertion into the ARTICLE table in a ON-INSERT trigger:
--------------------------------------------------------------
--
we are doing an explicit insert because the new record --
--
cannot contain a NULL collection --
--------------------------------------------------------------
INSERT INTO ARTICLES
(
CODE,
LIBELLE,
PRIX,
QTETOT,
CASES
)
VALUES
(
:ARTICLES.CODE,
:ARTICLES.LIBELLE,
:ARTICLES.PRIX,
:ARTICLES.QTETOT,
TAB_TYP_CASE() -- insert an empty
collection
)
;
Indeed, if we insert a NULL collection, it will be no longer possible to insert anything in the nested table.
Then after, it is easy to manage the detail records with the ON-xxx triggers of the CASES block:
ON-INSERT:
-- Insert the row in the collection --
INSERT
INTO TABLE
(
SELECT
cases
FROM
articles
WHERE
code = :ARTICLES.CODE
)
Values
(
TYP_CASE( :CASES.EMP, :CASES.QTE )
);
ON-DELETE:
-- Delete row in the collection --
DELETE
FROM TABLE
(
SELECT
cases
FROM
articles
WHERE
code = :ARTICLES.CODE
) cases
WHERE
cases.emp = :CASES.EMP
;
etc.
2.3.4 Block
based on multiple data sources

TEST_DATA_SOURCES.FMB
I
this sample dialog, we can see how to base a block on several tables that share
an identical structure.
(see
the JANVIER, FEVRIER and MARS tables created by the install.sql script)
The
list item is populated with the name of three tables that share the same
structure:

Then,
the source table of the block is changed dynamically in the When-List-Changed
trigger:
If :CTRL.CHOIX is not null Then
:global.choix := :ctrl.choix ;
clear_form ;
:ctrl.choix
:= :global.choix ;
-- change the Query Data Source --
Set_Block_Property('TEST2',
QUERY_DATA_SOURCE_NAME, :global.CHOIX );
go_block('TEST2');
execute_query;
End if ;
2.3.5
Block based on an object table that contains a collection of references

TEST_OBJETS.FMB
Let’s
see how to manage an object table that contains a collection of references.
This
sample is based on the object table (ARTICLE_OBJ) that contains a collection of
references:
create or replace TYPE REF_TYP_EMP AS OBJECT
(
ref_emp REF
TYP_EMP
)
create or replace TYPE TAB_REF_TYP_EMP AS TABLE OF
REF_TYP_EMP
create or replace TYPE TYP_ARTICLES AS OBJECT
(
CODE VARCHAR2 (20),
LIBELLE VARCHAR2 (100),
PRIX NUMBER (8,2),
QTETOT NUMBER (8),
REMP TAB_REF_TYP_EMP -- collection
)
CREATE TABLE
ARTICLES_OBJ OF TYP_ARTICLES
NESTED TABLE
REMP STORE AS REMP_NT
RETURN AS
VALUE;
The
tip is the same that the one used to manage the relational table with nested
table:
- A
when-New-Record-Instance trigger on the
master block to populate the detail block (the collection of references):
Declare
LC$Req Varchar2(256) ;
Begin
If
:ARTICLES.CODE Is not null Then
-- Dynamic query of secondary block --
LC$Req := '(SELECT emp.ref_emp.emp EMP, emp.ref_emp.qte QTE
FROM TABLE( SELECT REMP FROM articles_obj WHERE CODE = ''' || :ARTICLES.CODE || ''') emp
WHERE emp.ref_emp.art = ''' || :ARTICLES.CODE || ''')' ;
Go_Block('CASES'
);
Clear_Block
;
-- change the Query Data Source Name --
Set_Block_Property(
'CASES', QUERY_DATA_SOURCE_NAME, LC$Req ) ;
-- populate the block --
Execute_Query ;
Go_Block('ARTICLES')
;
Else
Go_Block('CASES'
);
Clear_Block
;
Go_Block('ARTICLES')
;
End if ;
End ;
- An
ON-INSERT trigger to insert a new record with an empty collection:
-------------------------------------------------------
--
We are doing an implicit insert because the new --
--
record cannot contain a NULL collection --
-------------------------------------------------------
INSERT INTO ARTICLES_OBJ
VALUES
(
TYP_ARTICLES
(
:ARTICLES.CODE,
:ARTICLES.LIBELLE,
:ARTICLES.PRIX,
:ARTICLES.QTETOT,
TAB_REF_TYP_EMP()
)
) ;
The
collection of references is managed with the corresponding ON-xxx trigger of
the detail block:
ON-INSERT:
-- Insert a row (REF) in the collection --
Declare
LC$Req
Varchar2(256) ;
Begin
LC$Req :=
'INSERT INTO TABLE
( SELECT
remp FROM ARTICLES_OBJ WHERE code = ''' || :ARTICLES.CODE || ''')
VALUES
(
REF_TYP_EMP ( (SELECT REF(a) FROM EMP_OBJ a WHERE a.ART = '''
||
:ARTICLES.CODE || ''' AND a.EMP = ''' || :CASES.EMP || ''') ) )' ;
Forms_Ddl( LC$R