A veces me aburro y, armado con un depurador, empiezo a investigar diferentes programas. Esta vez, mi elección recayó en Excel y quería averiguar cómo maneja las alturas de las filas, qué almacena, cómo considera la altura del rango de celdas, etc. Analicé Excel 2010 (excel.exe, 32 bits, versión 14.0.4756.1000, SHA1 a805cf60a5542f21001b0ea5d142d1cd0ee00b28).
Comencemos con la teoría.
Si recurre a la documentación de VBA para Microsoft Office, puede ver que la altura de la fila se puede obtener de una forma u otra a través de dos propiedades:
- RowHeight : devuelve o establece la altura de la primera fila en el rango especificado, medido en puntos. Leer / escribir doble;
- Altura : devuelve un valor Doble que representa la altura, en puntos, del rango. Solo lectura
Y si miras también aquí: especificaciones y límites de Excel . Puede encontrar que la altura máxima de la fila es de 409 puntos. Esto, desafortunadamente, está lejos de ser el único caso cuando los documentos oficiales de Microsoft son un poco engañosos. De hecho, en el código de Excel, la altura máxima de la fila se establece en 2047 píxeles, que será 1535.25 en puntos. Y el tamaño máximo de fuente es 409.55 puntos. Es imposible obtener una fila de una altura tan grande simplemente asignándola en VBA / Interop, pero puede tomar una fila, establecer la fuente Cambria Math en su primera celda y establecer el tamaño de la fuente en 409.55 puntos. Luego, Excel con su algoritmo astuto calculará la altura de la fila en función del formato de la celda, obtendrá un número que supere los 2047 píxeles (crea la palabra) y establecerá la fila en la altura máxima posible. Si pregunta la altura de esta serie a través de la interfaz de usuario, Excel mentirá que la altura es de 409.5 puntos, pero si solicita la altura de la serie a través de VBA, obtendrá 1535.25 puntos honestos, lo que equivale a 2047 píxeles. Es cierto que después de guardar el documento, la altura seguirá bajando a 409.5 puntos. Esta manipulación se puede observar aquí en este video: http://recordit.co/ivnFEsELLI
Mencioné los píxeles en el párrafo anterior por una razón. Excel en realidad almacena y calcula el tamaño de las celdas en enteros (generalmente hace todo lo posible en enteros). Muy a menudo, estos son píxeles multiplicados por algún factor. Curiosamente, Excel almacena la escala de apariencia en forma de fracción ordinaria, por ejemplo, una escala del 75% se almacenará como dos números 3 y 4. Y cuando sea necesario mostrar la fila, Excel tomará la altura de la fila como un número entero de píxeles, multiplique por 3 y divida por 4. Pero él realizará esta operación ya al final, a partir de esto se crea el efecto de que todo se considera en números fraccionarios. Para verificar esto, escriba el siguiente código en VBA:
w.Rows(1).RowHeight = 75.375 Debug.Print w.Rows(1).Height
VBA dará 75 porque 75.375 píxeles serán 100.5 píxeles, y Excel no puede permitírselo y dejará caer la parte fraccional hasta 100 píxeles. Cuando VBA solicita la altura de la fila en puntos, Excel traduce honestamente 100 píxeles en puntos y devuelve 75.
En principio, ya hemos llegado a escribir una clase en C # que describirá información sobre la altura de la fila:
class RowHeightInfo { public ushort Value { get; set; }
Deberá tomar mi palabra por ahora, pero en Excel, la altura de la fila se almacena de esa manera. Es decir, si se especifica que la altura de la fila es de 75 puntos, será de 100 en píxeles, luego se almacenará 400 en Valor. No he entendido completamente lo que significan todos los bits en Banderas (es difícil y largo calcular los valores de las banderas), pero sé que 0x4000 se establece para filas cuya altura se establece manualmente, y 0x2000 - se establece para filas ocultas. En general, para las filas visibles con una altura establecida manualmente, Flags suele ser igual a 0x4005, y para las filas en las que la altura se calcula según el formato de Banderas, es 0xA o 0x800E.
Preguntamos la altura de la fila
Ahora, en principio, puede echar un vistazo al método de excel.exe, que devuelve la altura de la fila por su índice (gracias a HexRays por el hermoso código):
int __userpurge GetRowHeight@<eax>(signed int rowIndex@<edx>, SheetLayoutInfo *sheetLayoutInfo@<esi>, bool flag) { RowHeightInfo *rowHeightInfo;
Qué es dword1A0, todavía no lo he descubierto. no se pudo encontrar un lugar donde se establezca esta bandera :(
Lo que es defaultRowDelta2 para mí también sigue siendo un misterio. Cuando Excel calcula la altura de la fila en función del formato, la representa como la suma de dos números. defaultRowDelta2 es el segundo número de esta suma para la altura de fila estándar. El valor del parámetro indicador también es misterioso, porque donde vi una llamada a este método en flag pasó falso.
La clase SheetLayoutInfo también aparece en este método. Lo llamé así, porque almacena una gran cantidad de todo tipo de información sobre el aspecto de la hoja. En SheetLayoutInfo hay campos como:
- DefaultFullRowHeightMul4: altura de fila estándar;
- MinRowIndexNonDefault: el índice de la primera fila, en el que la altura difiere del estándar;
- MaxRowIndexNonDefault: índice de la serie que sigue a la última, en la que la altura difiere del estándar;
- DefaultRowDelta2 es la misma parte de la suma de la altura de fila estándar.
- GroupIndexDelta - más sobre esto más adelante
En principio, la lógica de este método es comprensible:
- Si el índice de la serie es menor que el primero con una altura no estándar, devuelva el estándar;
- Si el índice de la serie es mayor que el anterior con una altura no estándar, devuelva el estándar;
- De lo contrario, obtenemos el objeto rowHeightInfo para la serie del método GetRowHeightCore;
- Si rowHeightInfo == nulo devuelve el alto de fila estándar;
- Hay magia con banderas, pero en general, devolvemos lo que está en rowHeightInfo.Value y establecemos el bit 16 en la respuesta si la altura de la fila no se configuró manualmente.
Si reescribe este código en C #, obtienes algo como lo siguiente:
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; }
Ahora puede echar un vistazo a lo que sucede dentro de GetRowHeightCore:
RowHeightInfo *__fastcall GetRowHeightCore(SheetLayoutInfo *sheetLayoutInfo, signed int rowIndex) { RowHeightInfo *result;
- Nuevamente, al principio, Excel verifica si el índice de la serie se encuentra entre las filas con una altura modificada y, si no, devuelve nulo.
- Encuentra el grupo deseado de filas; si no existe dicho grupo, devuelve nulo.
- Obtiene el índice de la serie en el grupo.
- Además, por el índice de la serie, encuentra el objeto deseado de la clase RowHeightInfo. wordBA, wordBC, wordB8: algunas constantes. Cambian solo con la historia. En principio, no afectan la comprensión del algoritmo.
Aquí vale la pena desviarse del tema y contar más sobre RowsGroupInfo. Excel almacena RowHeightInfo en grupos de 16 piezas, donde el grupo i-ésimo, representado por la clase RowsGroupInfo, almacenará información sobre filas de i × 16 a i × 16 + 15 inclusive.
Pero la información de altura de fila en RowsGroupInfo se almacena de una manera algo inusual. Muy probablemente debido a la necesidad de mantener el historial en Excel.
Hay tres campos importantes en RowsGroupInfo: Indices, HeightInfos y RowsCount, el segundo no es visible en el código anterior (debería estar en esta línea: (rowsGroupInfo + 8 × (...)), porque rowInfoIndex puede tomar valores muy diferentes , He visto incluso más de 1000 y no tengo ni idea de cómo establecer dicha estructura en la IDA. El campo RowsCount no aparece en el código anterior, pero esta es la cantidad de filas realmente no estándar que se almacenan en el grupo.
Además, en SheetLayoutInfo hay GroupIndexDelta, la diferencia entre el índice real del grupo y el índice del primer grupo con una altura de fila modificada.
El campo Índices almacena las compensaciones RowHeightInfo para cada índice de la serie dentro del grupo. Se almacenan allí en orden, pero en HeightInfos RowHeightInfo ya están almacenados en el orden de cambio.
Supongamos que tenemos una nueva hoja en blanco y de alguna manera cambiamos la altura de la fila número 23. Esta fila se encuentra en el segundo grupo de 16 filas, luego:
- Excel determinará el índice de grupo para esta serie. En el caso actual, el índice será 1 y cambiará GroupIndexDelta = -1.
- Crea un objeto de la clase RowsGroupInfo para una serie de filas y lo coloca en sheetLayoutInfo-> RowsGroups bajo el índice 0 (sheetLayoutInfo-> GroupIndexDelta + 1);
- En RowsGroupInfo Excel asignará memoria para 16 índices de 4 bytes, para RowsCount, wordBA, wordBC y wordB8, etc.
- Luego, Excel calcula el índice de la serie en el grupo a través de la operación AND a nivel de bits (esto es mucho más rápido que tomar el resto de la división): rowIndex & 0xF. El índice deseado en el grupo será: 23 & 0xF = 7;
- Después de eso, Excel obtiene el desplazamiento para el índice 7: desplazamiento = Índices [7]. Si offset = 0, Excel asigna 8 bytes al final de RowsGroupInto, aumenta RowsCount en uno y escribe el nuevo offset en Indices [7]. En cualquier caso, al final, Excel escribe información sobre la nueva altura de fila y las marcas en el desplazamiento en RowsGroupInfo.
La clase RowsGroupInfo en C # se vería así:
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; } } }
El método GetRowHeightCore se vería así:
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; }
Y así es como se vería SetRowHeight (no enumeré su código de 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; }
Un poco de practica
Después del ejemplo anterior, con un cambio en la altura de la fila 23, Excel se verá así (configuro la fila 23 a una altura de 75 puntos):
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 23
- GroupIndexDelta = -1
- RowsGroups Count = 1
- [0] RowsGroupInfo
- AlturaInfos Count = 1
- [0] RowHeightInfo
- Banderas = 0x4005
- Valor = 100
- Índices
- [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
Aquí y en el siguiente ejemplo, presentaré una representación esquemática de cómo se ven los datos en la memoria de Excel, realizados en Visual Studio desde clases autoescritas, porque un volcado directo desde la memoria no es muy informativo.
Ahora intentemos ocultar la fila 23. Para hacer esto, establezca el bit 0x2000 de Banderas. Cambiaremos la memoria para vivir. El resultado se puede ver en este video:
http://recordit.co/79vYIbwbzB .
Cada vez que oculta filas, Excel hace lo mismo.
Ahora establezcamos el alto de fila no explícitamente, sino a través del formato de celda. Deje que la fuente en la celda A20 se convierta en una altura de 40 puntos, luego la altura de la celda en puntos será de 45.75 y en la memoria de Excel será algo como esto:
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 20
- GroupIndexDelta = -1
- RowsGroups Count = 1
- [0] RowsGroupInfo
- AlturaInfos Count = 2
- [0] RowHeightInfo
- Banderas = 0x4005
- Valor = 100
- [1] RowHeightInfo
- Banderas = 0x800E
- Valor = 244
- Índices
- [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
Puede observar que Excel siempre almacena el alto de fila si no es estándar. Incluso si la altura no se establece explícitamente, sino que se calcula en función del contenido de las celdas o el formato, Excel aún la calculará una vez y colocará el resultado en el grupo apropiado.
Nos ocupamos de la inserción / eliminación de filas
Sería interesante analizar lo que sucede al insertar / eliminar filas. El código correspondiente en excel.exe no es difícil de encontrar, pero no había ningún deseo de desmontarlo, puede echarle un vistazo a una parte:
sub_305EC930El indicador a5 determina qué operación se está llevando a cabo actualmente.
int __userpurge sub_305EC930@<eax>(int a1@<eax>, int a2@<edx>, int a3@<ecx>, int a4, int a5, int a6) { int v6;
Además, en apariencia, puede comprender aproximadamente lo que está sucediendo allí, y el resto para terminar con signos indirectos.
Intentamos identificar estas características indirectas. Primero, establezca la altura para las filas 16 a 64 inclusive en orden aleatorio. Luego, frente a la fila debajo del índice 39, inserte una nueva fila. La nueva fila copiará el alto de la fila 38.
Veamos la información en los grupos de series antes y después de agregar una serie, destaqué las diferencias audaces:
Antes de agregar una fila | Después de agregar una fila |
---|
Desplazamientos en el primer grupo: | Desplazamientos en el primer grupo: |
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 |
Los valores de las alturas de las filas en el primer grupo: | Los valores de las alturas de las filas en el primer grupo: |
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 |
Desplazamientos en el segundo grupo: | Desplazamientos en el segundo grupo: |
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 |
Los valores de las alturas de las filas en el segundo grupo: | Los valores de las alturas de las filas en el segundo grupo: |
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 |
Desplazamientos en el tercer grupo: | Desplazamientos en el tercer grupo: |
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 |
Los valores de las alturas de las filas en el tercer grupo: | Los valores de las alturas de las filas en el tercer grupo: |
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 |
Compensaciones en el cuarto grupo: | Compensaciones en el cuarto grupo: |
_ _ | 00 |
Los valores de las alturas de las filas en el cuarto grupo: | Los valores de las alturas de las filas en el cuarto grupo: |
_ _ | 40 |
Esto es lo que se esperaba: Excel inserta en el segundo grupo una nueva fila con el índice 7 (39 y 0xF), cuyo desplazamiento es 0x05, copia la altura de la fila en el índice 6, mientras que la última fila, que fue el desplazamiento 05, se desplaza a la siguiente grupo, y desde allí la última fila se empuja hacia la cuarta, etc.
Ahora veamos qué sucede si elimina la fila 29.
Antes de eliminar una fila | Después de quitar la fila |
---|
Desplazamientos en el primer grupo: | Desplazamientos en el primer grupo: |
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 |
Los valores de las alturas de las filas en el primer grupo: | Los valores de las alturas de las filas en el primer grupo: |
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 |
Desplazamientos en el segundo grupo: | Desplazamientos en el segundo grupo: |
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 |
Los valores de las alturas de las filas en el segundo grupo: | Los valores de las alturas de las filas en el segundo grupo: |
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 |
Desplazamientos en el tercer grupo: | Desplazamientos en el tercer grupo: |
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 |
Los valores de las alturas de las filas en el tercer grupo: | Los valores de las alturas de las filas en el tercer grupo: |
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 |
Compensaciones en el cuarto grupo: | Compensaciones en el cuarto grupo: |
00 | 00 |
Los valores de las alturas de las filas en el cuarto grupo: | Los valores de las alturas de las filas en el cuarto grupo: |
40 | 50 |
En principio, cuando se elimina una fila, se producen operaciones inversas. En este caso, el cuarto grupo continúa existiendo y el valor de la altura de la fila allí se llena con la altura estándar con la bandera correspondiente - 0x8005.
Estos datos son suficientes para reproducir este algoritmo en C #:
Insertrow 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); } }
Removerow 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); } }
Puedes encontrar todo el código anterior en GitHub
Conclusiones
Esta no es la primera vez que el código Excel se sorprende con trucos interesantes. Esta vez descubrí cómo almacena información sobre las alturas de las filas. Si la comunidad está interesada, en el próximo artículo mostraré cómo Excel considera la altura del rango de celdas (spoiler: hay algo similar a la descomposición SQRT, pero por alguna razón sin almacenar en caché las sumas), allí puede ver cómo se aplica la escala en enteros .