ORACLE ERP开发之OracleForms基础(一)
【IT168 技术文档】
ORACLEERP开发基础之前言 http://tech.it168.com/a2009/0427/274/000000274048.shtml
Forms基本对象概念
明白了上面的基本概念,就可以开工了。
设置ITEM为必填项
Setup:
Effect:
此效果与是set_item_property('test.l_test',required,property_true)一样的。
设置ITEM的初始值为当前日期
实现按“ENTER”自动跳至下一条记录
设置BLOCK属性:导航器风格:改变记录。
使用堆叠画布
Effect:
1. 先将数据块、画布布局好(用向导的方式就可以了,具体操作就不用讲了吧)。
2. 在画布中创建一个堆叠画布。
3. 将项的画出属性更为堆叠画布(这一步最关键了)。
4.调整后得到下面这效果了。
编写一个健壮的FORMS应用程序,免不了要做各种数据的检验动作。所以必须了解FORMS事务触发器的工作原理。其他类型的触发器相对来说比较好理解,就不详说了。
① FORMS处理事务分成发送(POST)和提交(COMMIT)两个阶段。这个跟JAVA中的事务操作类似,也就是Statement和Commit两个阶段。但FORMS一些规则比较死,也就是说规定好POST之前会触发PRE触发器之类等等。
② 事务触发器分成三类,PRE-XXX、ON-XXX、POST-XXX,它们的执行顺序可以直接从其英文缩写得出。
例:执行INSERT操作,会按以下顺序进行。 1.1从数据项复制数据。 1.2触发PER-INSERT触发器。 1.3检查记录的惟一性。 1.4插入行到其表或者触发ON-INSERT触发器。 1.5触发POST-INSERT触发器。
创建FORMS数据块的主从关系
1、先创建两个数据块,当然这两个数据块在DB应该有外键的关系(不是一定要创建数据库外键关联哦?)。
2、选择从表的数据块,然后点击菜单数据块向导。
3、选择关联属性。
非孤立:表示当有从记录时,不允许删除主记录(默认)。 级联:当删除主记录时,从记录也一起删除(推荐使用)。 孤立:当删除主记录时,从记录保留不变(不推荐使用)。 事实上,当选择不同的属性时,FORMS会在主数据块中,自动创建两个触发器 ON-POPULATE-DETAILS:当插入主数据块记录时,自动将主键赋值给从数据块外键字段。 ON-CHECK-DELETE-MASTER:实现从删除主数据块记录时,从数据块要执行何种操作。
4、设置主键及报FRM-30100错误。 ①如果数据块属性设成使用主键,那么该数据块的ITEM必须有一个设成主键,否则报FRM-30100错误。 ②即使数据块设成不使用主键。但该块的查询数据源名称与DML数据目标名称不一致时,也是必须设至少一个ITEM为主键。否则报FRM-30100错误。如图:
注:查询数据源与DML数据目标不一致时,数据INSERT、UPDATE、DELETE都会发生在DML数据目标那张表。 例如:以视图为查询数据源,而在数据表中仅保留ID。而查询时,自动匹配查询到相应的number、description 将视图的字段设置如下,否则会报无法找到这个字段之错误。
5、主键赋值。 按数据库设计模式范式要求,每张表都应该有一个PRIMARY KEY。而且此字段一般不让用户操作,由sequence自动维护。我们可以数据块的PRE-INSERT触发器中加入代码:
select hek_test_sq.nextval into :hek_test_headers.hid from dual;
发布时间:2009-4-28 0:00:00
【IT168 技术文档】 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-层次树之使用 |