Vous êtes sur la page 1sur 22

CraigsOracleStuff

Welcome to Craig's Oracle


Stuff
Oracle Forms - How to's
Forms: Forms Compiler
command line
parameters
Forms: How to Base a
block on a FROM Clause
Query
Forms: How to Base a
data block on a
procedure using the
Advanced Database
options (ADo)
Forms: How to base a
Data Block on a table join
Forms: How to Check for
Duplicates using a
Record Group

Search this site

Oracle Forms - How to's


>

Forms:HowToCreateaHierachicalTreeform
Oracle Forms Hierarchical Tree widget is one of the more complex widgets available in Oracle Forms. The key to using this widget is to
understand how to populate it with data. A Hierarchical Tree (HT) can be thought of as a Master and Detail or Parent and Child(ren)
relationship.
An HT is made up of a root node, parent node and children node(s). Children nodes can be parents themselves. Think of it from the
analogy of a Parent, Child and Grandchild. As of the date of this publication, I am not aware of any limitations on the number of node
depths you can go with Oracle HT.
This How To document will use the sample HR.DEPARTMENTS and HR.EMPLOYEES tables as the source tables for the tree.
Each value in a tree is assigned a unique node_id. Node_IDs are assigned sequentially, in that as a value is added to the tree, it is assigned
the next available ID. For example; If I load all of the rows from the HR.DEPARTMENTS table into a tree, the rows would be assigned as
follows:

Forms: How to Configure


Forms Builder to run
forms locally

DEPARTMENT_NAME

NODE_ID

PARENT_NODE_ID

Forms: How to Create a


Context (Popup) Menu

Accounting

Administration

Benefits

Construction

Forms: How To Create a


Hierachical Tree form
Forms: How to Create a
Multiline Text Counter
Forms: How to Create an
Object List Report from
the command line
Forms: How to
Dynamically Populate a
Poplist

Etc..

Forms: How to Select


Multiple Records in a
block using Record
Groups

Now, when we add the employees in each department to the tree watch how NODE_IDs change.

Forms: How to use a


calendar in a Form
Oracle Forms WebUtil
1-WebUtil: A simplified
guide to understanding
WebUtil

DEPARTMENT_NAME

LAST_NAME

Accounting

NODE_ID

PARENT_NODE_ID

2: Configure WebUtil for


Forms 10g R2

Higgins

WebUtil: How to Read an


Excel file into an Oracle
Form

Gietz

King

Kochhar

De Haan

10

PL/SQL
PL/SQL: Send Email from
the database

Administration
Whalen

Sitemap

Executive

Finance

Notice how the Node_ID is incremented and how the children are linked to the parent through the PARENT_NODE_ID.
All tree nodes must have a unique Parent and Child Node ID combination.

Now that you have a basic understanding of how the data loads into the tree, lets build a form that uses a hierarchical tree. The following
demo form is based on the sample Schemas included with a standard installation of an Oracle database and uses the HR Schema and the
DEPARTMENTS and EMPLOYEES tables mentioned previously. This demo form will demonstrate the following concepts:
Populate the tree
Programmatically expand and collapse a single tree node
Programmatically expand and collapse all tree nodes.
How to search for a value in the tree and how to search for subsequent instances of a specified search value.
How to select a value from a tree node and use this to filter records in a different data block.
Assumptions made by this document:
1. The attached sample form was built using Oracle Forms 10g R2 (10.1.2.0.2).
2. The concepts and sample code are Forms version generic and should work in Forms 6i and higher. You might have to make minor
modifications to made it work with Forms 6i as I have not specifically created a Forms 6i version of this demo, but I have confirmed
all of the Forms Built-ins used are present in Forms 6i.
3. That you have a basic understanding of Oracle Forms and have built and successfully run at least one form from the Oracle Forms
development tool (Forms Builder).
4. With respects to item 3, your Oracle Forms installation is configured properly and you are able to run a form locally from the Forms
Builder.
5. All steps are listed from the perspective of the Property Palette, in that I list all of the properties of each object rather than instruct
you to draw an item on a canvas and make is so large, etc.
6. I have created and provided a Forms Program Unit (package) that simplifies using a hierarchical tree widget. You may use, modify or
extending it to suite your needs. Ive tried to encapsulate as much as possible into this package to simplify using hierarchical trees
as much as possible. The methods I use in this sample use package variables instead of Global or Control block items as they are
more efficient and offer greater flexibility. It is assumed that you are familiar with the use of Package variables.

LetsGetStarted
Start by opening the Oracle Forms builder, create a new Forms module and name it HIERACHY_TREE. Once you have a new forms module
created and saved; complete the following steps:
1)Create a new Program Unit, name it TREE_CONTROL and select Package Spec as the Program Unit Type.
a)Add the following code to the package specification:
PACKAGEtree_controlIS
PackageVariables
TheseVariablesareusedbysupportingTriggersand

packagemethods.

TheBlockandItemNameoftheTreeControlwidget
v_item_nameVARCHAR2(61)
Thecurrentandpreviouslyselectednode'sparentnodeID
n_curr_parent_nodeFtree.NODE
n_prev_parent_nodeFtree.NODE
Thecurrentandpreviouslyselectnode'sID
n_curr_nodeFtree.NODE:=0
n_prev_nodeFtree.NODE
Thecurrentandpreviouslyselectednode'slabel
v_curr_labelVARCHAR2(60)
v_prev_labelVARCHAR2(60)
Thecurrnetandpreviouslyselectednode'svalue
v_curr_valueVARCHAR2(256)
v_prev_valueVARCHAR2(256)
FUNCTIONCreate_RG(p_rg_nameVARCHAR2DEFAULT'TREE_GROUP')
RETURNRecordGroup
PROCEDUREADD_RG_ROW(rg_idRECORDGROUP,
n_stateNUMBER,
n_rowNUMBER,
n_levelNUMBER,
v_labelVARCHAR2,
v_iconVARCHAR2,
v_dataVARCHAR2)
PROCEDUREEXPAND_ALL(p_treeVARCHAR2)
PROCEDUREEXPAND_ONE(p_treeVARCHAR2)
PROCEDURECOLLAPSE_ALL(p_treeVARCHAR2)
PROCEDURECOLLAPSE_ONE(p_treeVARCHAR2)
PROCEDURENode_Selected
PROCEDURENode_Activated
PROCEDUREFind_Node(p_valueVARCHAR2,
p_start_nodeNUMBERDEFAULT0)
PROCEDURECASE_SENS_FIND_NODE(p_valueVARCHAR2,
p_start_nodeNUMBERDEFAULT0)

END

2)Create a new Program Unit, name it TREE_CONTROL and select Package Body as the program unit type.
a)Add the following code to the Package Body:
PACKAGEBODYtree_controlIS
Create_RG
CreatesanInstanceofaRecordGroup

FUNCTIONCreate_RG(p_rg_nameVARCHAR2DEFAULT'TREE_GROUP')RETURN
RecordGroupIS
rg_idRecordGroup
gc_idGroupColumn
BEGIN
rg_id:=Find_Group(p_rg_name)

MakesuretheRGdoesn'talreadyexist.
IF(NOTId_NULL(rg_id))THEN
Delete_Group(rg_id)
ENDIF
IF(Id_NULL(rg_id))THEN
rg_id:=Create_Group(p_rg_name)
Initialstate.
gc_id:=Add_Group_Column(rg_id,'state',NUMBER_COLUMN)
AddNodeTreeDept
gc_id:=Add_Group_Column(rg_id,'dept',NUMBER_COLUMN)
Label
gc_id:=Add_Group_Column(rg_id,'label',CHAR_COLUMN,50)
Icon
gc_id:=Add_Group_Column(rg_id,'icon',CHAR_COLUMN,10)
Data

gc_id:=Add_Group_Column(rg_id,'data',CHAR_COLUMN,50)
ENDIF
RETURNrg_id
ENDCreate_RG
ADD_RECORD
AddsarecordtotheRecordGroup

PROCEDUREADD_RG_ROW(rg_idRECORDGROUP,
n_stateNUMBER,
n_rowNUMBER,
n_levelNUMBER,
v_labelVARCHAR2,
v_iconVARCHAR2,
v_dataVARCHAR2)
AS
BEGIN
Add_Group_Row(rg_id,END_OF_GROUP)
Set_Group_Number_Cell('Tree_Group.state',n_row,n_state)
Set_Group_Number_Cell('Tree_Group.dept',n_row,n_level)
Set_Group_Char_Cell('Tree_Group.label',n_row,v_label)
Set_Group_Char_Cell('Tree_Group.icon',n_row,v_icon)
Set_Group_Char_Cell('Tree_Group.data',n_row,v_data)
ENDADD_RG_ROW
EXPAND_ALL
StartingfromtheROOTnode,thisprocedurewill
Expandallnodesofatree.
PARAMETERS:
p_treeNameoftheTreeControlobject.

PROCEDUREexpand_all(p_treeVARCHAR2)IS
n_nodeftree.NODE
i_treeITEM
v_stateVARCHAR2(30)
BEGIN
FindtheTreeItem
i_tree:=Find_Item(p_tree)
FindtheROOTNodeofthetree
n_node:=Ftree.Find_Tree_Node(i_tree,'')

LOOPthroughallofthenodesandexpandeachone
ifitiscollapsed
<<expand_tree>>
WHILENOTFtree.ID_NULL(n_node)LOOP
v_state:=Ftree.Get_Tree_Node_Property(i_tree,n_node,
Ftree.NODE_STATE)
IF(v_state=Ftree.COLLAPSED_NODE)THEN
Ftree.Set_Tree_Node_Property(i_tree,n_node,
Ftree.NODE_STATE,Ftree.EXPANDED_NODE)
ENDIF
n_node:=Ftree.Find_Tree_Node(i_tree,'',Ftree.Find_Next,
Ftree.NODE_LABEL,'',n_node)
ENDLOOPexpand_tree
ENDexpand_all
EXPAND_ONE
StartingfromtheROOTnode,thisprocedurewill
Expandallnodesofatree.
PARAMETERS:
p_treeNameoftheTreeControlobject.

PROCEDUREEXPAND_ONE(p_treeVARCHAR2)IS
v_stateVARCHAR2(30)
i_treeITEM
BEGIN
FindtheTreeItem
i_tree:=Find_Item(p_tree)
v_state:=Ftree.Get_Tree_Node_Property(i_tree,n_curr_node,
Ftree.NODE_STATE)
IF(v_state=Ftree.COLLAPSED_NODE)THEN
Ftree.Set_Tree_Node_Property(i_tree,n_curr_node,
Ftree.NODE_STATE,Ftree.EXPANDED_NODE)
ENDIF
ENDEXPAND_ONE
COLLAPSE_ALL

StartingfromtheROOTnode,thisprocedurewill
Collapseallnodesofatree.
PARAMETERS:
p_treeNameoftheTreeControlobject.

PROCEDUREcollapse_all(p_treeVARCHAR2)IS
n_nodeFtree.NODE
i_treeITEM
v_stateVARCHAR2(30)
BEGIN
FindtheTreeItem
i_tree:=Find_Item(p_tree)
Findtherootnode
n_node:=Ftree.Find_Tree_Node(i_Tree,'')
Loopthroughallofthenodesandcollapseifexpanded.
<<collapse_tree>>
WHILE(NOTFtree.ID_NULL(n_node))LOOP
v_state:=Ftree.Get_Tree_Node_Property(i_tree,n_node,
Ftree.NODE_STATE)
IF(v_state=Ftree.EXPANDED_NODE)THEN
Ftree.Set_Tree_Node_Property(i_tree,n_node,
Ftree.NODE_STATE,Ftree.COLLAPSED_NODE)
ENDIF
n_node:=Ftree.Find_Tree_Node(i_tree,'',Ftree.FIND_NEXT,
Ftree.NODE_LABEL,'',n_node)
ENDLOOPcollapse_tree
ENDcollapse_all
COLLAPSE_ONE
StartingfromtheROOTnode,thisprocedurewill
Expandallnodesofatree.
PARAMETERS:
p_treeNameoftheTreeControlobject.

PROCEDURECOLLAPSE_ONE(p_treeVARCHAR2)IS
v_stateVARCHAR2(30)
i_treeITEM

BEGIN
FindtheTreeItem
i_tree:=Find_Item(p_tree)
v_state:=Ftree.Get_Tree_Node_Property(i_tree,n_curr_node,
Ftree.NODE_STATE)
IF(v_state=Ftree.EXPANDED_NODE)THEN
Ftree.Set_Tree_Node_Property(i_tree,n_curr_node,
Ftree.NODE_STATE,Ftree.COLLAPSED_NODE)
ENDIF
ENDCOLLAPSE_ONE
NODE_SELECTED
UsedinWhenTreeNodeSelectedtriggertocapture
theCurrentNode,CurrentParentNode,CurrentNodeLabel,
PrevNode,PrevParentNode,andPrevNodeLabel.

PROCEDURENode_SelectedIS
BEGIN
IF(:SYSTEM.Trigger_Node_SELECTED='TRUE')THEN
n_curr_node:=:SYSTEM.Trigger_Node
n_curr_parent_node:=Ftree.Get_Tree_Node_Parent(
v_item_name,:SYSTEM.Trigger_Node)
v_curr_label:=Ftree.Get_Tree_Node_Property(
v_item_name,:SYSTEM.Trigger_Node,
Ftree.NODE_LABEL)
ELSE
n_prev_node:=:SYSTEM.Trigger_Node
n_prev_parent_node:=Ftree.Get_Tree_Node_Parent(
v_item_name,:SYSTEM.Trigger_Node)
v_prev_label:=Tree_Control.v_curr_label
ENDIF
ENDNode_Selected
NODE_ACTIVATED
UsedintheWhenTreeNodeActivatedtrigger
tocapturetheNODE_VALUEoftheselectednode.

PROCEDURENode_ActivatedIS
BEGIN

IF(v_curr_valueISNOTNULL)THEN
v_prev_value:=v_curr_value
ENDIF
v_curr_value:=Ftree.Get_Tree_Node_Property(
v_item_name,n_curr_node,Ftree.NODE_VALUE)
ENDNode_Activated
FIND_NODE
CalledbyaUserdefinedbuttontosearchthe
treeforavalue.Ifavalueisinthetreemorethanonce,
callingtheFIND_NODEasubsequenttimewillfindthenext
occuranceofthevaluespecifiedinP_VALUE.
Parameters:
p_valuethevalueyouaresearchingfor
p_start_nodedefaultsto0(zero).Ifspecified,
thesearchwillbeginatthisnode
location,elseitstartsattheROOTnode.

PROCEDUREfind_node(p_valueVARCHAR2,
p_start_nodeNUMBERDEFAULT0)IS
htreeITEM
found_nodeFTREE.node
BEGIN
htree:=Find_Item(Tree_Control.v_item_name)
IF(p_start_node=0)THEN
found_node:=Ftree.Find_Tree_Node(htree
,p_value
,Ftree.FIND_NEXT
,Ftree.NODE_LABEL
,Ftree.ROOT_NODE
,Ftree.ROOT_NODE)
ELSE
found_node:=Ftree.Find_Tree_Node(htree
,p_value
,Ftree.FIND_NEXT
,Ftree.NODE_LABEL
,Ftree.ROOT_NODE
,p_start_node)
ENDIF

IF(NOTFtree.ID_NULL(found_node))THEN
Clear_Message
Message('Value:"'||p_value||'"found')
n_curr_node:=found_node
Ftree.Set_Tree_Selection(htree,found_node,
Ftree.SELECT_ON)
ELSE
Clear_Message
Message('Value:"'||p_value||
'"notfound.Pleasetryagain.')
ENDIF
EXCEPTION
WHENOTHERSTHEN
Clear_Message
Message('Value:"'||p_value||
'"notfound.Pleasetryagain.')
Message('')
ENDFind_Node
END

3)Now that the TREE_CONTROL package has been created we need a tree widget to manipulate.
a)First we need to create a Canvas object to assign our tree widget too
i)Right-click on the Canvas and open the Property Palette and set the following properties:
(1)Name: TREE
(2)Canvas Type: Content (should be the default value)
(3)Window: WINDOW1 (should automatically be created with the new Forms Module)
(4)Width: 540
(5)Height: 324
b)Right-Click on the TREE Canvas in the Object Navigator and select the Layout Editor
i)Click on the Hierarchical Tree icon in the Layout Toolbar and then draw a tree widget anywhere on the TREE canvas. This
will create a data block in addition to the tree item.
ii)Open the property palette for the tree item and set the following properties:
(1)Name: MY_TREE

(2)Canvas: Tree (should already be set)


(3)X Position: 15
(4)Y Position: 23
(5)Width: 205
(6)Height: 278
iii)Now, open the property palette for the block that was created and set the following properties:
(1)Name: MY_TREE_DATA
(2)Database Data Block: No
4)Now it is time to build a Record Group and use this to populate the Tree.
a)Create a Form level When-New-Form-Instance trigger and add the following code:
DECLARE
CURSORc_deptIS
SELECT*
FROMhr.departments
CURSORc_emp(n_deptNUMBER)IS
SELECT*
FROMhr.employees
WHEREdepartment_id=n_dept
n_levelNUMBER:=1
n_rowNUMBER:=1
rg_idRecordGroup
nodeFtree.Node
BEGIN
Tree_Control.v_item_name:='MY_TREE_DATA.MY_TREE'
rg_id:=Tree_Control.Create_RG
<<parent>>
FORr_deptINc_deptLOOP
AddParentDepartment_ID
Tree_Control.ADD_RG_ROW(rg_id,ftree.collapsed_node,n_row,
n_level,r_dept.department_name,null,
r_dept.department_id)
n_row:=n_row+1
n_level:=n_level+1

AddChildEmployeeRecords
<<child>>
FORr_empINc_emp(r_dept.department_id)LOOP
Tree_Control.ADD_RG_ROW(rg_id,ftree.collapsed_node,
n_row,n_level,r_emp.last_name,
NULL,r_emp.employee_id)
n_row:=n_row+1
ENDLOOPchild
n_level:=n_level1
ENDLOOPparent
ftree.Add_Tree_Data(Tree_Control.v_item_name,ftree.ROOT_NODE,
ftree.PARENT_OFFSET,ftree.LAST_CHILD,
ftree.RECORD_GROUP,rg_id)
END
5)At this point you should be able to run your form to confirm the Tree is populated. Your form should look similar to the following:

6)Now, lets add the code to expand and collapse all tree nodes.
a)Create a new data block and set the following properties:
i)Name: CONTROL
ii)Database Data Block: No
b)Add two new items to the CONTROL block. These items are going to be the Expand All and Collapse All buttons.
c)Select the first item and set the following properties in the property palette:
i)Name: BTN_EXPAND_ALL
ii)Item Type: Push Button
iii)Label: >>
iv)Canvas: TREE
v)X Position: 2
vi)Y Position: 4
vii)Width: 18
viii)Height: 18

ix)Tooltip: Expand All Nodes


d)Select the second item added and set the following properties in the property palette:
i)Name: BTN_COLLAPSE_ALL
ii)Item Type: Push Button
iii)Label: <<
iv)Canvas: TREE
v)X Position: 214
vi)Y Position: 4
vii)Width: 18
viii)Height: 18
ix)Tooltip: Collapse All Nodes
e)Select the BTN_EXPAND_ALL item and add a When-Button-Pressed trigger with the following code:

Tree_Control.Expand_all(Tree_Control.v_item_name)

f)Select the BTN_COLLAPSE_ALL item and add a When-Button-Pressed trigger with the following code:

Tree_Control.Collapse_all(Tree_Control.v_item_name)

g)Let run the form and test your code. Your form should look similar to the following:

h)Now we will add the Expand One and Collapse One buttons.
i)Add two more items between BTN_EXPAND_ALL and BTN_COLLASPE_ALL buttons in the CONTROL block.
j)Select the new item that follows the BTN_EXPAND_ALL button and set the following properties in the property palette:
i)Name: BTN_EXPAND_ONE
ii)Item Type: Push Button
iii)Label: >
iv)Canvas: TREE
v)X Position: 21
vi)Y Position: 4
vii)Width: 18
viii)Height: 18
ix)Tooltip: Expand selected node
x)Add a When-Button Pressed trigger with the following code:
Tree_Control.EXPAND_ONE(Tree_Control.v_item_name)

k)Select the next newly added item and set the following properties in the property palette:
i)Name: BTN_COLLAPSE_ONE
ii)Item Type: Push Button
iii)Label: <
iv)Canvas: TREE
v)X Position: 195
vi)Y Position: 4
vii)Width: 18
viii)Height: 18

ix)Tooltip: Collapse selected node


x)Add a When-Button Pressed trigger with the following code:
Tree_Control.COLLAPSE_ONE(Tree_Control.v_item_name)

l)From the Object Navigator, select the MY_TREE_DATA block and expand the MY_TREE item and add the following triggers and
code:
i)When-Tree-Node-Selected
Tree_Control.Node_Selected

ii)When-Tree-Node-Activated
Tree_Control.Node_Activated

m)Run the form and lets test what weve added. Select a node of the tree and click the Expand One and Collapse One buttons.
Your form should look similar to the following:

n)Now, lets add the ability to search the tree. Note; Values in the tree are case sensitive with respects to how the data is stored
in the table.

o)Click on the CONTROL.BTN_EXPAND_ONE item and add two new items.


p)Click on the first new item and set the following properties:
i)Name: FIND_NAME
ii)Item Type: Text Item (default value)
iii)Maximum Length: 60
iv)Canvas: TREE
v)X Position: 43
vi)Y Position: 6
vii)Width: 85
viii)Height: 14
ix)Tooltip: Enter a name to search for
q)Select the next new item and set the following properties:
i)Name: BTN_FIND
ii)Item Type: Push Button
iii)Label: Find
iv)Access Key: d
v)Canvas: TREE
vi)X Position: 129
vii)Y Position: 4
viii)Width: 30
ix)Height: 18
x)Tooltip: Click to search for the given name
r)Add a When-Button-Pressed trigger with the following code:

FindfromCurrentselectednode.
Tree_Control.find_node(:CONTROL.Find_Name,Tree_Control.n_curr_node)

Note: the comment in the code. The find function always starts at the currently select tree node. This enables the ability to search
and find more than first instance of a given value.

s)Again, lets run the form to test our changes.

t)In the search field, enter Smith and click the find button.
u)Your form should look similar to the following:

7)Now lets add the final code to select a tree node value and perform an action based on the value. In this example, we will query
the Departments and Employees tables.
a)Create two new data blocks using the Data Block Wizard for the HR.DEPARTMENTS and HR.EMPLOYEES tables.
i)For the Departments table, only display the DEPARTMENT_ID and DEPARTMENT_NAME columns.
ii)Allow the data block wizard to call the Layout Wizard and use a Form layout versus a Tabular layout.
iii)For the EMPLOYEES table, select all columns to be displayed.

iv)Allow the data block wizard to call the Layout Wizard and use a Form layout versus a Tabular layout.
v)Select the MY_TREE_DATA block, MY_TREE item and add the following triggers and code:
(1)When-Tree-Node-Activated
BEGIN
Tree_Control.Node_Activated
IF(Tree_Control.n_curr_parent_node=0)THENDeptSelected
Set_Block_Property('DEPARTMENTS',ONETIME_WHERE,
'DEPARTMENT_ID='||TO_NUMBER(Tree_Control.v_curr_value))
Go_Block('DEPARTMENTS')
ELSEEmployeeselected.
Set_Block_Property('EMPLOYEES',ONETIME_WHERE,
'EMPLOYEE_ID='||
TO_NUMBER(Tree_Control.v_curr_value))
Go_Block('EMPLOYEES')
ENDIF
Execute_Query
Go_Item('MY_TREE_DATA.MY_TREE')
END

(2)When-Tree-Node-Selected
Tree_Control.Node_Selected

vi)Now, lets run the form and validate everything works correctly. Your form should look similar to the following:

Terjemahkan

vii)Double-clicking the Purchasing node will populate the Department region. Double-clicking the user Raphaely will populate
the Employee region.
This ends this Forms Tree widget demonstration. This demonstration utilizes the many of the functions available to a Tree widget and
should sufficiently familiarize you with the use of a tree widget. There are many other Forms Built-in available for the Tree widget. You
should now be familiar enough with the use of a Forms Tree widget that you can figure out how to use the remaining Tree built-ins.
If you find a bug in this code or have any suggestions, please let me know.
I hope you find this demonstration helpful.
Craig J. Butts
Oracle Application Developer
Web: http://sites.google.com/site/craigsoraclestuff/
Email:

Hierachy_Tree.zip (153k)

Craig Butts, Nov 22, 2010, 8:21 AM

Komentar
Andatidakmemilikiizinuntukmenambahkankomentar.

Masuk | Aktivitas Situs Terbaru | Laporkan Penyalahgunaan | Cetak Laman | Diberdayakan oleh Google Sites

v.4

Vous aimerez peut-être aussi