有时我很无聊,并且配备了调试器,然后开始研究不同的程序。 这次,我的选择落在Excel上,希望弄清楚它如何处理行的高度,它存储的内容,如何考虑单元格范围的高度等。 我解析了Excel 2010(excel.exe,32位,版本14.0.4756.1000,SHA1 a805cf60a5542f21001b0ea5d142d1cd0ee00b28)。
让我们从理论开始
如果转到Microsoft Office的VBA文档,则可以看到行高度可以通过两个属性以一种或另一种方式获得:
- RowHeight-返回或设置指定范围内的第一行的高度,以磅为单位。 读/写Double;
- 高度 -返回一个Double值,该值表示范围的高度(以磅为单位)。 只读的。
如果您也在此处查看: Excel规范和限制 。 您可以找到最大行高为409点。 不幸的是,这远非唯一的Microsoft官方文件具有误导性的情况。 实际上,在Excel代码中,最大行高设置为2047像素,以点为1535.25。 且最大字体大小为409.55磅。 仅通过在VBA / Interop中分配行就不可能获得如此高的行,但是您可以一行,在其第一个单元格中设置Cambria Math字体,并将字体大小设置为409.55点。 然后,Excel及其狡猾算法将根据单元格格式计算行高,获取超过2047像素(相信单词)的数字,并将行设置为最大可能的高度。 如果通过UI询问该系列的高度,则Excel会认为该高度是409.5点,但是如果您通过VBA请求该系列的高度,则您会得到1535.25点,等于2047像素。 是的,保存文档后,高度仍将降至409.5点。 可以在以下视频中看到此操作: http : //recordit.co/ivnFEsELLI
我在上一段中提到像素是有原因的。 Excel实际上以整数形式存储和计算单元格的大小(通常,它会尽可能多地执行整数操作)。 最常见的是像素乘以某个因子。 有趣的是,Excel以普通分数的形式存储外观比例,例如,比例比例75%将被存储为两个数字3和4。当需要显示行时,Excel将行高作为整数像素,乘以3并除以乘以4,但是他将在最后执行此操作,由此产生的效果是,所有内容均以小数形式考虑。 要验证这一点,请在VBA中编写以下代码:
w.Rows(1).RowHeight = 75.375 Debug.Print w.Rows(1).Height
VBA会给出75,因为 75.375像素将为100.5像素,Excel无法承受,并且会将小数部分最多减少100像素。 当VBA要求以点为单位的行高时,Excel会诚实地将100像素转换为点并返回75。
原则上,我们已经用C#编写了一个类来描述有关行高的信息:
class RowHeightInfo { public ushort Value { get; set; }
您现在必须说出我的话,但是在Excel中,行高度是以这种方式存储的。 也就是说,如果指定行高为75点,将以像素为单位,则为100,然后将400存储在Value中。我还没有完全弄清楚Flags中的所有位的含义(要弄清楚Flags的值很困难而且很长),但是我肯定知道手动设置高度的行设置为0x4000,隐藏行的设置为0x2000。 通常,对于具有手动设置的高度的可见行,Flags通常等于0x4005,对于根据Flags格式计算高度的行,其值为0xA或0x800E。
我们问行高
现在,原则上,现在您可以看一下excel.exe中的方法,该方法通过其索引返回行高(这要归功于HexRays的漂亮代码):
int __userpurge GetRowHeight@<eax>(signed int rowIndex@<edx>, SheetLayoutInfo *sheetLayoutInfo@<esi>, bool flag) { RowHeightInfo *rowHeightInfo;
dword1A0是什么,我仍然没有弄清楚。 找不到设置此标志的地方:(
对我来说,defaultRowDelta2仍然是个谜。 当excel根据格式计算行高时,它表示为两个数字的总和。 defaultRowDelta2是此总和中第二个数字(标准行高度)。 flag参数的值也很神秘,因为 无论我在哪里看到在flag中传递给此方法的调用都传递false。
SheetLayoutInfo类也出现在此方法中。 我之所以这样命名,是因为它存储了大量有关工作表外观的信息。 在SheetLayoutInfo中,有如下字段:
- DefaultFullRowHeightMul4-标准行高;
- MinRowIndexNonDefault-第一行的索引,高度不同于标准行;
- MaxRowIndexNonDefault-最后一个序列之后的序列的索引,该序列的高度不同于标准;
- DefaultRowDelta2与标准行高的总和相同。
- GroupIndexDelta-稍后会详细介绍
原则上,这种方法的逻辑是可以理解的:
- 如果系列的索引小于非标准高度的第一个索引,则返回标准;否则,返回标准。
- 如果系列的索引大于具有非标准高度的最后一个索引,则返回标准;否则,返回标准。
- 否则,我们将从GetRowHeightCore方法获取该系列的rowHeightInfo对象;
- 如果rowHeightInfo == null,则返回标准行高。
- 带有标志的魔术,但是通常,如果没有手动设置行高,我们将返回rowHeightInfo.Value中的内容,并在响应中设置第16位。
如果用C#重写此代码,则会得到如下所示的内容:
const ulong HiddenRowMask = 0x2000; const ulong CustomHeightMask = 0x4000; const ushort DefaultHeightMask = 0x8000; public static ushort GetRowHeight(int rowIndex, SheetLayoutInfo sheetLayoutInfo) { ushort defaultHeight = (ushort) (sheetLayoutInfo.DefaultFullRowHeightMul4 | (~(sheetLayoutInfo.DefaultRowDelta2 >> 14 << 15) & DefaultHeightMask)); if (rowIndex < sheetLayoutInfo.MinRowIndexNonDefault) return defaultHeight; if (rowIndex >= sheetLayoutInfo.MaxRowIndexNonDefault) return defaultHeight; RowHeightInfo rowHeightInfo = GetRowHeightCore(sheetLayoutInfo, rowIndex); if (rowHeightInfo == null) return defaultHeight; ushort result = 0; if ((rowHeightInfo.Flags & HiddenRowMask) == 0) result = rowHeightInfo.Value; if ((rowHeightInfo.Flags & CustomHeightMask) == 0) result |= DefaultHeightMask; return result; }
现在,您可以看一下GetRowHeightCore内部发生的情况:
RowHeightInfo *__fastcall GetRowHeightCore(SheetLayoutInfo *sheetLayoutInfo, signed int rowIndex) { RowHeightInfo *result;
- 再次,在开始时,Excel将检查该系列的索引是否在高度已修改的行之间,如果不是,则返回null。
- 查找所需的行组;如果没有这样的组,则返回null。
- 获取组中系列的索引。
- 此外,通过系列的索引,它找到RowHeightInfo类的所需对象。 wordBA,wordBC,wordB8-一些常量。 它们仅随着历史而改变。 原则上,它们不会影响对算法的理解。
在这里值得偏离本主题,并更多地介绍RowsGroupInfo。 Excel以16件为一组存储RowHeightInfo,其中以RowsGroupInfo类表示的第i组将存储有关从i×16到i×16 + 15(含)的行的信息。
但是RowsGroupInfo中的行高信息以某种不寻常的方式存储。 最有可能是由于需要在Excel中维护历史记录。
RowsGroupInfo中有三个重要字段:Indices,HeightInfos和RowsCount,第二个在上面的代码中不可见(它应该在此行中:(rowsGroupInfo + 8×(...)),因为rowInfoIndex可以采用非常不同的值,我已经看过1000多个,而且我不知道如何在IDA中设置这样的结构。RowsCount字段没有出现在上面的代码中,但这是组中存储了多少个真正的非标准行。
另外,在SheetLayoutInfo中有GroupIndexDelta-组的实际索引与具有更改的行高的第一个组的索引之间的差。
索引字段存储组中系列的每个索引的RowHeightInfo偏移量。 它们按顺序存储在此处,但是在HeightInfos中,RowHeightInfo已按更改顺序存储。
假设我们有一个新的空白表,并且以某种方式更改了行号23的高度。此行位于第二组的16行中,然后:
- Excel将确定该系列的组索引。 在当前情况下,索引将为1,并将更改GroupIndexDelta = -1。
- 为一系列行创建RowsGroupInfo类的对象,并将其放在索引0(sheetLayoutInfo-> GroupIndexDelta + 1)下的sheetLayoutInfo-> RowsGroups中;
- 在RowsGroupInfo中,Excel将为16个4字节索引分配内存,分别用于RowsCount,wordBA,wordBC和wordB8等。
- 然后,Excel通过按位与运算(比采用除法的其余部分要快得多)来计算组中系列的索引:rowIndex&0xF。 该组中的期望索引为:23&0xF = 7;
- 之后,Excel获得索引7的偏移量:offset = Indices [7]。 如果offset = 0,则Excel在RowsGroupInto的末尾分配8个字节,将RowsCount增加1,然后将新的偏移量写入索引[7]。 无论如何,最后,Excel都会在RowsGroupInfo中的偏移处写入有关新行高和标志的信息。
C#本身中的RowsGroupInfo类看起来像这样:
class RowsGroupInfo { public int[] Indices { get; } public List<RowHeightInfo> HeightInfos { get; } public RowsGroupInfo() { Indices = new int[SheetLayoutInfo.MaxRowsCountInGroup]; HeightInfos = new List<RowHeightInfo>(); for (int i = 0; i < SheetLayoutInfo.MaxRowsCountInGroup; i++) { Indices[i] = -1; } } }
GetRowHeightCore方法将如下所示:
static RowHeightInfo GetRowHeightCore(SheetLayoutInfo sheetLayoutInfo, int rowIndex) { if (rowIndex < sheetLayoutInfo.MinRowIndexNonDefault || rowIndex >= sheetLayoutInfo.MaxRowIndexNonDefault) return null; RowsGroupInfo rowsGroupInfo = sheetLayoutInfo.RowsGroups[sheetLayoutInfo.GroupIndexDelta + (rowIndex >> 4)]; if (rowsGroupInfo == null) return null; int rowInfoIndex = rowsGroupInfo.Indices[rowIndex & 0xF]; return rowInfoIndex != -1 ? rowsGroupInfo.HeightInfos[rowInfoIndex] : null; }
这就是SetRowHeight的样子(我没有从excel.exe中列出其代码):
public static void SetRowHeight(int rowIndex, ushort newRowHeight, ushort flags, SheetLayoutInfo sheetLayoutInfo) { sheetLayoutInfo.MaxRowIndexNonDefault = Math.Max(sheetLayoutInfo.MaxRowIndexNonDefault, rowIndex + 1); sheetLayoutInfo.MinRowIndexNonDefault = Math.Min(sheetLayoutInfo.MinRowIndexNonDefault, rowIndex); int realGroupIndex = rowIndex >> 4; if (sheetLayoutInfo.RowsGroups.Count == 0) { sheetLayoutInfo.RowsGroups.Add(null); sheetLayoutInfo.GroupIndexDelta = -realGroupIndex; } else if (sheetLayoutInfo.GroupIndexDelta + realGroupIndex < 0) { int bucketSize = -(sheetLayoutInfo.GroupIndexDelta + realGroupIndex); sheetLayoutInfo.RowsGroups.InsertRange(0, new RowsGroupInfo[bucketSize]); sheetLayoutInfo.GroupIndexDelta = -realGroupIndex; } else if (sheetLayoutInfo.GroupIndexDelta + realGroupIndex >= sheetLayoutInfo.RowsGroups.Count) { int bucketSize = sheetLayoutInfo.GroupIndexDelta + realGroupIndex - sheetLayoutInfo.RowsGroups.Count + 1; sheetLayoutInfo.RowsGroups.AddRange(new RowsGroupInfo[bucketSize]); } RowsGroupInfo rowsGroupInfo = sheetLayoutInfo.RowsGroups[sheetLayoutInfo.GroupIndexDelta + realGroupIndex]; if (rowsGroupInfo == null) { rowsGroupInfo = new RowsGroupInfo(); sheetLayoutInfo.RowsGroups[sheetLayoutInfo.GroupIndexDelta + realGroupIndex] = rowsGroupInfo; } int rowInfoIndex = rowsGroupInfo.Indices[rowIndex & 0xF]; RowHeightInfo rowHeightInfo; if (rowInfoIndex == -1) { rowHeightInfo = new RowHeightInfo(); rowsGroupInfo.HeightInfos.Add(rowHeightInfo); rowsGroupInfo.Indices[rowIndex & 0xF] = rowsGroupInfo.HeightInfos.Count - 1; } else { rowHeightInfo = rowsGroupInfo.HeightInfos[rowInfoIndex]; } rowHeightInfo.Value = newRowHeight; rowHeightInfo.Flags = flags; }
一点练习
在上面的示例之后,更改了第23行的高度,Excel将看起来像这样(我将第23行的高度设置为75点):
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 23
- GroupIndexDelta = -1
- RowsGroups Count = 1
- [0] RowsGroupInfo
- HeightInfos计数= 1
- [0] RowHeightInfo
- 标志= 0x4005
- 价值= 100
- 指标
- [0] = -1
- [1] = -1
- [2] = -1
- [3] = -1
- [4] = -1
- [5] = -1
- [6] = -1
- [7] = 0
- [8] = -1
- [9] = -1
- [10] = -1
- [11] = -1
- [12] = -1
- [13] = -1
- [14] = -1
- [15] = -1
在这里和下面的示例中,我将布局一个示意图,说明Excel内存中的数据是如何通过自写类在Visual Studio中生成的,因为从内存中直接转储的信息不多。
现在,让我们尝试隐藏第23行。为此,将Flags设置为0x2000。 我们将改变生活的记忆。 结果可以在以下视频中看到:
http :
//recordit.co/79vYIbwbzB 。
每当您隐藏行时,Excel都会执行相同的操作。
现在,我们不通过行格式来显式设置行高。 让单元格A20中的字体变为40点的高度,然后以点为单位的单元格高度将为45.75,并且在Excel内存中将是这样的:
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 20
- GroupIndexDelta = -1
- RowsGroups Count = 1
- [0] RowsGroupInfo
- HeightInfos计数= 2
- [0] RowHeightInfo
- 标志= 0x4005
- 价值= 100
- [1] RowHeightInfo
- 标志= 0x800E
- 价值= 244
- 指标
- [0] = -1
- [1] = -1
- [2] = -1
- [3] = -1
- [4] = 1
- [5] = -1
- [6] = -1
- [7] = 0
- [8] = -1
- [9] = -1
- [10] = -1
- [11] = -1
- [12] = -1
- [13] = -1
- [14] = -1
- [15] = -1
您可能会注意到,如果不是标准行,Excel总是存储行高。 即使未明确设置高度,而是根据单元格或格式的内容计算出的高度,Excel仍然会对其进行一次计算并将结果放入适当的组中。
我们处理行的插入/删除
解析插入/删除行时会发生什么会很有趣。 在excel.exe中对应的代码并不难找到,但是没有反汇编的欲望,您可以看一下其中的一部分:
sub_305EC930标志a5确定当前正在执行哪个操作。
int __userpurge sub_305EC930@<eax>(int a1@<eax>, int a2@<edx>, int a3@<ecx>, int a4, int a5, int a6) { int v6;
此外,从外观上,您可以大致了解那里发生的事情,其余的可以通过间接标志来结束。
我们尝试识别这些间接功能。 首先,以随机顺序将第16行的高度设置为64(含)。 然后,在索引39下的行的前面插入新行。 新行将复制第38行的高度。
让我们看一下添加系列之前和之后的系列分组中的信息,我重点介绍了粗体的区别:
在添加行之前 | 添加一行后 |
---|
第一组的排量: | 第一组的排量: |
0E,04、07、00、05、0A,09、0F,03、06、08、0D,01、0B,0C,02 | 0E,04、07、00、05、0A,09、0F,03、06、08、0D,01、0B,0C,02 |
第一组中各行的高度值: | 第一组中各行的高度值: |
05、2B,35、45、4B,50、5B,6B,7B,8B,A5,AB,B0,B5,E0、100 | 05、2B,35、45、4B,50、5B,6B,7B,8B,A5,AB,B0,B5,E0、100 |
第二组位移: | 第二组位移: |
06、02、0E, 09、01、07、0F,0C ,00、0A,04、0B, 03、08、0D,05 | 06、02、0E, 09、01、07、0F ,05、0C,00、0A,04、0B, 03、08、0D |
第二组中的行的高度值: | 第二组中的行的高度值: |
10、15、20、25、30、75、85、90、9B,A0,C5,CB,D0,D5,E5,F0 | 10、15、20、25、30,F0、85、90、9B,A0,C5,CB,D0,D5,E5,F0 |
第三组位移: | 第三组位移: |
0C,08、0E,07、0A,01、06、0F,09、0D,00、05、0B,02、04、03 | 03,0C,08,0E,07,0A,01,06,0F,09,0D,00,05,0B,02,04 |
第三组中的行的高度值: | 第三组中的行的高度值: |
0B,1B,3B,40、55、60、65、70、80、95,BB,C0,DB,EB,F5,FB | 0B,1B,3B,75、55、60、65、70、80、95,BB,C0,DB,EB,F5,FB |
第四组的偏移量: | 第四组的偏移量: |
_ | 00 |
第四组中的行的高度值: | 第四组中的行的高度值: |
_ | 40 |
这是预期的:Excel将第二行插入索引为7(39&0xF)的新行,其偏移量为0x05,将行高度复制到索引6,而最后一行(偏移量为05)被推到下一行组,然后从那里将最后一行推到第四行,依此类推。
现在让我们看看如果删除第29行会发生什么。
删除行之前 | 删除行后 |
---|
第一组的排量: | 第一组的排量: |
0E,04、07、00、05、0A,09、0F,03、06、08、0D, 01、0B,0C,02 | 0E,04、07、00、05、0A,09、0F,03、06、08、0D,01、0C ,02、0B |
第一组中各行的高度值: | 第一组中各行的高度值: |
05、2B,35、45、4B,50、5B,6B,7B,8B,A5, AB ,B0,B5,E0、100 | 05、2B,35、45、4B,50、5B,6B,7B,8B,A5、85,B0,B5,E0、100 |
第二组位移: | 第二组位移: |
06,02,0E,09,01,07,0F,05,0C,00,0A,04,0B,03,08,0D | 02,0E,09,01,07,0F,05,0C,00,0A,04,0B,03,08,0D, 06 |
第二组中的行的高度值: | 第二组中的行的高度值: |
10、15、20、25、30,F0、85、90、9B,A0,C5,CB,D0,D5,E5,F0 | 10、15、20、25、30,F0、75、90、9B,A0,C5,CB,D0,D5,E5,F0 |
第三组位移: | 第三组位移: |
03,0C,08,0E,07,0A,01,06,0F,09,0D,00,05,0B,02,04 | 0C,08、0E,07、0A,01、06、0F,09、0D,00、05、0B,02、04、03 |
第三组中的行的高度值: | 第三组中的行的高度值: |
0B,1B,3B,75、55、60、65、70、80、95,BB,C0,DB,EB,F5,FB | 0B,1B,3B,40、55、60、65、70、80、95,BB,C0,DB,EB,F5,FB |
第四组的偏移量: | 第四组的偏移量: |
00 | 00 |
第四组中的行的高度值: | 第四组中的行的高度值: |
40 | 50 |
原则上,当删除一行时,会发生反向操作。 在这种情况下,第四组继续存在,并且该行的高度值被标准高度填充,并带有相应的标志-0x8005。
这些数据足以在C#中重现此算法:
插入行 public static void InsertRow(SheetLayoutInfo sheetLayoutInfo, int rowIndex) { if (rowIndex >= sheetLayoutInfo.MaxRowIndexNonDefault) return; RowHeightInfo etalonRowHeightInfo = GetRowHeightCore(sheetLayoutInfo, rowIndex); RowHeightInfo newRowHeightInfo = etalonRowHeightInfo != null ? etalonRowHeightInfo.Clone() : CreateDefaultRowHeight(sheetLayoutInfo); int realGroupIndex = (rowIndex + 1) >> 4; int newRowInGroupIndex = (rowIndex + 1) & 0xF; int groupIndex; for (groupIndex = realGroupIndex + sheetLayoutInfo.GroupIndexDelta; groupIndex < sheetLayoutInfo.RowsGroups.Count; groupIndex++, newRowInGroupIndex = 0) { if (groupIndex < 0) continue; if (groupIndex == SheetLayoutInfo.MaxGroupsCount) break; RowsGroupInfo rowsGroupInfo = sheetLayoutInfo.RowsGroups[groupIndex]; if (rowsGroupInfo == null) { if ((newRowHeightInfo.Flags & CustomHeightMask) == 0) continue; rowsGroupInfo = new RowsGroupInfo(); sheetLayoutInfo.RowsGroups[groupIndex] = rowsGroupInfo; } int rowInfoIndex = rowsGroupInfo.Indices[newRowInGroupIndex]; RowHeightInfo lastRowHeightInGroup; if (rowInfoIndex == -1 || rowsGroupInfo.HeightInfos.Count < SheetLayoutInfo.MaxRowsCountInGroup) { lastRowHeightInGroup = GetRowHeightCore(sheetLayoutInfo, ((groupIndex - sheetLayoutInfo.GroupIndexDelta) << 4) + SheetLayoutInfo.MaxRowsCountInGroup - 1); Array.Copy(rowsGroupInfo.Indices, newRowInGroupIndex, rowsGroupInfo.Indices, newRowInGroupIndex + 1, SheetLayoutInfo.MaxRowsCountInGroup - 1 - newRowInGroupIndex); rowsGroupInfo.HeightInfos.Add(newRowHeightInfo); rowsGroupInfo.Indices[newRowInGroupIndex] = rowsGroupInfo.HeightInfos.Count - 1; } else { int lastIndex = rowsGroupInfo.Indices[SheetLayoutInfo.MaxRowsCountInGroup - 1]; lastRowHeightInGroup = rowsGroupInfo.HeightInfos[lastIndex]; Array.Copy(rowsGroupInfo.Indices, newRowInGroupIndex, rowsGroupInfo.Indices, newRowInGroupIndex + 1, SheetLayoutInfo.MaxRowsCountInGroup - 1 - newRowInGroupIndex); rowsGroupInfo.HeightInfos[lastIndex] = newRowHeightInfo; rowsGroupInfo.Indices[newRowInGroupIndex] = lastIndex; } newRowHeightInfo = lastRowHeightInGroup ?? CreateDefaultRowHeight(sheetLayoutInfo); } if ((newRowHeightInfo.Flags & CustomHeightMask) != 0 && groupIndex != SheetLayoutInfo.MaxGroupsCount) { SetRowHeight(((groupIndex - sheetLayoutInfo.GroupIndexDelta) << 4) + newRowInGroupIndex, newRowHeightInfo.Value, newRowHeightInfo.Flags, sheetLayoutInfo); } else { sheetLayoutInfo.MaxRowIndexNonDefault = Math.Min(sheetLayoutInfo.MaxRowIndexNonDefault + 1, SheetLayoutInfo.MaxRowsCount); } }
删除行 public static void RemoveRow(SheetLayoutInfo sheetLayoutInfo, int rowIndex) { if (rowIndex >= sheetLayoutInfo.MaxRowIndexNonDefault) return; int realGroupIndex = rowIndex >> 4; int newRowInGroupIndex = rowIndex & 0xF; int groupIndex; for (groupIndex = realGroupIndex + sheetLayoutInfo.GroupIndexDelta; groupIndex < sheetLayoutInfo.RowsGroups.Count; groupIndex++, newRowInGroupIndex = 0) { if (groupIndex < -1) continue; if (groupIndex == -1) { sheetLayoutInfo.RowsGroups.Insert(0, null); sheetLayoutInfo.GroupIndexDelta++; groupIndex = 0; } if (groupIndex == SheetLayoutInfo.MaxGroupsCount) break; var newRowHeightInfo = groupIndex == SheetLayoutInfo.MaxGroupsCount - 1 ? null : GetRowHeightCore(sheetLayoutInfo, (groupIndex - sheetLayoutInfo.GroupIndexDelta + 1) << 4); RowsGroupInfo rowsGroupInfo = sheetLayoutInfo.RowsGroups[groupIndex]; if (rowsGroupInfo == null) { if (newRowHeightInfo == null || (newRowHeightInfo.Flags & CustomHeightMask) == 0) continue; rowsGroupInfo = new RowsGroupInfo(); sheetLayoutInfo.RowsGroups[groupIndex] = rowsGroupInfo; } if (newRowHeightInfo == null) { newRowHeightInfo = CreateDefaultRowHeight(sheetLayoutInfo); } int rowInfoIndex = rowsGroupInfo.Indices[newRowInGroupIndex]; if (rowInfoIndex == -1) { for (int i = newRowInGroupIndex; i < SheetLayoutInfo.MaxRowsCountInGroup - 1; i++) { rowsGroupInfo.Indices[i] = rowsGroupInfo.Indices[i + 1]; } rowsGroupInfo.HeightInfos.Add(newRowHeightInfo); rowsGroupInfo.Indices[SheetLayoutInfo.MaxRowsCountInGroup - 1] = rowsGroupInfo.HeightInfos.Count - 1; } else { for(int i = newRowInGroupIndex; i < rowsGroupInfo.HeightInfos.Count - 1; i++) { rowsGroupInfo.Indices[i] = rowsGroupInfo.Indices[i + 1]; } rowsGroupInfo.Indices[rowsGroupInfo.HeightInfos.Count - 1] = rowInfoIndex; rowsGroupInfo.HeightInfos[rowInfoIndex] = newRowHeightInfo; } } if(rowIndex <= sheetLayoutInfo.MinRowIndexNonDefault) { sheetLayoutInfo.MinRowIndexNonDefault = Math.Max(sheetLayoutInfo.MinRowIndexNonDefault - 1, 0); } }
您可以在GitHub上找到上述所有代码
结论
这不是Excel代码第一次对有趣的技巧感到惊讶。 这次,我发现了他如何存储有关行高的信息。 如果社区感兴趣,那么在下一篇文章中,我将展示Excel如何考虑像元范围的高度(扰流器:有些类似于SQRT分解,但由于某种原因而没有求和缓存),您可以看到它如何将整数按比例缩放。