【IT168 技术文档】
ORACLE ERP开发基础之前言http://tech.it168.com/a2009/0427/274/000000274048.shtml
ORACLE ERP开发基础之FORMS基础-- Forms设置部分 http://tech.it168.com/a2009/0428/274/000000274178.shtml
FORMS变量类型
FORMS有提供多种变量,提供给PL/SQL使用,不同的变量,生存周期是不一致的。
1.项变量:只能在当前的确表单内引用。
语法::blockname.itemname
2.全局变量:只能存储字符型数据,可以在当前会话的所有表单内引用。
语法::global.name
例:可以when-new-form-instance中声明一个全局变量
Global.name:=‘this is a global‘;
然后在when-button-pressed中引用
Message(:Global.name);
3.参数(Parameter):在Forms中直接创建一个参数。
语法::Parameter.parameter1 := ?test‘;
另外:在EBS中,可以通过在注册功能时,给Parameter赋值。
4. 系统变量:这个是FORMS预定义的,直接使用即可
例::SYSTEM.CURRENT_DATETIME
判断数据块的项是否为空
IF :BlockName.Item1 is null THEN
message(?Item1为空‘);
END IF;
这个和一般的编程语言使用字符串比较很不一致,但从PL/SQL的语句来看,却也是正常的。
信息提示框之基本用法
1、message Usage:message('提示信息');
Effect:在FORMS左下角会出现这个提示信息。
2、fnd_message用法大全 2.1 FND_Message.Debug
Usage::fnd_message.debug('提示信息');
Effect:会直接弹出一个对话框,与vb中的msgbox("提示信息")类似。
2.2、fnd_message.question .
Usage: -----------------------------------------------------------------------------
declare
v_num number;
begin
FND_MESSAGE.SET_STRING('确要执行此操作吗?');
v_num := FND_MESSAGE.QUESTION('否', '是',NULL, 1,2); i
f v_num=2 then fnd_message.debug('选择了是');
elsif v_num=1 then fnd_message.debug('选择了否');
end if;
end;
Effect: Effect:会直接弹出一个选择框,与vb中的msgbox("提示信息",vbokcancel)类似。
2.3、FND_MESSAGE.show 这是一个Procedure,把信息以最基本的方式显示给用户,和FND_MESSAGE.DEBUG一样的效果。但分成了两步来写。
begin
fnd_message.set_string('show a string!);
fnd_message.show;
end;
2.4、FND_MESSAGE.hint 这是一个Procedure这种方式不会弹出对话框给用户,而是显示在左下脚的状态栏上面。
2.5、FND_MESSAGE.error 这是一个Procedure以Error信息的方式显示给用户
用代码控制ITEM属性
1、用代码控制ITEM的可用性。
1.1、SET_ITEM_PROPERTY和SET_ITEM_INSTANCE_PROPERTY:
如果是控制单行记录或者多行记录中的全部记录:SET_ITEM_PROPERTY
如果是控制多行记录中的单一行记录:SET_ITEM_INSTANCE_PROPERTY
1.2、理解它们的最好区别就是亲自动手写一例子。
例: ①控制数据块T_TEST(多条记录)的ITEM的某一条记录是否可更改。
SET_ITEM_INSTANCE_PROPERTY(?T_TEST.TID‘, CURRENT_RECORD,UPDATE_ALLOWED,PROPERTY_FALSE);
SET_ITEM_INSTANCE_PROPERTY('T_TEST.TID', CURRENT_RECORD,INSERT_ALLOWED,PROPERTY_FALSE);
Effect:
③ 控制数据块T_TEST(多条记录)的单个ITEM的全部记录是否可更改。
Effect:
③控制数据块某个ITEM只允许insert,不允许delete.
在when-new-form-instance中加入
--先将数据块设为不可删除
set_block_property('T_TEST ',delete_allowed,property_false);
--然后对ITEM设为不可update set_item_property(' T_TEST .TID',
update_allowed,property_false);
2、用代码控制ITEM的可见性
SET_ITEM_PROPERTY('CONTROL.ITEM1', ENABLED, PROPERTY_FALSE);
在when-new-record-instance控制BLOCK的可用状态
这个触发器很好用,例如:可以根据主块的某个项的值,来控制子块是否可操作。
Begin
If :blockname.test = ?Y‘ then
set_block_property('blockname',DELETE_ALLOWED,PROPERTY_FALSE);
set_block_property('blockname',INSERT_ALLOWED,PROPERTY_FALSE);
set_block_property('blockname',UPDATE_ALLOWED,PROPERTY_FALSE);
end if;
end;
实现将LOV可以自行录入内容
在WHEN-NEW-ITEM-INSTANCE触发器加入
begin set_item_property('block.item1',
VALIDATE_FROM_LIST, property_false);
end;
在FORMS调用WEB页面
web.show_document('http://www.sina.com.cn','_blank'); 第二参数为页面的加载方式,有四种选择。
_SELF
_PARENT
_TOP
_BLANK
Effect:自己可以去show一下,印象会比较深。
Name_In()\COPY()函数
1、Name_In()函数有点奇怪,丢进去是字符串,返回也是字符串。Oracle官方文档说是为了实现indirect reference。 例: IF :emp.ename = 'smith' -- direct reference
IF NAME_IN('emp.ename') = 'smith' -- indirect reference
另外:Name_In()可以用来间接引用一些系统函数。例:var t_form varchar2(50):=Name_In(?system.current_form‘);
2、Copy()函数呢?也是一样,也是为了实现indirect reference。 例:
:emp.ename := 'smith'; -- direct reference
Copy('smith','emp.ename'); -- indirect reference
3、后来在ITPUB上面看到,说此两个函数是为了实现类似于C++中宏定义。
3.1 NAME_IN实现宏定义的例子
FUNCTION FUN_JF (PRE_BLOCK_NAME
IN VARCHAR) RETURN BOOLEAN IS
BEGIN
if name_in(pre_block_name'sj.dwjtc')>name_in(pre_block_name'yj.dwjtc') then
message(' 测试!');
return false;
end if;
return true;
END;
-- PRE_BLOCK_NAME是形参,实际调用时,通过Name_in()函数来实现变量宏替换成FORMS.ITEM。
3.2 COPY实现宏定义的例子:
BEGIN
DELCATE
strItemName varchar2(20);
BEGIN
for I in 1 „6 loop
strItemName:=':b.l"to_char(i);
copy(null,strItemName); --strItemName由变量宏替换成Forms中Item。
end loop;
END;
4、通过上面两个例子,应该可以明白indirect reference与direct reference的区别。但我个人觉得C++的宏定义很少这样作用,一般都是起常量的作用。例:#difned BUFFER 100。而且ANSI C++推荐使用const来代替宏定义。
Forms数据提交的方式
1、commit_form
先针对form上面的数据变动进行commit,然后对于代码中的类似DML语句也进行提交; 如果form上面的数据变动和代码中的数据变动有冲突,最后以FORM上的为准。 适用:一般来在直接修改Form上的数据时,就使用commit_form。
2、commit
对form和数据库进行提交。如果form上面的数据和代码中的数据变动有冲突,最后以FORM上的为准。 适用:一般来在直接使用DML代码修改数据时,就使用commit。
3、do_key('commit_form')
会首先寻找form下的triggers中的KEY-COMMIT这个trigger,并执行KEY-COMMIT中所写的代码。 如果没有KEY-COMMIT这个trigger,则会针对form和代码一起提交。 如果form上面的数据变动和代码中的数据变动有冲突,最后以界面上的为准。 适用:只是更改了一下代码执行的任先顺序,没有什么实质区别。
4、forms_ddl('commit')
只针对代码中的update,insert,delete语句进行提交。如果form上面的数据有变动,是不会提交的。 适用:一般不用。
在一个FORM中调用不同的WINDOW
1、基础概念:
① 一个FORM是可以包含多个WINDOW,一个WINDOW可以切换不同的CANVAS。
②对于包含有多个WINDOW的FORM,FORM的“第一个导航块”属性,决定了首先打开的WINDOW。
③另外在EBS开发中,template模板中FORM-LEVEL的PRE-FORM触发器,会有如下代码:
app_standard.event('PRE-FORM');
app_window.set_window_position('MAIN_W', 'FIRST_WINDOW');
上述代码也会决定,加载FORM时首先要打开的WINDOW
2、FORM调用WINDOW的例子: 对于包含有多个WINDOW的FORM,在已打开的WINDOW上,通过按钮打开另一个WINDOW。
BEGIN
SHOW_WINDOW('Test_W',2,2);
SET_WINDOW_PROPERTY('Test_W',TITLE,'标题');
END;
Effect:
3.错误: 关闭窗口时出现以下提示
解决:在该画布上必须要有一个可以导航的item。该ITEM必须满足以下其中一个条件:
1.该item是可用的,并且该ITEM所属的块必须是数据块。
2.该item是失效的,即enabed为property_false。
Form假死锁问题之初步解决
1、错误提示:
2、原理知识:
2.1当我们在Forms中,试图更改block中数据的时候,Forms先发出一个对该行数据的select ... for update nowait查询,希望锁定该行(该锁是ORACLE行级X锁)。如果不能锁定,Forms提示Could not reserve records (2 trys). Keep trying?。如果用户选择No,Forms报告FRM-40510错误:ORACLE error: unable to reserve record for update or delete。
2.2 block加锁的模式。
block的锁定模式如果为automatic或immediate,会在修改记录时Forms会立即锁定数据库记录;如果设为delayed,在保存时Forms才尝试锁定记录。也就是说这个属性不管如何设置,都不会影响锁的模式,有影响的只是什么时候加锁而已。
2.4上面已经说过了,Forms在更数据行时,会先对该数据行加行级X锁。这个是Oracle写死在Forms中的,我们无法手工修改。你想不加锁也不行!Oracle够野蛮吧!^_^。不要拿Oracle推荐的transaction来压我,要明白这个世界上,不是全部的业务应用都需要严格的transaction。
2.5即使前面的查询锁定步骤成功,Forms还要比较查询结果和当前行的值是否一致。如果两者不完全相同,Forms抛出FRM-40654(记录已经被另一个用户更新,重新查询以查看修改)错误。这么做主要是为了防止lost update的情形,不让用户根据过时的数据来做出修改。这一点请认真理解哦?
3、解决方法:
3.1避免在TRIGGER中直接使用UPDATE语句,这样会造成Forms上的数据与Oracle数据库的数据不一致,从而造成FRM-40654的错误。
3.2要是问题都是由方法3.1造成的,那么它就不是问题了。
3.2.1下面细讲造成这次造成Form假死锁的根本原因。被锁的记录-订单编号为2000000747:
3.2.2注意认真查看这条记录,我们注意到3967.8这个字段。^_^,至于为什么会注意到这个字段。我折腾了一周多,并且在ITPUB上反复认真学习FORM底层数据操作的原理,再加N*N次方TEST,最后才锁定这个字段。3967.8是“供方承担”的金额字段。经查数据字典又得知数据库字段名为“supply_pay”
3.2.3我们直接从数据库SELECT这个记录出来看看。
select hm.oe_head_number,hm.supply_pay from hek_ods_th_fee_m hm where hm.oe_head_number='2000000747'
看清楚了吗?supply_pay数据库的值为3967.799796,而FORM界面上的值却为3967.8。也就是说FORM自动帮我们四舍五入了。这样我们在FORMS上MODIFY这条记录时,FORM就会判断3967.8<>3967.799796,从而报FRM-40654的错误。而我们呢?却被误导一直在数据库锁方面找问题。
于是,我们总结出,可恶的ORACLE自作聪明,帮我们四舍五入了。可是我们真需要四舍五入吗?就算是要,自已加个ROUND不就得了吗?这不能不说是FORMS一个BUG!这也再次提醒我们不能对ORACLE太崇拜了哦?
FORM6i引入JAVA类
看到FORM中有一个导入JAVA类的菜单,就特亲切。^_^,至少不会让我学的JAVA知识白费。事实上ORACLE很多产品都是要么是用JAVA开发,要么提供了JAVA编程接口。所以JAVA程序员转到ORACLE阵容,还是比较容易的。
1. 安装JDK,FORM6i最好安装1.4的版本,太高版本怕支持不了,比如泛型类。
2. 设置环境变量。
2.1首先设置JAVA基本环境变量。
JAVA_HOME:C:\j2sdk1.4.2_17
Classpath:C:\j2sdk1.4.2_17\lib\tools.jar;
Path:C:\j2sdk1.4.2_17\bin;
2.2设置FORM有关JAVA的环境变量。
Classpath:.;C:\orant\TOOLS\COMMON60\JAVA\IMPORTER.JAR;C:\orant\TOOLS\COMMON60\JAVA
Path::C:\Program Files\Java\j2re1.4.2_17\bin;C:\j2sdk1.4.2_17\jre\bin\client
3.引入JAVA类。
3.1首先,写一个Test的JAVA类。然后将这个类的目录加入Classpath环境变量中。 Classpath:D:\JAVA_FORM\Socket\classes;
3.2导入类。
导入成功后,FORMS会将JAVA类转成PL/SQL包。
3.3调用Test类。
declare
obj
ORA_JAVA.JOBJECT;
a varchar2(50);
begin
obj := Test.new();
a:=TEST.GETTEST(obj);
message(a);
end;
FORMS之列表项动态赋值
1.在数据块d_test,增加一列表项l_test。
2.在when-new-form-instance触发器中加入。
declare
rg_name_test varchar2(40) :='test_name';
v_sql_test varchar2(300);
rg_id_test recordgroup; v_status number;
begin
clear_list('d_test.l_test');
rg_id_test := find_group(rg_name_test);
if id_null(rg_id_test) then --记录组要求有两个为varchar2的列
v_sql_test := ' select hname,hmark from hek_test_headers';
rg_id_test := create_group_from_query(rg_name_test,v_sql_test);
end if;
v_status := populate_group(rg_id_test);
populate_list(' d_test.l_test ', rg_id_test);
end;
FORMS之LOV动态赋给记录组
1.在数据块d_test,增加一文本项l_test、LOV项lov_test、记录组Rec_test1、Rec_test2。
2.给记录组Rec_test1、Rec_test2赋SQL语句,并将项Lov_test的记录组设成Rec_test1。
3.在WHEN-BUTTON-PRESS触发器中加入
Declare Lov_id LOV;
Begin
Lov_id := Find_Lov('lov_test');
if Get_Lov_Property(Lov_id,Group_Name)=' Rec_test1' then
Set_Lov_Property(kpi_lov_xsbb,Group_Name,'
Rec_test2');
end if;
End;
屏蔽FORM系统提示信息
1.1直接将SYSTEM.MESSAGE_LEVEL 设成25,这样大于25的信息提示就不会提示了。
Declare
t_l number := :SYSTEM.MESSAGE_LEVEL;
Begin :
SYSTEM.MESSAGE_LEVEL :=25;
…….. :
SYSTEM.MESSAGE_LEVEL := t_l;
End;
1.2上述方法只适用于小于25的信息提示,无法屏蔽错误提示屏蔽错误提示可以FORM的ON-ERROR或ON-MESSAGE中加入拦截代码(不提倡屏蔽错误提示。)。
declare
t varchar2(10) := error_type;
begin
if (error_code=40202 or error_code=40401) or t='FRM' then NULL;
end if;
end;
FORM之间的调用
ORACLE FORM提供多种方法来实现不同FORM之间的调用。
1.CALL_FORM或OPEN_FORM
这种方法比较直观,但此种方法需要给出FORM的详细路径。如:
call_form('/data/deve/deveappl/au/11.5.0/forms/ZHS/HEK_DISCOUNT_PERIOD_NEW.fmx');
其中:call_form与open_form的区别:open_form可以保留原表单。
2. APP_NAVIGATE.EXECUTE或FND_FUNCTION.EXECUTE
例:APP_NAVIGATE.EXECUTE('HEK_DISCOUNT_PERIOD_NEW', 'Y', 'Y', null);
其中APP_NAVIGATE.EXECUTE与FND_FUNCTION.EXECUTE的区别:
APP_NAVIGATE.EXECUTE只打一个FORM,而FND_FUNCTION.EXECUTE调用多少次,就打开多少个。
Tree-层次树之使用