NPOI 版本信息:
Binary: 2.1.3.1
Source Code: https://github.com/tonyqus/npoi (2015-06-15)
本期问题:Cell Comment
1. Comment Count
NPOI 限制了每个 HSSFSheet 最大 Comment 数量:1024。(不知道为什么要这么做)(貌似 POI 中已经修复了这个 bug)
这会导致:
1.1 如果添加的 Comment 数超过 1024,会引起 NPOI 内部抛出异常 (ArgumentException)。
a) HSSFComment 有个属性 NoteRecord (_note,NPOI.HSSF.Record.NoteRecord),记录了 Comment 的一些信息。
b) HSSFComment 创建后,其属性 ShapeId (int) 会被赋值。
c) 在这个赋值的过程中又会对 NoteRecord 的属性 ShapeId 赋值 (对1024取余的值):
public override int ShapeId
{
get { return base.ShapeId; }
set
{
//...
_note.ShapeId = (value % 1024);
}
}
d) NPOI 内部 (EscherAggregate)用 Dictionary 维护这些 NoteRecord。其中 Key 的值是 NoteRecord 的属性 ShapeId。(这就限定了最大数量:1024)。
e) 当把 NoteRecord 添加到 Dictionary(tailRec)中时,会先判断是否已存在相同的 Key。如果已存在,就往 Dictionary 中添加,这就导致抛出异常。(为什么要这么做啊)
internal void AddTailRecord(NoteRecord note)
{
if (tailRec.ContainsKey(note.ShapeId))
tailRec.Add(note.ShapeId, note);
else
tailRec[note.ShapeId] = note;
}
1.2 如果从现有文件载入 Sheet,且 Sheet 中 Comment 数量超过1024,会导致在取1024前的 Comment 时取到 null,在1024后的 Comment 时拿到的是1024前的某个 Comment。
a) NPOI 载入 Sheet 时会读取所有 NoteRecord(包括其它 Record),存入List。在创建 NoteRecord 实例时,从文件数据流中读取的 ShapeId 可能会重复,即:两个 NoteRecord 有相同的 ShapeId。而且 ShapeId 都是小于 1024 的非负整数。暂时还不了解原因(难道是对文件数据流的解析有问题?)。
b) 首次获取 Cell Comment 时,准确的说是首次创建 HSSFSheet 的 _patriarch (HSSFPatriarch)时,会将这些 NoteRecord 存入 tailRec。此时可能因为 NoteRecord 的 ShapeId 有重复,导致
后面添加的项可能会替换之前的项。
c) 创建 HSSFPatriarch 实例的同时,会创建 HSSFComment 实例(HSSFShapeFactory.CreateShapeTree)。此时会根据相应 Record 的数据,从 tailRec 里找到对应的 NoteRecord,并将其传给 HSSFComment 的构造函数。查找 NoteRecord 时是根据 NoteRecord 的 ShapeId 属性进行判断。所以由上一步可知,可能发生 HSSFComment 与 NoteRecord 错乱的情况。
c) 获取某个 Cell 的 Comment 时,会搜索所有 Comment,根据 Cell 的 Row 和 Column 是否与 Comment 的 Row 和 Column 相等。而 Comment 的 Row 和 Column 的值是从它对应的 NoteRecord 的 Row 和 Column 取的。由上一步可知,可能目标的 Comment 无法与该 Cell 匹配,从而得到 null;或其它 Comment 与该 Cell 匹配成功,得到错位的 Comment。
另:XSSF 中处理 Comment 的机制不同,不存在上述问题。
2. Comment Size
NPOI 中要设置 Comment 的大小,暂时只能在创建 Comment 时通过传入的 IClientAnchor 设置其大小。
public interface IDrawing
{
//...
IComment CreateCellComment(IClientAnchor anchor);
//...
}
通过设置 IClientAnchor 的四个属性可以设置 Comment 的大小。
public interface IClientAnchor
{
//...
int Col1 { get; set; }
int Col2 { get; set; }
int Row1 { get; set; }
int Row2 { get; set; }
//...
}
这样设置的 Comment 大小与这四个属性指示的这些单元格的大小相关。如果这些单元格变宽,Comment 的宽度也会相应增加(高度也一样)。无法设置固定大小。
(Microsoft.Office.Interop.Excel 中可以通过 Comment 的 Shape 属性设置固定的大小)