技术开发 频道

ORACLEERP开发基础之OracleForms基础(二)

  【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-层次树之使用

0
相关文章