本文试图解释Lookup窗体的实现方式和原理,大多数内容是Axapta的联机帮助的重新组织,部分内容没有参考资料,属于猜测.
准备知识
1.控件类型
Axapta中的窗体控件根据与数据源的绑定情况可以分为三种,绑定控件,非绑定控件和计算控件.
所谓绑定控件是指该控件的DataSource属性指定了某个具体的DataSource,DataField指定了DataSource中的某个字段.
非绑定则没有指定具体的数据源,而ExtendedDataType指定了某个类型.
计算控件是指通过Display或者Edit方法获取的返回值.
2.几个需要关注的控件方法
FormStringControl,FormDateControl等控件类有几个方法用于实现Lookup机制.
Lookup方法,这个方法是在用户点击下拉框时首先触发的方法,该方法的Super()方法会根据该控件与数据源是否绑定分别调用不同的方法:
如果是绑定控件,则会调用performDBLookup方法,performDBLookup继续调用DataSource中字段的Lookup方法.
如果是非绑定控件则会调用performTypeLookup方法,performTypeLookup继续调用PerformFormLookup方法.
从上述描述可以看出根据控件类型,LookUp会走两条不同的路线:
绑定控件: 控件LookUp方法->控件performDBLookup方法->FormDataObject的Lookup方法.
非绑定控件: 控件LookUp方法->控件performTypeLookup方法->控件PerformFormLookup方法.
上述方法的原型如下:
public final void performDBLookup( [fieldId _fieldId, tableId _tableId, selectableDataArea _company] )
public final void performTypeLookup( [int _userType, int _arrayIndex, selectableDataArea _company] )
public void performFormLookup(FormRun _form)
前两个方法是final类型的不允许覆盖.
需要解决的问题
要搞清楚Lookup的原理,需要整明白几件事情:
1.点击下拉框时展示数据的到底是什么?
这个毋庸置疑是一个窗体,也就是一个FormRun的实例对象,由于下拉框一般用Grid展示,所以大部分Form只有一个Grid控件.
2.Grid中展示的数据是如何添加进去的?比如为什么CustTable这个窗体中的客户组字段的Lookup窗体显示了客户组和和说明两个字段而不是其他的字段?
对于绑定控件,在文档中没有找到具体的说明是如何实现的,不过对于Grid中所包含的字段有个说明,是通过表之间的关系来实现的.比如CustTable中的客户组这个字段是CustGroup这个表的外键,于是Grid的字段由表CustGroup的TitleField1和TitleField2这两个属性(分别为CustGroup 和Name),另外就是这两个表之间的关联字段CustGroup,还有表CustGroup的第一个索引.如果这些选项有重复的话会去掉重复的.如果不想用默认的这些属性,那么可以在Field Groups中的AutoLookUp中添加项,这样出来的就是AutoLookUp中的项了.
对于非绑定控件,默认情况下跟绑定控件是一样的,根据控件EDT属性中指定的EDT类型找到对应的表,Grid中包含的字段跟绑定控件类似.
在上述两种情况下,通过设定EDT类型中的FormHelp都可以改变Lookup窗体,比如可以将CustGroupId改成CustGroup等,不过由于Lookup窗体需要特殊定制,一般的窗体是不能满足条件的.
对于非绑定控件,可以重载performFormLookup或者控件的lookup方法,使其调用其他的窗体,如联机帮助中的代码所示:
{
FormRun FR = New FormRun(New Args("ColorLookup"));
FR.Init();
this.PerformFormLookup(FR);
}
3.Lookup窗体是怎么创建出来的?
窗体的创建可以用两种方式:
a.在AOT中创建
b.用代码创建
这两种情形的结果是一样的,最终在内存中都是一个FormRun对象.由于看不到performDBLookup的源代码,我们只能根据performFormLookup这个方法还推测其原理.
用代码创建窗体AOT中有一个挺好的例子 类SysTableLookup,这个类用于动态创建一个FormRun对象,然后调用窗体控件的performFormLookup方法.下面的代码是使用该类的客户端代码:
{
Query query = new Query();
QueryBuildDataSource queryBuildDataSource;
QueryBuildRange queryBuildRange;
// Create an instance of SysTableLookup where 'this' the current Form control.
SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(custTable), this);
;
// The field to be shown in the lookup form.
sysTableLookup.addLookupField(fieldNum(custTable, accountNum));
sysTableLookup.addLookupField(fieldNum(custTable, name));
// Limit and arrange data selection.
queryBuildDataSource = query.addDataSource(tableNum(custTable));
queryBuildRange = queryBuildDataSource.addRange(fieldNum(custTable, accountNum));
queryBuildRange.value('A..B');
sysTableLookup.parmQuery(query);
// Perform lookup
sysTableLookup.performFormLookup();
// do not call super().
// super()
}
上述代码就可以创建一个Lookup窗体,需要注意的是,如果newParameters的入参是临时表的话,需要用parmTmpBuffer这个函数将当前的临时表传入进去,否则查不出任何数据.原因很简单,看一下SysTableLookup这个类的FormRun方法就知道了,它会把传入的表作为数据源,如果是实际的物理表这没任何问题,因为每次都是从数据库中查询,但是由于每一个临时表都对应物理磁盘的一个文件,这样如果只传入一个临时表的表名,根据表名它没有办法知道去寻找应该对应哪个物理磁盘文件.该类的FormRun方法用如下语句创建关联:
{
formDataSource.init();
formDataSource.cursor().setTmp(); // if using non-temp table in tmp mode
formDataSource.cursor().setTmpData(tmpBuffer);
}
OK,到这里,下拉框里的窗体真相大白了,那么这个窗体怎么会在用户选择完之后就自动关掉了,并且会将Grid中的某个特定的值赋值到对应的控件上那?
这里用到了FormRun里的两个方法,selectMode和selectTarget.
其中selectMode指定取值字段,selectTarget则指定把值赋值到哪个控件上.
其函数原型如下:
public final void selectMode( [FormControl _control] )
public final FormControl selectTarget( [FormControl _target] )
selectMode的调用在SysTableLookup中可以找到
formGridControl.setFocus();
formRun.selectMode(formRun.control(controlIdx))
最佳实践:
没有规矩不成方圆,以后俺也关注一下BP,免得被说老土,呵呵.
1.只有系统不能自动生成Lookup窗体时才考虑在AOT中创建自己的Lookup窗体,这些窗体必须以Lookup作为后缀.通常情况下通过定制AutoLookup组就可以满足要求了.
2.如果需要指定Query,显示栏位或者两者都要显示,则应考虑用SysTableLookup类的功能.
3.手动创建的窗体必须与系统自动产生的窗体具有相同的功能,支持查询表达式,根据输入的查询条件自动对焦到相应行.
4.为了避免Lookup窗体闪烁晃动,需要在run方法中禁用自动查询功能.示例代码如下:
{
FormStringControl callerControl =
SysTableLookup::getCallerStringControl(element.args());
boolean filterLookup = false;
;
if (callerControl.text() && callerControl.hasChanged())
{
filterLookup = true;
Common_ds.autoSearch(false);
}
super();
if (filterLookup)
{
Common_ds.research();
Common_LookupField.filter(callerControl.text());
}
}
|
Data source |
No |
安全检查需要关掉 |
|
Data source |
No |
不允许编辑 |
|
Data source |
No |
不允许创建 |
|
Data source |
No |
不允许删除 |
|
Data source |
Yes |
只取在Grid中展示的字段 |
|
Design |
Border |
将不会在窗体上显示属性 |
|
Design |
Popup |
- |
|
Grid |
No |
- |