☰
NC65二次开发入门:从读懂类关系到掌握高频API操作
2026/10/20 14:28:31 网站建设 项目流程

简介:面向用友NC65平台开发新手,这份PDF系统汇总了开发中高频使用的API接口与可直接套用的Java代码片段,内容涵盖表格行列信息获取、界面默认值设置、表单默认执行方法、报表合计行、小数位精度、数据清空、字段可编辑、查询条件打印、提示框、查询面板取值、时间比较、编辑公式、查询对话框默认值、继承关系、按钮控制、动作脚本、字段显隐、数据导入导出、集合操作等二十余个常见场景。该PDF为单个文档,共1个文件,压缩包仅193KB,轻量便携。目前已有583人浏览学习,适合需要快速上手NC65表单与报表开发的初中级开发者。文档按功能点逐条展开,每个场景均配有说明与关键代码示例,并整理了单据开发一般步骤、界面数据访问等进阶内容,可直接对照项目实际需要取用,有效减少踩坑、提升NC65定制开发效率。

1. 从第一个困惑说起:NC65二开到底在改什么

第一次接触NC65开发,多半是拿到一张企业单据要加功能或改逻辑,打开代码编辑器却发现满眼都是类、方法、继承关系,不知道从哪儿下手。我当年也一样,翻开那份API资料目录,看到六十多个条目,第一反应是合上它。

这个困惑的本质,是你还没分清“平台的公共能力”和“你这张单据的特殊逻辑”。NC65平台本身已经把建单据、存数据、出报表这些底子做完了,你做的二次开发,说到底是回答一个问题:这张单据在用户操作时,要额外触发哪些动作。

NC65开发常见API把这些高频动作收在一份清单里:读选中行、设默认值、控制字段编辑、弹提示框、算合计、比较时间。它不让你从零造轮子,而是告诉你现成方法能解决手头这个需求。这篇文章会带你从最基础的类关系开始,一步步走到第一个能跑的API例子,让你不再对着代码发怵。

2. 先搞懂三件事:类、事件与数据流转,再去看API

2.1 一张单据界面背后,至少有四个类在配合

NC65的单据界面,用户眼里是一张表头加表体的单据窗口,但在代码里,这张界面由多个类一起配合。给新手的清单很简单,记住四个角色就行:

  • 界面类负责展示,表格、输入框、按钮都归它管。
  • 事件处理类负责响应动作,保存、增行、删除这些按钮的逻辑写在这里。
  • 控制器类负责把界面和数据模型绑定起来。
  • 业务委托类负责把数据送到后台去。

以管理型单据为例,界面类写出来长这样:

public class ClientUI extends BIllManageUI { // 1. 告诉平台:界面用哪个业务委托类 protected BusinessDelegator createBusinessDelegator() { return new MyBusinessDelegator(); } // 2. 告诉平台:按钮事件用哪个事件处理类 protected ManageEventHandler createEventHandler() { return new MyEventHandler(this); } // 3. 告诉平台:控制器用哪个类 protected AbstractManageController createController() { return new MyController(this); } }

这三个方法分别回答三个问题:数据交给谁、动作交给谁、绑定交给谁。入门的话建议先别管实现细节,只要记住这句话:你在界面类里看到的这三大方法,决定了整个单据的运行骨架。新手最容易卡住的地方,恰恰是方法抄了一堆,却不知道每个方法应该在哪个类里出现。返回来把这张最小分工装进脑子里,后面就顺了。

值得一提的是,事件处理类里最常用的是那几个固定方法,保存、编辑、删除、取消都有一套默认入口。例如保存操作对应onBoSave(),编辑对应onBoEdit(),这些方法在事件处理类里被重写,平台在对应按钮被点击时会自动调用。你不需要手动找按钮去绑事件。

2.2 为什么先懂继承关系,再去看别人的代码

新手最爱踩的一个坑,是看到网上分享的一段代码,复制回自己项目,编译直接失败。原因十有八九不是语法错误,而是把片段放错了类。别人是在事件处理类里运行的代码,你却放进了界面类,方法名都对应不上,编译器当然报错。

那份API资料里专门讲到了管理型界面各个类的继承关系。实际维护中,你需要做的是“反向查父类”:界面类里找不到某个方法时,按快捷键看继承链,往上一层一层翻,直到找到这个方法的定义处。这个习惯比死记所有方法都有用。

排查的逻辑很固定:先确认这段代码应该在哪个类里运行,再看类里有没有继承到目标方法,最后检查包的导入。三步走完整,基本能解决八成“找不到符号”的情况。

2.3 表头表体数据结构:数据到底怎么流转

NC65里一张单据的数据,靠值对象类来承载,表头字段在表头值对象里,表体行在表体值对象里,多个页签就存在多个子值对象里。取数的时候有三个最常见的入口:

// 取表头字段,先拿到控件,再取值 Object headValue = getBillCardPanel().getHeadItem("pk_billtype").getValue(); // 取表体第 0 行某个字段的值 Object bodyValue = getBillCardPanel().getBodyValueAt(0, "sys_shipper_id"); // 设置表体某一行某个字段的值 getBillCardPanel().setBodyValueAt("新值", 0, "sys_shipper_id");

第一个入口取的是表头,第二个入口取的是表体,第三个入口是写回表体。注意第二个方法里的行号是从 0 开始的,第一行是 0,第二行是 1,这和很多新手的预期不一样。我第一次写这个 API 时也在这里翻过车,以为是像表格软件那样从 1 开始,结果取到的始终是第二行。

3. 上手第一个例子:把选中的表体行数、列数读出来

3.1 为什么一上来先学读行

“读选中行”几乎是NC65开发里最适合起步的场景。因为后面大部分操作——清数据、改字段、执行编辑公式、弹提示框——都得先知道当前选中了第几行。用户点哪一行,代码就去操作哪一行,这是所有交互逻辑的起点。

另外它也是最好的环境验证用例。这个 API 复杂度低,不涉及状态管理,也没有缓存干扰。你要是能在自己的开发环境里跑通“选中一行,打印出行号、列号和字段值”,说明从导入工程到调试运行的链路已经全部打通了。后面再看别的代码,心态完全不一样。

3.2 完整代码:判断选中并读取行列信息

以下是一段可以直接放进UI类里测试的核心代码,来自那份资料第一条,我稍做了注释:

// 得到当前选中的列(视图列索引) int selCol = getBillCardPanelWrapper().getBillCardPanel() .getBillTable().getSelectedColumn(); // 把视图列索引转换成模型列索引 int colIndex = getBillCardPanel().getBillTable() .convertColumnIndexToModel(selCol); // 得到当前选中的行号,从 0 开始;-1 表示没选中 int iSelRow = getBillCardPanel().getBillTable().getSelectedRow(); // 得到表体一共有多少行 int totalRows = getBillCardPanel().getRowCount(); // 判断是否选中了行 if (iSelRow == -1) { getBillCardPanel().showErrorMessage("请选择表体数据行"); return; }

这段代码做的事情,一句话就能说清:拿到当前表格控件的选中行号和列号,再判断用户是不是真的有选中。整个过程中最隐蔽的坑是列索引。表格控件在界面上显示列顺序并不一定和数据模型里的顺序相同,比如中间有隐藏列时,getSelectedColumn()返回的是用户看到的列位置,直接拿去取数据会对不上。因此要调用convertColumnIndexToModel()做一次转换。

提示:getSelectedRow()返回 -1 表示没有选中任何行,这是一个约定,不要把它当成正常的行号来处理。

3.3 再进一步:读取选中行的某个字段值

只拿行号还不够,实际业务里更常用的是“取这一行某一个字段的值”。比如你要判断“这一行的发货单位填了没有”,代码可以这样写:

// 先拿到当前选中行 int row = getBillCardPanel().getBillTable().getSelectedRow(); // 读这一行指定字段的值 Object value = getBillCardPanel().getBodyValueAt(row, "sys_shipper_id"); // 判断是否为空 if (value == null) { getBillCardPanel().showErrorMessage("请录入发货单位"); return; } // 转成字符串后使用 String shipperId = value.toString();

getBodyValueAt()返回的是 Object 类型,说明它可能为 null,也可以存放任意类型的内容。一个稳定的写法是:先判空,再转型,最后使用。很多新手直接.toString(),结果字段没填时直接抛空指针,这就是血泪经验。

表格控件里的方法名看着长,其实拆开就三部分:先拿到表格,再定位行,最后按字段编码取数据。写多了你会发现,几乎每一段表体操作代码都逃不出这个模式。

3.4 失败时看什么:三个新手最常见的报错

第一个是空指针异常。原因通常是没判断行号是否为 -1,直接把 -1 传进了取值方法。解决方式就是先做选中判断,再做取值操作。如果你一进界面就调用这段代码,界面还没显示完,表格也没有行,很容易踩到。

第二个是列索引越界。原因是把视图列索引直接拿去给数据模型用了,没做转换。解决方式是用convertColumnIndexToModel()把索引转一下再传。遇到隐藏列越界,优先怀疑这里。

第三个是字段值一直取不到,控制台也不报错。原因多半是字段编码写错了,界面上显示的是名称,但代码里getBodyValueAt()用的是数据库里的字段编码,两个不一样。解决方式是去单据模板或数据字典里复制字段编码,不要凭印象手敲。

4. 高频场景拆解:六个最常见的API操作

4.1 设置初始化默认值:一进界面就帮用户填好

很多企业单据,制单人、制单日期、业务类型这些字段明明可以先填好,让用户少点几下。在 NC65 里,你可以重写 UI 类的初始化方法来做这件事:

/** * 初始化卡片界面时加载的默认属性,写在 UI 类中 */ public void setDefaultData() throws Exception { // 单据类型默认值,第二个参数是单据类型编码 getBillCardPanel().setHeadItem("pk_billtype", "CY_DYAPPLYPLAN"); // 制单日期默认取当天 getBillCardPanel().setTailItem("doperatordate", _getDate()); }

setHeadItem()的两个参数分别是字段编码和值,值通常要从业务逻辑里算出来,不要写死。比如制单日期用_getDate()拿当前时间填进去,这样既省了用户操作,又保证数据一致性。界面初始化方法里还可以顺带设置小数位数、隐藏字段、按钮状态等,相当于把这个界面的“出厂设置”集中在一起。

4.2 让字段变成可编辑或只读:交互控制的一种玩法

业务里经常有这样的规则:表头“运输方式”选成 A 的时候,开始日期和结束日期可以填;选成 B 的时候,这两个字段要清空并置灰。这种联动控制,写在编辑后事件里最合适:

public void afterEdit(BillEditEvent evt) { super.afterEdit(evt); // 得到刚编辑的字段编码 String colStr = evt.getKey(); if (colStr.equals("conveytype")) { // 拿到当前运输方式的值 String value = getBillCardPanel().getHeadItem("conveytype").getValue(); if (value != null && !value.trim().equals("")) { if ("A".equals(value)) { // A 方式下,两个日期可编辑 getBillCardPanel().getHeadItem("startdate").setEnabled(true); getBillCardPanel().getHeadItem("enddate").setEnabled(true); } else { // 其他方式下,清空并置灰 getBillCardPanel().setHeadItem("startdate", null); getBillCardPanel().setHeadItem("enddate", null); getBillCardPanel().getHeadItem("startdate").setEnabled(false); getBillCardPanel().getHeadItem("enddate").setEnabled(false); } } } }

这里有个时机问题容易忽略:afterEdit()是“用户编辑完某个字段之后”触发的事件。如果把这段逻辑放到beforeEdit()里,时机就不对。另外,事件方法开头一定要调用父类方法,否则平台的事件处理链路被中断,后面再写什么都没反应。记住这两点,联动控制就不会翻车。

4.3 弹出提示框:给用户一个明确反馈

提示框在NC65里常用的有两种:普通提示框和带确定取消的确认框。代码写法都比较固定:

// 确认框:返回布尔结果,true 表示用户点了确定 boolean ok = MessageDialog.showOkCancelDlg( getBillCardPanel(), "提示", "你确定要下发该单据吗?"); // 普通提示框 MessageDialog.showHintDlg(getBillCardPanel(), "提示", "客户基本档案更新成功");

确认框一般要跟在后续动作前面配合使用。比如点“下发”按钮时,先弹一个确认框,用户点了确定再继续执行下发的逻辑。新手最容易犯的错误,是把确认框当提示框用,弹出来却不管返回结果,这样确认框就失去了意义。我的习惯是:任何确认框,先接住返回值,再按结果走不同的分支。

4.4 报表里加合计行与设置小数位数:让数字更好读

报表底部加一行“合计”,是用户总会提的需求。做法是在查询结果返回之后,手动构造一行,算出合计值,再追加到结果数组的末尾:

// 合计累加,注意这里用的是 UFDouble,不是 double UFDouble total = new UFDouble(0); for (int i = 0; i < results.length; i++) { total = total.add((UFDouble) results[i].getAttributeValue("shuliang")); } // 构造合计行 ReportBaseVO totalVO = new ReportBaseVO(); totalVO.setAttributeValue("port", "合计"); totalVO.setAttributeValue("shuliang", total); // 把合计行追加到结果末尾 CircularlyAccessibleValueObject[] newResults = new CircularlyAccessibleValueObject[results.length + 1]; for (int i = 0; i < results.length; i++) { newResults[i] = results[i]; } newResults[results.length] = totalVO; getReportBase().setBodyDataVO(newResults);

这段逻辑的关键,是累计时用UFDouble而不是 double 或 Float。金额数量这种数据,直接拿 double 累加很容易出现类似 7.999999999 的浮点问题。UFDouble这个平台类型会在内部处理好精度,这也是为什么不建议自己去循环求和的原因。

顺便说一下小数位数的设置。在 UI 类构造方法里可以一次给多个字段指定小数位:

public MyUI() { // 前面是字段编码数组,后面是每个字段的小数位数组 this.setColumnDecimal(new String[]{"carnum", "tonnum", "jsmoney"}, new int[]{0, 2, 0}); }

两个数组长度必须一样,顺序一一对应,否则运行时会出现数组越界。这个对应关系写多了就习惯了,但第一次写时务必数清楚下标。

4.5 清空表体数据和缓冲数据:一次后悔药操作

用户填到一半,希望把整个表体清掉重新来。NC65 里有两个清空入口,看着像,作用范围却不一样:

// 清掉表格控件中已显示的行 getBillCardPanel().getBillModel().clearBodyData(); // 清掉平台维护的缓冲数据 getBufferData().clear();
操作作用范围后果
clearBodyData()界面表格显示的行界面清空,但后台缓冲可能还有旧数据
getBufferData().clear()平台维护的缓冲副本缓冲没了,再保存时旧数据不会回来

最坑的场景,是只调了clearBodyData(),界面看着干净了,但保存时平台又拿缓冲区里的旧数据写回数据库。如果你的业务逻辑要求“界面清了,缓冲也同步”,那就两个一起调用。反过来,你只想清掉临时副本,保留界面显示,就单独调第二个,这种场景比较少,但理解两者的区别能帮你避免数据回滚的尴尬。

4.6 比较时间前后:防止起止日期填反

业务校验里有一个高频检查:起始日期不能大于结束日期。NC65 里日期字段可以直接用compareTo()比较:

// begindate、enddate 是界面上的日期值 if (begindate.compareTo(enddate) > 0) { getBillCardPanel().showErrorMessage("起始日期不能超过结束日期"); return; }

compareTo()的返回值,小于 0 表示前者早,大于 0 表示前者晚。这里最容易踩的坑是日期里带了时间部分。比如开始日期是当天 00:00,结束日期也是当天 23:59,直接用日期类型去比较,有时候会出现“同一天也被判断为大于”的怪现象。稳妥的做法是在比较前统一把日期截断到天,或者用一个工具方法把日期先格式化成人见人爱的年月日再比。

5. 新手避坑指南:最容易栽的五个坑和排查顺序

5.1 编译报错“找不到符号”先别改代码,先查导包

现象:明明把资料里的代码复制过来了,编辑器还报“找不到符号”,连续试了好几个方法都这样。

原因:资料里往往只写了方法调用,没写完整类名对应的导入语句。复制进项目后,编译器不知道你调用的方法属于哪个类。

解决:拿报错的方法名到平台提供的类库里去搜索,找到带完整包路径的类,在文件头部加上 import。加完再编译,一般就过了。如果还报错,再检查一下是否把代码放错了类,比如把界面类的方法放进了事件处理类。

5.2 表体清空了,保存后旧数据又回来

现象:通过清空方法把表格里的行清掉,保存单据再重新打开,旧数据完好无损地躺在表体里。

原因:你清了界面显示的数据,却没同步清缓冲区,也没更新行状态。NC65 保存时是以缓冲区数据和行状态为准的,不是以界面显示为准。

解决:清空表体的同时把缓冲数据也清掉。如果只是想删掉某几行,就用平台的删行方法,而不是把整单数据全部 reset。简单说,要清楚你要的是“清界面”还是“清数据”,两个操作背后对应不同的 API。

5.3 编辑后事件不触发

现象:在 UI 类里写了afterEdit(),但用户改了字段值,代码毫无反应,断点也进不去。

原因:常见两个。一是把 UI 事件方法写进了事件处理类,但平台对 UI 事件的触发和对按钮事件的处理是两个不同渠道。二是重写afterEdit()时没有调用super.afterEdit(evt),父类的事件分发被中断了。

解决:先确认方法写在 UI 类里,再确认方法第一行调用了父类方法。做成这两件事,再打断点调试,一步步看事件有没有进来。新手最容易轻视这一点,觉得 super 方法可有可无,其实它是整个事件链路的入口。

5.4 设了字段不可编辑,界面上还是能改

现象:按业务规则给某个字段调用了setEnabled(false),保存后再次查看,字段仍然可以编辑。

原因:setEnabled(false)只表示失去输入焦点能力,但字段可能通过编辑公式、批量填充等其他机制被赋值。另外如果赋值顺序不对,比如在界面数据加载之前就设了禁用,平台后续还会按模板配置把状态刷回来。

解决:控制可编辑状态时,把逻辑放到合适的时机,比如编辑后事件里,根据当前值去做判断,这样每次用户触发修改时都会重新执行一遍。如果逻辑要求严格,就要在保存前再校验一次,别把界面置灰当成唯一的防线。

5.5 报表合计行算出来是个很长的小数

现象:报表底部合计行显示 7.999999999 这类脏数字,用户看了直接提工单。

原因:你用了 double 或 Float 做累加,浮点数精度问题在金额数量上被放大了。

解决:金额和数量合计用UFDouble的add()方法,不要自己用 double 循环加。另一个经验是,如果报表的数据来源是数据库查询,能直接在 SQL 里用聚合函数算合计的,就别取回来自己在内存里加。让数据库做它擅长的事。

6. 下一步怎么学:从API片段走向第一张完整单据

6.1 把这份API当字典用,而不是当教材背

NC65 开发常见 API 里收录了六十多个场景,但绝大多数不是你每天都会遇到的。比如根据单据状态控制按钮可用性,你可能两个月才碰到一次。这时候,你不需要把所有场景背下来,需要的是当需求落下来那一刻,能快速定位到对应条目,读完代码,再结合当前上下文去改。

所以学习的第一步,是建立索引:看到“初始化默认值”就想起 UI 类;看到“选中行”就想起表格对象;看到“合计行”就想起结果数组加一行。说得直接一点,API 清单是一张地图,不是一本书,翻着用就对了。

6.2 建议的动手顺序

如果你目标是短期内能自己拼出一张简单单据,我建议按这个顺序动手:

  1. 先跑通“读取选中行的行列号”,验证环境能编译、能调试。
  2. 练一遍初始化默认值,理解界面初始化时机的含义。
  3. 做一个自定义按钮,在按钮点击里弹出提示框,串一遍事件处理的完整链路。
  4. 试着把两个字段做联动,控制可编辑状态,理解编辑后事件的时机。
  5. 最后,找一张简单单据,按照界面类到事件处理再到后台保存的路径,把整条链路走通。

每一小步都独立可验证。中间失败了,就去查第 5 章那五个常见问题,基本能定位到原因。

6.3 一个我保留至今的习惯

我后来每次写 NC65 代码,都会强制自己过一遍三个问题:读到的数据来自表头还是表体;这段操作发生在哪个类里;当前行的数据状态是新增、修改还是删除。这三个问题对任何一个 API 调用都适用。刚入门的头两周,我在这上面踩了不少坑,后来形成肌肉记忆,调代码的时间少了一半。希望这套方法能帮到你,少走我走过的弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询