5.Report组件

问1.如何学习和数据源相关的报表开发？

1.请参考“开发者指南”中“Supcan Report组件”的“附录：数据源实例”，完全按照其步骤实战一遍;
2.如果您是asp.net开发者，请参考我们主页上“后端开发案例”中的第一个案例，完成数据源的数据提供服务编程，将其结合到上例的实战中；
3.如果您是JavaEE开发者，请参考我们主页上“后端开发案例”中的第二个案例，完成数据源的数据提供服务编程，将其结合到上例的实战中；
4.仔细学习Report的各个在线演示页，请关注其“技术分析区”，它包含了很多很重要的帮助、技巧；

问2.点了工具条上的“保存”按钮，报表怎么没保存到服务器端啊？

Report本身并不具备上传文件的能力，该“保存”功能必须要和页面js、服务器端程序结合才能生效。具体做法如下：
方案一：
在页面中增加一个“上传”的按钮，在按钮的onClick事件中调用GetFileXML( )函数，以取得当前报表的XML串，然后通过Ajax POST到服务器;
方案二：
直接使用工具条上的“保存”按钮，但需要在OnEvent()事件中处理“Saved”事件或“Toolbar”事件：执行GetFileXML( )函数，取得当前报表的XML串，然后通过Ajax POST到服务器。例如：

    function OnEvent(id, Event, p1, p2, p3, p4)
    {
     if(Event == "Toolbar" && p1=="104") { //若用户点击保存按钮，将立即触发该事件
       var xml = AF.func("GetFileXML", "");
       ...
       //Ajax发送 xml 串，(略)
       ...
       AF.func("CancelEvent", "");   //该函数很重要，表示不再执行按钮的后续默认动作
     }
    }

方案三：
类似方案二，但不需要执行 GetFileXML 获得XML串，而是直接把保存的本地文件上传给服务器：

    function OnEvent(id, Event, p1, p2, p3, p4)
    {
     if(Event == "Saved") {
      AF.func("HttpPostLocalFile", "http://localhost/WritePost.aspx?fun=AD91 \r\n" +p1+ "\r\n mode=asynch");
      return;
     }
    }

本方案最简单, 但硕正插件 1.0.104.0 版本后才支持.

问3.报表的工具条、编辑设计功能都很丰富，这到底是为开发设计人员用还是为最终用户使用？

Demo页只是全方位展示Report丰富的功能，最终部署到应用程序时，可以根据需要裁剪掉一些功能。
最简单的功能裁剪方法就是重新定义创建参数，屏蔽掉一些工具条或工具条上的按钮，具体可参考 demo 中的 “几种展现方式”。
如果不怕麻烦的话，可完全抛弃Report自带的工具条：采用自定义工具条，具体可参考 Report 开发指南及相关 demo 页。

问4.Treelist、Freeform 都有 XML 文档规范可参考，为什么 Report 的 XML 文档规范没有公布？

因为Report本身就是一个设计器，你设计好后“另存”到本地文件即可。此外，Report 的 XML 格式远比 Treelist、Freeform 复杂，手写 XML 很难设计出复杂的报表。

问5.怎样控制按数据类型输入，比如数值型只能输入数字、日期型只能输入日期？

打开工具条左侧的“属性”对话框(如右图所示)，选中“启用单元格的数据类型校验(在输入时)”： 但是请注意，该选项在设计时(DesignTime)是不起作用的. |

问6. 硕正报表有没有类似 Excel 的表头冻结锁定功能？

有，鼠标滑过报表上标尺上方、左标尺左侧，会改变形状的，此时向下、向右拖拽即可(见右图)： 此外，执行 SeparateView 函数也可以达到同样效果. 保存该报表模板，当下次再打开，该冻结仍能生效，行列冻结信息是随报表XML一起保存的. |

问7.我明明修改了报表并保存了，下次打开发现还是修改前的报表，这是怎么回事？

是浏览器缓存或Web服务器的原因，和硕正控件毫无关系，关于缓存，请参考 “3.安装、部署和运行” 中的 "问14.经常发现貌似浏览器缓存...".

问8.我某张报表很大，有好几兆，所以上传很慢，有没有加速的方法，比如压缩？

有办法：

    function OnEvent(id, Event, p1, p2, p3, p4) {
     //自定义103(保存)功能号
     if(Event == "Toolbar" && p1 == "103") {
      //1.调用全局函数 CacheDirUtility, 生成临时文件
      var tempfile = AF.func("CacheDirUtility", "isCreateTempFile=true;ext=xml");
      //2.调用104功能号, 保存报表到临时文件
      AF.func("callfunc", "104 \r\n" + tempfile);
      //3.调用全局函数 HttpPostLocalFile, 压缩上传, 注意 compress=zip 选项
      AF.func("HttpPostLocalFile", "http://192.168.1.1/nse/get.aspx?id=10203 \r\n" +tempfile + "\r\n compress=zip; mode=asynch");
      //4.终止默认的后续动作
      AF.func("CancelEvent", "");
      return;
     }
    }

报表是以 zip 格式压缩的，您的后端程序可以直接把二进制流写到文件或保存到数据库，没必要解压。下次打开报表，可以直接返回这个二进制流, 因为硕正控件能识别 zip 流、自动解压的.
为了弄懂其原理，建议仔细查阅这些函数的文档.

问9.采用Supcan Report设计好报表后，如何保证让最终用户既能编辑格式、但又不改坏已定义好的临时数据源？

建议您采用包含如下功能的方案：
1.为每种报表设计一张原始的“模板”报表，内含完整的数据源定义，报表外观编排可以不完全按照用户的需求，粗糙一点也没关系；
2.提供自定义报表功能，用户新建的报表只能从“模板”复制，而不能让其直接修改“模板”；
3.需要为用户实现报表的保存功能，保存到服务器，下次默认打开的是用户自己新建的报表；
这样就能很好地实现不同的登录用户打开不同外观的报表、一个用户拥有多张报表的功能----其实都是从同一张模板中复制的。
中心数据源不存在这样的问题。

问10.如何实现报表仅用于被查询而不被修改？或报表中部分内容不被修改？

首先，要区分是什么工作模式：上报模式和非上报模式(即普通模式)，以下将单独说明：
1.上报模式
上报模式的运行时(UploadRuntime)的控制比较简单，可编辑区域就是标记了 TabOrder 的单元格，以及可编辑范围内的内嵌表格. 这些都是由您在设计时确定的.
2.非上报模式
大多数情况下，用权限类函数中的 "Swkrntpomzqa" 函数就能解决问题。
如果您想让所有单元格都不准被修改，那么只要写如下语句即可：

    AF.func("Swkrntpomzqa", "1, 2");

如果要让某些单元格不被修改，最简单的办法就是为单元格设置 “保护” 属性。请注意：“保护” 属性无法单独起作用，必须结合 Swkrntpomzqa 函数才能生效:

    AF.func("Swkrntpomzqa", "16");

“保护” 是需要手工逐个单元格预先设置的，如果您嫌这个步骤麻烦，那么可以参考开发文档中的其它权限类函数，比如 addEditAbleOnly( )、addUnEditAbleOnly( ) 函数，其功能非常强大，例如下例表示仅红色背景色的单元格能够修改:

    AF.func("addEditAbleOnly", "bgClor=red");
    或
    AF.func("addEditAbleOnly", "bgClor='#FF0000'");

除了颜色，还有其它的属性可以用于判断，详情请参考该函数的使用文档.

问11.我设计好报表、填入数据源的数据，然后将报表另存，当我再次打开这个报表(XML文件)后，发现填入的数据不见了，这是怎么会事？

在默认情况下，报表永远是以“模板”的形式保存的，模板是不包含从数据源中取到的数据的，也就是说在保存时，临时剔除了填入的数据。当报表再一次在页面中展示时，需要在OnReady( )事件中书写代码以取数、填充、展现。您明白这个机理了吗？
如果您非要连同数据一起保存，那么可以在工具条菜单 数据源管理 - 高级 中将选项“数据源填充的数据连同报表一起保存”勾上即可。

问12.我更改了打印的设置(如页边界、分页参数)，如何保持住这些参数？

打印的所有参数，都是保存在XML模板文件中的。你修改了打印设置，需要保存一下模板(或上传到服务器).

问13. 为什么在打印预览中明明 1 张纸能打印得下，真正打印时却打印了2张纸？

不可能的，因为硕正控件的源码中，预览和打印是使用同一个分页程序的。
造成该现象的原因很可能是您修改了打印配置后，忘了保存模板啦。比如，在预览界面中调整了边距，在关闭预览界面后却没有将报表模板保存，这样下次打开报表后，边距仍然还是旧的。
记住：报表的打印设置全部是保存在报表模板中的，它不可能保存在别的地方的.

问14.如何实现同类单证的连续打印？

通常需要写如下的循环:

    AF.func("Build", "reports/aax.xml"); //打开模板
    for( ... ) { //自己写循环
     //假设是临时数据源, 设置查询取数条件 (即更改数据源)
     AF.func("SetSource", "ds1 \r\n abc.aspx?mon=" + mon);
     //计算 (备注：必须同步计算，不能选异步)
     AF.func("Calc", "");
     //打印, 变量 isOpenDlg 用来控制是否弹出对话框, 通常第一遍循环中设为1，其余的为0，这样打印对话框就只弹出一次
     AF.func("Print", "isOpenSysDialog=" + isOpenDlg);
    }

如果循环中需要切换报表模板，那就把第一行移入循环.
此外，报表组件还提供了另一种更高级的连续打印方式：调用 BeginBatchPrint / EndBatchPrint 进行批打印，具体请参考此函数的说明文档.

问15.我在报表打印配置对话框中自定义了打印纸，为什么有的电脑打开这个报表时，会出现如下提示？

是有这个可能的. 自定义打印纸的尺寸信息是包含在报表XML中，当控件在打开报表后, 会尝试着去创建这个自定义打印纸，但是创建过程可能会失败，失败原因就在于操作系统分配给浏览器的权限不够. 如果创建失败, 您只能在 "开始" 菜单 \ "设备和打印机" 中选择打印机, 然后在上方的 "打印机服务属性" 对话框中去手工创建(见右图). |
此外，如果你的打印机不支持自定义打印纸、或者自定义打印纸正好是标准打印纸 (如A4、A3) 的 n 分之一，你就不必去创建自定义打印纸了，改而使用这个选项: (见右图) |

问16. 我使用了自定义打印纸，并用针式打印机打印的，发现打印1、2张没问题，但在连续打印时，上下偏移误差会越来越大，这是什么原因？

有两种可能：
首先，您观察一下打印预览，如果后面几页的预览内容就已经不正确了，说明您的报表模板高度已经超出了打印纸的高度，只要适当调整一下某行或某几行的行高即可；
如果预览正常、而实际打印有问题，那就说明纸张的实际高度和你自定义的纸张高度是不一致的，此问题和硕正插件一点关系都没有。
解决办法：按照上一个问题的截图提示, 打开打印机服务属性，逐步微调自定义纸张的高度，使之和纸张物理高度趋于一致.

问17. 我使用了自定义打印纸，并用针式打印机打印的，左右边距都设成2毫米，在预览中表格线能紧挨左侧的虚线(边距标记), 一切正常, 但是实际打印时, 左侧会留出太多的空白, 这是什么原因？

可以推测, 表格线左侧约 2 毫米的位置, 应该就是打印头的 x 轴坐标原点，如右图箭头所指: 既然该打印机支持穿孔打印纸，那么在它的背后的滑杆上应该有两个穿孔咬合处，其位置是能微调的，将它们整体往右移动约1厘米，应该就正常了. |

问18. 为什么打印预览时表格都是居中的，但是真实打印出来，左边距会比右边距多大约1厘米？

大多数情况下，将模版中的这个选项选 "不忽略" 即可正常，因为不同打印机的物理有效边距有轻微差异 (见右图): 在设计报表模板时，幅面大小不要满打满算，应该考虑到打印有效边距因素、以及软件的误差因素. |

问19. 我在打印配置中选择了使用某台打印机, 但是下次打开这报表发现又变成其它打印机了, 能固定住吗?

下次进去时, 肯定是采用当前操作系统的默认打印机的。如果你想固定用某台打印机打印，必须勾选下方的 "认定用这台打印机打印" 复选框(见右图). 在网络环境下, 其它电脑也使用这个模板时, 硕正插件首先会去本地查找同名打印机，如果找不到，则又切换回本地操作系统的默认打印机了. |

问20.我不想在页面中出现报表控件，只是想调用CallFunc函数直接打印，所以我把 div 的高、宽设为1px，为什么经常会不稳定，似乎控件没有创建？

你把高、宽各设成 2px 就很稳定了，估计和浏览器对div的渲染有关.

问21.继续上一个问题，我把 div 的高、宽设为2px，但还是有个白色(或黑色)的小点，能去掉它吗？

在它的 OnReady 事件中，将它的div容器隐藏即可。隐藏div 可设置 visibility 为 hidden, 不建议用display="none" 。

    ...
    function OnReady(id)
    {
     var container = document.getElementById('dv1');
     container.style.visibility='hidden';
     AF.func("Build", "report/report1.xml");
     AF.func("callfunc", "18");
    }
    </script>
    </head>

    <body>
    <div id="dv1" style="position:relative;width:2px;height:2px"><script>insertReport('AF', '')</script></div>
    </body>
    </html>

问22.我在学习带参数的数据源，是不是URL中的变量如 "test.aspx?y=2011&mon=23&dept=A1" 中的 y、mon、dept都必须当作参数？

不是的，报表中的“参数”是指内容取自单元格的参数，像固定的、或由js控制的变量就不能算是“参数”.

问23.中心数据源或临时数据源，有很多处的URL(例如 dataURL)属性，如果采用相对URL，到底相对谁？

遵循这2个规则： 1.谁定义，就相对谁; 2.斜杠("/")开头表示相对根目录。

问24.电子表单通常分二部分：表头(master)和表体(detail)，硕正Report能实现吗？

请参考demo页“报表 - 精确打印 - 套打(一)”，这应该就是您所要的。 或者，也可以参考下Freeform的demo页“9.有表头表体的表单(2)”

问25.我们的数据服务都设计成 rest 方式调用的WebService，请问应该怎样和硕正的数据源配套使用？

选择普通的XML数据源(包括临时数据源或中心数据源)就行，而不是硕正Web Service数据源，因为硕正WebService数据源仅用于POST的、非rest方式的.
此外，你后端需要配置为： 1.请求方式必须是Get; 2.MediaType.TEXT_PLAIN.

问26.我用 NotePad 打开一张使用了中心数据源的报表XML文件，发现其中有中心数据源的列结构的详细信息(即所谓“数据源模版”)，这些列结构有什么用呢？

在大多数情况下，这些列结构信息确实没啥用处，因为报表在展现时，列结构将强行和后端的中心数据源同步. 但是如果你修改了列的显示名(标题)、列的显示顺序、是否隐藏等等，那么这些信息就有用了。
当然如果使用的是临时数据源，那么列结构信息是绝对有用的。

问27. SetSource( ) 函数到底应该在 Build( ) 函数前执行还是后执行？

如果是临时数据源，必须先执行Build( )，再执行 SetSource( ); 如果是中心数据源，则应该先执行 SetSource( )，因为它是全局有效的, 对今后的新增报表都能生效.
如果你的报表中既有中心数据源，又有临时数据源，那么在 Build 前先执行针对中心数据源的 SetSource 函数，在 Build 后再执行针对临时数据源的 SetSource 函数。

问28. 我使用了中心数据源，执行了
AF.func("SetSource", "../dscenter.aspx?id=337")
然后通过Build打开报表模板、正常计算，没问题。然后在另一个页面执行
AF.func("SetSource", "../dscenter.aspx?dept=mnger");
然后通过Build打开报表模板，发现总是只有一张报表能计算，另一张计算不了，为什么？

在浏览器进程中， 应该保证中心数据源只有唯一的入口。你通过 SetSource 切换了入口地址，期望能有多个入口，这是办不到的。

问29. 我使用了中心数据源，用XML查看器直接查看报表的XML文件中的数据源描述部分，发现里面有一个中心数据源的URL(即下面<Source>内容):
是不是意味着不需要执行SetSource函数？

这个URL是设计时的中心数据源入口URL，如果没有执行过 SetSource 函数，我们会默认采用这个URL作为中心数据源入口地址。
尽管如此，我们还是强烈建议您在 Build 之前先执行一下 SetSource，因为只要执行过 SetSource，中心数据源的入口URL就被明确指定了，报表中的这个URL将无效。
试想一下，如果不执行SetSource, 你打开了2张不同中心数据源的报表，中心数据源最终以哪张为准呢？

问30. 报表中使用了超链接，有时候是直接打开了链接页面，有时候没反应，这有什么规律吗？

如果你预订(SubscribeEvent) 了Clicked事件，那么只抛出Clicked事件，不会打开超链接页面的；否则则相反，只打开链接页面、不抛出事件.

问31. 我想在页面 js 脚本中直接调用数据源中的数据，可以吗？

可以，请参考“全局函数”文档中的 “5.对 计算函数 的封装”。但你只能调用 data、head 这些单个数据，无法调用 datarow、headrow 这些块状的数据.

问32. 我想对报表中数据源填充区域做一个合计，例如在 =datarow( )函数的下一行写 =sum(D4:D20)，但无法预计数据有多少行，不知道D20这样写对不对？

报表在线演示中有很多这方面的演示页的，例如右图: 关键就是需要“@单元名”宏，详细请参考 "公共内容" 中的 "3.宏". |

问33. 报表中的单元格怎样让它使用下拉？

首先，您必须在报表中先设定“字典”，字典功能在“工具箱”上方第四个小按钮中；
“新建”字典，字典中有2列：key 和 text，这里看似需要逐条输入，其实也可以直接从数据源加载的(在对话框右上方的“数据源”按钮)；
建好字典后，设置单元格的选项，在工具箱、单元格选项的 " 下拉框选择录入"选项卡中.

问34. 我想把当前报表复制到一个新建的工作表中，该怎么做？

直接复制工作表的功能是没有的，但可以这样实现：

     //取得本地的一个临时文件 (注: CacheDirUtility 函数是全局函数，请参考“全局函数”文档)
     var filename = AF.func("CacheDirUtility", "isCreateTempFile = 1");
     //调用 1074 功能号，把当前报表另存到临时文件 (注：请参考工具条功能号文档)
     AF.func("callfunc", "1074 \r\n" + filename);
     //添加工作表
     AF.func("AppendWorkSheet", filename);

问35. 怎样动态地用 js 往报表中插入图片？

有二种方法：
方法一: 通过 callfunc 调用 192 功能号，在报表的文档“4.工具条功能号”中有该功能号的说明和例子;
方法二: 插入文本框，而不是图片，通过动态将其显示属性设成 "以图片显示(内容为URL ...)" 显示图片，这种方法更加灵活，尤其是需要动态更改图片时;

     //插入别名为 "MyPic" 的文本框
     AF.func("callfunc", "112 \r\n x=10; y=20; width=200; height=140; alias=MyPic");
     //将其数据类型改成字符串型
     AF.func("SetCellProp", "MyPic \r\n dataType \r\n string");
     //添加图片显示掩码
     var id = AF.func("Mask_Add", "string \r\n =picture()");
     //修改它的显示掩码， 至此，该文本框就以图片显示了
     AF.func("SetCellProp", "MyPic \r\n maskId \r\n" + id);

     //赋图片:  通过 SetCellData 赋值就行
     AF.func("SetCellData", "MyPic \r\nDotNet/res/aa.jpg");
     //设置其它效果：背景色为透明、无边框
     AF.func("SetCellProp", "MyPic \r\n bgColor \r\n T");
     AF.func("SetCellProp", "MyPic \r\n borderThick \r\n 0");
     //刷新一下屏幕
     AF.func("SetRedrawAble", "true");

问36. 我按照 “20.分类汇总” 做了个分类汇总表，汇总计算结果含有千位符，怎样才能不显示千位符呢？

修改一下那个汇总表达式，把里面的 @sum 替换成 replaceAll(@sum, ',', '') 或 formatNum(@sum, '#.00')

问37. 数据源加载的数据，为什么有些单元格显示为诸如“5.405297231E7”这种科学计数法?

这个串来自你的XML/JSON数据包，如果在数据源中该字段设为 string 型，报表控件是原封不动将其显示的。
你只要将该字段设为 decimal 型即可。
如果该数据源已经被拖拽到报表中了，你那必须把相应单元格的数据类型也设为数值型。

问38. 报表的 PDF 格式导出，发现内部是图片，能否导出纯文字啊？

目前尚未支持，但有一简单的办法：安装一个 adobe pdf虚拟打印机吧.

问39. 我需要用 js 执行大量的 GetCellData, SetCellData, GetCellProp, SetCellProp等等存取单元格信息的函数，以前速度很快，但不知道是什么原因，有时候速度奇慢，这是怎么回事？

导致慢的通常有2个原因。
原因1：
每次执行完 SetCellData、SetCellProp 这些更改单元格数据或状态的函数, 硕正插件都会自动发起一个界面刷新(即重画)的操作, 而密集的界面刷新既无必要，又严重拖累了整体性能速度。
为此，建议你执行批量函数前关闭重画，执行完后开启重画。重画的函数是 SetRedrawAble, 请详见文档中该函数的具体用法。
原因2：
函数中第一个参数是单元格名，须保证该单元格是存在的，加入您执行了 AF.func("GetCellData", "K33"), 其中这个 "K33" 单元格必须真实存在，否则会拖累整体性能.
硕正报表内部实现原理是这样的：如果上面这个 "K33" 单元格不存在，就会自动将 "K33" 当作是某个单元格的别名，从报表中按别名查找单元格，而这个过程的效率是不高的，如果在循环语句中大量执行，整体速度肯定不会快。

问40. 我这样写，分别调用163(计算)、18(预览)功能号，为什么第二句永远不执行？

     AF.func("CallFunc","163");
     AF.func("CallFunc","18");

因为163功能号 (即按钮点击 “计算”) 是异步计算，所以你应该用 Calc 函数，不要调用 163 功能号。

问41. 工具栏中的“计算”按钮(即齿轮图标)，和函数 Calc( ) 有什么差别？

差别不大，插件内部都是调用同一个内部函数的，只是按钮启动的是异步计算，相当于是执行了 AF.func("calc", "mode=asynch; range=full")。

问42. 关于分类汇总表或交叉表，我看例子中，旁边都配有一个纯数据的工作表，这个工作表可以删除吗？删除它对旁边的分类汇总表/交叉表有影响吗？

无影响，可以删除。
例子中保留这个纯数据工作表，只是用于对照，为了让你明白: 对数据源的格式要求，也只是普通的平面数据而已。

问43. 假如 A1 单元格的计算公式为 "=E2 + F2", 然后我用 SetCellData 更改了 E2 单元格的内容，发现 A1 单元格不会自动更新，请问如何让它计算 ？

在 SetCellData 后，执行 calc 函数吧。
如果当前工作表有数据源填入区，你会发现执行了 calc 后, 数据源填入区重新刷新了！如果你希望数据源公式不参与计算，那么请用 260 功能号代替 calc 函数，详情请参考文档 “5.工具条功能号”

问44. 我在 A1、A2 单元格分别输入 1、2，然后 让 B1 = A1 + A2, 结果是 3, 能否让它们以字符串方式相加，也就是说结果是 “12” ？

你应该将 A1、A2 单元格的数据类型设成字符型。

问45. 我的分类汇总表如图，我怎样才能不显示 “城市” 列的重复行内容？比如 “布宜诺斯艾利斯” 只显示在第一行，下面同名的就不显示?

请在 datarow( ) 所在行的城市单元格，将它的 "显示掩码" 设成: if(ref(row( ) - 1, col( )) == data, '', data) 重新计算后，结果就这样了(如右图)： |

问46. 单元格内的文字, 怎样强制换行? 比如下面是自动换行, 我怎样才能在 "实际" 处断行 ?

有二种办法:
办法一: 在断行处输入 "\r\n";
办法二: 双击单元格, 在单元格自己的输入框中, 在断行处敲键 Ctrl + 回车
如果您的数据是从数据源填入的, 要强制换行, XML/JSON 数据串中也应该含有回车符, 或者是 "\r\n" 的这种串.

问47. 我用了分页小计函数 =PageSum('D'), 打印预览时是正确的, 但显示时, 却会显示成 1.0 或 0.0, 能否做到普通显示时不显示, 单元格留空?

把公式改成诸如这样吧：= if(Pages()==0, '', pageSum('D'))