13.其它



13.1 如何使用数据源

  一旦建立好数据源,报表就可以使用数据源来填充数据了。打开工具条上的“工具箱”()按钮,切换到“数据源”选项卡,即可看到数据源的列结构:

  上图选项卡中,数据源的结构树分为“表”和“单个数据”, 可以用鼠标直接将其下级内容直接拖拽至报表中,“表” 下的 “标题行” 和 “数据行”、以及 “单个数据” ,都是有很大差别的,分别说明如下。

13.1.1 datarow( )
  将 “表” 的 “数据行” 拖到报表的某个单元格,该单元格将会自动生成一个计算公式如"=datarow('ds1')", 表示以该单元格作为左上角,将数据源填充至报表。由于数据源的总行数是不确定的,所以报表将动态插入新的临时行。
  “数据行"(datarow 函数)导致的整表填充,填充是规则的,规则和某些设计技巧说明如下:
规则1: 数据源的第一行直接填充到本行;
规则2: 报表动态插入的临时行,用于容纳数据源的其它行,这些动态行的行号在标尺中略显蓝色并倾斜显示, 如下图的行号,从 5 开始:
规则3: 从左向右的填充规则为:逐个单元格顺序填充,但遇到如下情况,将跨过这些单元格:
    1.单元格是被组合的、不是主单元格;
    2.单元格内有其它计算函数;

  此外,数据源菜单中的这些功能比较有用,能定制列的左右次序、以及是否填充:
  要保存设计好的模版时,建议用上图中的 "清除从数据源填入的数据" 菜单功能清除临时新增行:

13.1.2 headrow( )
  将 “表” 的 “标题行” 拖到报表的某个单元格,该单元格将会自动生成一个计算公式如 "=headrow('ds1')", 表示从该单元格开始,将数据源的标题从左向右填充。
  headrow( ) 通常是位于 datarow( ) 上方的一行,建议您对照一下在线演示中的例子。
  headrow( ) 不是必须的,它的好处就是您不必一个个单元格去输入字段的标题文字了。
  此外,headrow( ) 还有个好处:鼠标点击标题单元格可以实现即时排序。要开通这个功能,需要您在上图的 “自定义排序” 对话框的 “高级” 功能中,去指定哪些列允许点击排序.

13.1.3 data( )
  将 “单个数据” 下的的某个字段拖入报表的某个单元格(或文本框), 该单元格(或文本框)将自动生成一个计算公式如 "=data('ds1', 1, 'price')", 表示仅从数据源取得单个数据,填充到本单元格(或文本框), 这个函数的参数用于指定数据源的行号和列名。
  本方法通常用于有表头表体(master-detail)的报表,典型的如单据,数据库的表头就一行,明细有多行,但在报表的显示中,表头是显示在不同的行,单元格的分布可能会比较随意,所以此时必须要用 data( ) 函数了。
  data( ) 的例子请参看在线演示 “47.套打(一)”、“48.套打(二)”。



13.2 报表文件

  报表最终的存储格式是 XML 格式,您可以将报表保存在本地,也可以保存到服务器。
  将报表保存到服务器,通常是通过 js 调用 GetFileXML 函数实现的:

var xml = AF.func("GetFileXML", "");
  上例中的这个名为"xml"的变量,其内容是一个大串,您可以通过 ajax 将它 POST 给应用服务器,让应用服务器去解决最终的存储。
  至于如何在 html 页面上安排具体的上传功能,可以有如下几种方案:
方案1:在页面中增加一个“上传”的按钮,在按钮的 onClick 事件中调用 GetFileXML( ) 函数,将 XML 串 POST 给服务器;
方案2:监听工具条上的“保存”按钮: 在OnEvent( ) 事件函数中处理“Saved”事件, 在事件中执行 GetFileXML( ) 函数,将 XML 串 POST 给服务器;

  报表组件从1.0.81.0版开始,支持以 zip 方式压缩的 XML 文件了,该支持包括 2 个方面:
1.Build( ) 函数能直接读入 zip 二进制文件或 http 流,例如 http://mysite/reports/abc1.zip , 当然也支持动态的 aspx/jsp 的 URL, 只要这个 URL 返回 zip 文件的纯二进制内容即可;
2.将报表 XML 压缩成 zip 文件,并以 http POST 将二进制发送给服务器, 具体的实现方法如下:
步骤1. 通过 callfunc 函数调用 104 功能号 (104功能号的说明在"4.工具条功能号"中),生成当前报表的 zip 文件,如:
AF.func("callfunc", "104 \r\n c:\\report3.zip");
  上例中,文件名是写死的,不太符合实际开发习惯,所以建议使用全局缓存函数 CacheDirUtility,该函数能动态生成唯一的文件名,将程序修改成如下:
var filename = AF.func("CacheDirUtility", "isCreateTempFile=true; ext=zip");
AF.func("callfunc", "104 \r\n" + filename);
步骤2. 通过全局函数 HttpPostLocalFile 将该文件上传,如:
AF.func("HttpPostLocalFile", "http://mysite/reports/rec.aspx?name=rpt1&org=23 \r\n" + filename);
  当然,上面的这个 "rec.aspx" 接收程序得由您自己写,由您决定把 Http Body 中的二进制内容写到服务器文件或存入数据库.



13.3 异步计算

  通过API函数Calc( ),或者工具条上的“计算”按钮,可以触发报表的填数计算。如果计算过程比较漫长(例如后端计算性能、数据量等因素导致),浏览器表现为锁死, 甚至无法切换Tab选项卡,必须要等到整个计算过程结束才能恢复可操作。

  硕正报表从1.0.66.0版开始,增加了异步计算功能,解决了计算过程中的浏览器僵死问题。
  如果Calc( )函数的参数设为 "mode=asynch", 就表示采用异步计算模式,计算过程被安排在另一个线程中,而该函数则立即返回,当计算真正结束时,报表组件会通过“Calced”事件通知浏览器。
  从启动异步计算,到发出“Calced”事件之间,你不应该调用该控件的任何API函数,否则异步将无意义。

工具条上的计算按钮(),也是采用异步计算的.



13.4 自定义打印纸

  在打印设置对话框中,有自定义打印纸尺寸的选项按钮,关于自定义打印纸这个功能,有必要在此分析一下其原理。
  首先,硕正报表的打印是基于Windows的常规API打印的,也就是说是基于页式打印,不可能去调用具体的打印机驱动程序做诸如逐行即打即停的功能的。
  这个“页式打印”就是以打印纸为单位,每完成一个页任务,打印机就应该吐出一张打印纸来,所以,对于大多数激光、喷墨打印机而言,自定义打印纸恐怕是毫无意义的。
  所以,通常所说的“自定义打印纸”是用于针式打印机的,针式打印机一般都允许自定义打印纸。

  如果在自定义打印纸过程中出现如下错误:
  有2种可能的原因:
原因1 - 所选的打印机本身就不支持自定义纸张;
原因2 - 操作系统没有给IE控件足够的权限,具体的解决办法是在IE的“Internet选项”的“安全”中降低安全等级,待打印纸自定义完毕,再恢复安全等级;
  如果权限问题总是无法解决,那么建议您到“控制面板”- “设备和打印机”中去手动设置,事实上,用硕正控件设置自定义打印纸和手动自定义打印纸是没区别的。
  一旦打印纸自定义成功,那么在“打印纸”的下拉选择框中,就会自动包含有刚才自定义的那种打印纸,这个自定义信息是登记在操作系统注册表中的,一次定义好后,今后都能用。

  由于自定义打印纸是和具体的打印机相关联的,所以还建议您进一步勾选对话框中的“认定使用这台打印机”的选项,然后保存模板。

  最后,我们谈谈自定义打印纸的另一个常见话题:打印模板发布后,网络上其它用户都需要逐个去设置自定义打印纸吗?
  首先,我们模板中是包含了自定义打印纸的完整的自定义信息的,比如纸名、设定的尺寸。当其它用户打开该模板后,报表控件首先会检查本地是否安装了同样的打印机,如果能找到同名的打印机,那么它会进一步去检查是否已经有这种自定义打印纸了,如果没有,它会自动创建的。所以这个过程是自动的。
  当然,在自动创建的过程中,也同样可能遇到权限不足的问题,遇到此问题,您只好按上面的解决办法手工去解决了。



13.5 条形码

  报表中的单元格和文本框都可以作为条形码显示,操作方法为:
1.在工具箱中切换到“显示格式”选项卡;
2.将数据类型改成“字符型”;
3.在"其它显示形式" 选 "以条形码(第三方组件)显示", 如果您需要的是二维码,可以选“以二维码(内置)显示”, 因为“内置”表示直接就有,不需要下载;
4.条形码更多的选项在 "条形码选项";

第三方组件条形码: 您的服务器须部署条形码组件,这样能自动下载的,如果下载安装不成功,则需要手工安装。该组件不支持 64 位 IE, 它也包含了二维码.


内置二维码: 硕正核心程序自带,直接能用,并支持 64 位.




13.6 保存数据到数据库

  相对于硕正树列表(Treelist),要将报表中的某些单元格保存到数据库,是个挺复杂的过程,因为报表格式太灵活了,需要明确行、列和数据库字段的映射关系。
  有如下 5 种实现方案可供选择。
方案一.上报模式
  请参见文档 “11.上报汇总模式”,在线演示中,也有好几个例子供您参考。
方案二.数据源模式
  创建参数 WorkMode 专门有一个针对数据源的模式:InputDSRunTime 模式,加载数据源后,允许您对数据源内容加以修改,并整体保存。请参见在线演示例子 “36.提交修改内容”。
方案三.CollectXML 函数模式
  该函数比较简单,它专门收集报表中某些特殊标记的单元格内容,比如别名、底色,辅之以函数 GetChangedCells、ResetChanged,也能实现较复杂的应用。
方案四.原始模式
  报表的 GetCellData 函数可以获取单元格的数据、用 GetCellProp 函数可以获取单元格的属性(包括自定义属性),你用 js 写循环语句去遍历所有单元格吧,方法比较笨,但也确实是一个办法。
方案五.后端解析模式
  通过 GetFileXML 函数取得报表XML串,将该串抛给应用服务器,后端Java/C#解析这个XML,从中提取出数据来,这是最无奈的办法。


13.7 内嵌树列表(Treelist)

  从1.0.72.0版开始,工具条中增加了树列表的图标,您可以把一个树列表直接加入到报表中。在上报模式中,树列表也能够以“指标”的方式被预先定义在指标库中。
  内嵌树列表只有在运行时(RunTime)才是真正可用的。 而在设计时(DesignTime), 树列表始终处于不活动的状态,您看到的树列表只是一幅图,供您拖拽布局:

  内嵌树列表 和 普通的页面中的树列表,完全是同一种东西,但是访问报表中的内嵌树列表,通常是先获得树列表的句柄,然后以扩展函数方式调用,如下例:
//先获得树列表的句柄 (注:"m010" 是树列表的别名)
var h = AF.func("GetHandle", "m010");
 
//以扩展方式执行该树列表的函数
var xml = AF.func(h + "GetChangedXML", "level=0;isIgnoreChange=true");
  此外,也可以采用另一种更简洁的写法:
var xml = AF.func("m010.GetChangedXML", "level=0;isIgnoreChange=true");  //"m010" 是树列表的别名
  通过上述的这2种扩展方式,您可以访问树列表的几乎所有函数。

  在运行时(RunTime), 报表内嵌的树列表能向页面抛出 OnEvent(id, Event, p1, p2, p3, p4) 事件,但是和普通的页面树列表有一个差别:Event(事件名)前面带有树列表的别名,例如:
function OnEvent(id, Event, p1, p2, p3, p4)
{
 if(id=='AF') { 	//id不变
   //树列表的双击事件,其中 "m010" 是触发了事件的树列表的别名
   if(Event == "m010.DblClicked") {
    ...
   }
 }
}

  不推荐使用内嵌树列表,打印会成为问题。