Parfois je m'ennuie et, armé d'un débogueur, je commence à creuser dans différents programmes. Cette fois, mon choix s'est porté sur Excel et je voulais comprendre comment il gère les hauteurs des lignes, ce qu'il stocke, comment il considère la hauteur de la plage de cellules, etc. J'ai analysé Excel 2010 (excel.exe, 32 bits, version 14.0.4756.1000, SHA1 a805cf60a5542f21001b0ea5d142d1cd0ee00b28).
Commençons par la théorie
Si vous consultez la documentation VBA pour Microsoft Office, vous pouvez voir que la hauteur de ligne peut être obtenue d'une manière ou d'une autre via deux propriétés:
- RowHeight - Renvoie ou définit la hauteur de la première ligne dans la plage spécifiée, mesurée en points. Lecture / écriture Double;
- Hauteur - Renvoie une valeur Double qui représente la hauteur, en points, de la plage. Lecture seule.
Et si vous regardez aussi ici: spécifications et limites Excel . Vous pouvez constater que la hauteur de ligne maximale est de 409 points. Malheureusement, c'est loin d'être le seul cas où les documents officiels de Microsoft sont un peu rusés. En fait, dans le code Excel, la hauteur de ligne maximale est définie sur 2047 pixels, ce qui correspond à 1535,25 points. Et la taille de police maximale est de 409,55 points. Il est impossible d'obtenir une ligne d'une telle hauteur en l'affectant simplement dans VBA / Interop, mais vous pouvez prendre une ligne, définir la police Cambria Math dans sa première cellule et définir la taille de la police sur 409,55 points. Ensuite, Excel avec son algorithme rusé calculera la hauteur de ligne en fonction du format de cellule, obtiendra un nombre supérieur à 2047 pixels (croyez le mot) et définira la ligne à la hauteur maximale possible. Si vous demandez la hauteur de cette série via l'interface utilisateur, Excel mentira que la hauteur est de 409,5 points, mais si vous demandez la hauteur de la série via VBA, vous obtenez honnêtement 1535,25 points, ce qui équivaut à 2047 pixels. Certes, après avoir enregistré le document, la hauteur baissera toujours à 409,5 points. Cette manipulation peut être observée ici dans cette vidéo: http://recordit.co/ivnFEsELLI
J'ai mentionné les pixels dans le paragraphe précédent pour une raison. Excel stocke et calcule en fait la taille des cellules en nombres entiers (il fait généralement tout autant que possible en nombres entiers). Le plus souvent, ce sont des pixels multipliés par un facteur. Fait intéressant, Excel stocke l'échelle d'apparence sous la forme d'une fraction ordinaire, par exemple, une échelle de 75% sera stockée sous la forme de deux nombres 3 et 4. Et quand il est nécessaire d'afficher la ligne, Excel prendra la hauteur de la ligne comme un nombre entier de pixels, multipliez par 3 et divisez par 4. Mais il effectuera cette opération déjà à la toute fin, ce qui crée l'effet que tout est considéré en nombres fractionnaires. Pour vérifier cela, écrivez le code suivant dans VBA:
w.Rows(1).RowHeight = 75.375 Debug.Print w.Rows(1).Height
VBA donnera 75 parce que 75,375 pixels seront 100,5 pixels, et Excel ne peut pas se le permettre et déposera la partie fractionnaire jusqu'à 100 pixels. Lorsque VBA demande la hauteur de ligne en points, Excel traduit honnêtement 100 pixels en points et renvoie 75.
En principe, nous avons déjà commencé à écrire une classe en C # qui décrira des informations sur la hauteur de ligne:
class RowHeightInfo { public ushort Value { get; set; }
Vous devrez prendre ma parole pour l'instant, mais dans Excel, la hauteur de ligne est stockée de cette façon. Autrement dit, s'il est spécifié que la hauteur de la ligne est de 75 points, elle sera de 100 en pixels, puis 400 sera stockée dans Value. Je n'ai pas entièrement compris ce que tous les bits dans Flags signifient (il est difficile et long de comprendre les valeurs des drapeaux), mais je sais avec certitude que 0x4000 est défini pour les lignes dont la hauteur est définie manuellement et 0x2000 - est défini pour les lignes masquées. En général, pour les lignes visibles avec une hauteur définie manuellement, les indicateurs sont le plus souvent égaux à 0x4005, et pour les lignes dans lesquelles la hauteur est calculée en fonction du formatage des indicateurs, il s'agit de 0xA ou 0x800E.
Nous demandons la hauteur de ligne
Maintenant, en principe, vous pouvez jeter un œil à la méthode de excel.exe, qui retourne la hauteur de la ligne par son index (merci à HexRays pour le beau code):
int __userpurge GetRowHeight@<eax>(signed int rowIndex@<edx>, SheetLayoutInfo *sheetLayoutInfo@<esi>, bool flag) { RowHeightInfo *rowHeightInfo;
Qu'est-ce que dword1A0, je n'ai toujours pas compris. n'a pas pu trouver un endroit où ce drapeau est défini :(
Ce qui est defaultRowDelta2 pour moi aussi, reste toujours un mystère. Lorsque Excel calcule la hauteur de ligne en fonction du format, il la représente comme la somme de deux nombres. defaultRowDelta2 est le deuxième nombre de cette somme pour la hauteur de ligne standard. La valeur du paramètre flag est également mystérieuse, car partout où j'ai vu un appel à cette méthode dans flag passé faux.
La classe SheetLayoutInfo apparaît également dans cette méthode. Je l'ai nommé ainsi, car il stocke beaucoup de toutes sortes d'informations sur l'apparence de la feuille. Dans SheetLayoutInfo, il existe des champs tels que:
- DefaultFullRowHeightMul4 - hauteur de ligne standard;
- MinRowIndexNonDefault - l'indice de la première ligne, à laquelle la hauteur diffère de la norme;
- MaxRowIndexNonDefault - indice de la série suivant la dernière, à laquelle la hauteur diffère de la norme;
- DefaultRowDelta2 est la même partie de la somme de la hauteur de ligne standard.
- GroupIndexDelta - plus à ce sujet plus tard
En principe, la logique de cette méthode est compréhensible:
- Si l'indice de la série est inférieur au premier avec une hauteur non standard, renvoyez le standard;
- Si l'indice de la série est supérieur au dernier avec une hauteur non standard, renvoyez le standard;
- Sinon, nous obtenons l'objet rowHeightInfo pour la série à partir de la méthode GetRowHeightCore;
- Si rowHeightInfo == null renvoie la hauteur de ligne standard;
- Il y a de la magie avec les drapeaux, mais en général, nous renvoyons ce qui est dans rowHeightInfo.Value et définissons le 16e bit dans la réponse si la hauteur de la ligne n'a pas été définie manuellement.
Si vous réécrivez ce code en C #, vous obtenez quelque chose comme ceci:
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; }
Vous pouvez maintenant voir ce qui se passe à l'intérieur de GetRowHeightCore:
RowHeightInfo *__fastcall GetRowHeightCore(SheetLayoutInfo *sheetLayoutInfo, signed int rowIndex) { RowHeightInfo *result;
- Encore une fois, au début, Excel vérifie si l'index de la série se trouve parmi les lignes avec une hauteur modifiée, et sinon, il renvoie null.
- Recherche le groupe de lignes souhaité; s'il n'y en a pas, il renvoie null.
- Obtient l'index de la série dans le groupe.
- De plus, par l'index de la série, il trouve l'objet souhaité de la classe RowHeightInfo. wordBA, wordBC, wordB8 - quelques constantes. Ils ne changent qu'avec l'histoire. En principe, ils n'affectent pas la compréhension de l'algorithme.
Ici, il vaut la peine de s'écarter du sujet et d'en dire plus sur RowsGroupInfo. Excel stocke RowHeightInfo dans des groupes de 16 pièces, où le i-ème groupe, représenté par la classe RowsGroupInfo, stockera des informations sur les lignes de i × 16 à i × 16 + 15 inclus.
Mais les informations de hauteur de ligne dans RowsGroupInfo sont stockées d'une manière quelque peu inhabituelle. Très probablement en raison de la nécessité de conserver l'historique dans Excel.
Il y a trois champs importants dans RowsGroupInfo: Indices, HeightInfos et RowsCount, le second n'est pas visible dans le code ci-dessus (il devrait être dans cette ligne: (rowsGroupInfo + 8 × (...)), car rowInfoIndex peut prendre des valeurs très différentes , J'en ai vu encore plus de 1000 et je ne sais pas comment définir une telle structure dans l'IDA.Le champ RowsCount n'apparaît pas dans le code ci-dessus, mais c'est le nombre de lignes vraiment non standard stockées dans le groupe.
De plus, dans SheetLayoutInfo, il y a GroupIndexDelta - la différence entre l'index réel du groupe et l'index du premier groupe avec une hauteur de ligne modifiée.
Le champ Indices stocke les décalages RowHeightInfo pour chaque index de la série dans le groupe. Ils y sont stockés dans l'ordre, mais dans HeightInfos RowHeightInfo sont déjà stockés dans l'ordre de modification.
Supposons que nous ayons une nouvelle feuille vierge et que nous ayons en quelque sorte changé la hauteur de la ligne numéro 23. Cette ligne se trouve dans le deuxième groupe de 16 lignes, puis:
- Excel déterminera l'index de groupe pour cette série. Dans le cas actuel, l'index sera 1 et changera GroupIndexDelta = -1.
- Crée un objet de la classe RowsGroupInfo pour une série de lignes et le place dans sheetLayoutInfo-> RowsGroups sous l'index 0 (sheetLayoutInfo-> GroupIndexDelta + 1);
- Dans RowsGroupInfo, Excel allouera de la mémoire pour 16 indices à 4 octets, pour RowsCount, wordBA, wordBC et wordB8, etc.;
- Excel calcule ensuite l'index de la série dans le groupe via l'opération ET au niveau du bit (c'est beaucoup plus rapide que de prendre le reste de la division): rowIndex & 0xF. L'index souhaité dans le groupe sera: 23 & 0xF = 7;
- Après cela, Excel obtient le décalage pour l'index 7: offset = Indices [7]. Si offset = 0, Excel alloue 8 octets à la fin de RowsGroupInto, augmente le RowsCount de un et écrit le nouveau décalage dans Indices [7]. Dans tous les cas, à la fin, Excel écrit des informations sur la nouvelle hauteur de ligne et les indicateurs au décalage dans RowsGroupInfo.
La classe RowsGroupInfo en C # lui-même ressemblerait à ceci:
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; } } }
La méthode GetRowHeightCore ressemblerait à ceci:
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; }
Et voici à quoi ressemblerait SetRowHeight (je n'ai pas répertorié son code depuis 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 peu de pratique
Après l'exemple ci-dessus, avec un changement dans la hauteur de la ligne 23, Excel ressemblera à ceci (j'ai défini la ligne 23 à une hauteur de 75 points):
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 23
- GroupIndexDelta = -1
- Nombre de groupes de lignes = 1
- [0] RowsGroupInfo
- HeightInfos Count = 1
- [0] RowHeightInfo
- Drapeaux = 0x4005
- Valeur = 100
- Indices
- [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
Ici et dans l'exemple suivant, je présenterai une représentation schématique de l'apparence des données dans la mémoire Excel, réalisée dans Visual Studio à partir de classes auto-écrites, car un vidage direct de la mémoire n'est pas très informatif.
Essayons maintenant de masquer la ligne 23. Pour ce faire, définissez le bit 0x2000 de drapeaux. Nous allons changer la mémoire pour vivre. Le résultat peut être vu dans cette vidéo:
http://recordit.co/79vYIbwbzB .
Chaque fois que vous masquez des lignes, Excel fait de même.
Définissons maintenant la hauteur de ligne non pas explicitement, mais via le format de cellule. Laissez la police dans la cellule A20 devenir une hauteur de 40 points, puis la hauteur des cellules en points sera de 45,75 et dans la mémoire Excel, ce sera quelque chose comme ceci:
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 20
- GroupIndexDelta = -1
- Nombre de groupes de lignes = 1
- [0] RowsGroupInfo
- HeightInfos Count = 2
- [0] RowHeightInfo
- Drapeaux = 0x4005
- Valeur = 100
- [1] RowHeightInfo
- Indicateurs = 0x800E
- Valeur = 244
- Indices
- [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
Vous pouvez remarquer qu'Excel stocke toujours la hauteur de ligne si elle n'est pas standard. Même si la hauteur n'est pas définie explicitement, mais est calculée en fonction du contenu des cellules ou du format, Excel la calculera toujours une fois et mettra le résultat dans le groupe approprié.
Nous nous occupons de l'insertion / suppression de lignes
Il serait intéressant d'analyser ce qui se passe lors de l'insertion / suppression de lignes. Le code correspondant dans excel.exe n'est pas difficile à trouver, mais il n'y avait aucune envie de le démonter, vous pouvez en voir une partie:
sub_305EC930Le drapeau a5 détermine quelle opération est en cours.
int __userpurge sub_305EC930@<eax>(int a1@<eax>, int a2@<edx>, int a3@<ecx>, int a4, int a5, int a6) { int v6;
De plus, en apparence, vous pouvez à peu près comprendre ce qui s'y passe, et le reste pour finir par des signes indirects.
Nous essayons d'identifier ces caractéristiques indirectes. Tout d'abord, définissez la hauteur des lignes 16 à 64 inclusivement dans un ordre aléatoire. Ensuite, devant la ligne sous l'index 39, insérez une nouvelle ligne. La nouvelle ligne copiera la hauteur de la ligne 38.
Regardons les informations dans les groupes de séries avant et après avoir ajouté une série, j'ai souligné les différences audacieuses:
Avant d'ajouter une ligne | Après avoir ajouté une ligne |
---|
Déplacements dans le premier groupe: | Déplacements dans le premier groupe: |
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 |
Les valeurs des hauteurs des lignes du premier groupe: | Les valeurs des hauteurs des lignes du premier groupe: |
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 |
Déplacements dans le deuxième groupe: | Déplacements dans le deuxième groupe: |
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 |
Les valeurs des hauteurs des rangées du deuxième groupe: | Les valeurs des hauteurs des rangées du deuxième groupe: |
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 |
Déplacements dans le troisième groupe: | Déplacements dans le troisième groupe: |
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 |
Les valeurs des hauteurs des rangées du troisième groupe: | Les valeurs des hauteurs des rangées du troisième groupe: |
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 |
Décalages dans le quatrième groupe: | Décalages dans le quatrième groupe: |
_ | 00 |
Les valeurs des hauteurs des rangées du quatrième groupe: | Les valeurs des hauteurs des rangées du quatrième groupe: |
_ | 40 |
C'est ce qui était attendu: Excel insère dans le deuxième groupe une nouvelle ligne avec l'index 7 (39 & 0xF), dont le décalage est 0x05, copie la hauteur de ligne à l'index 6, tandis que la dernière ligne, qui était décalée 05, est poussée à la suivante groupe, et à partir de là, la dernière ligne est poussée dans le quatrième, etc.
Voyons maintenant ce qui se passe si vous supprimez la 29e ligne.
Avant de supprimer une ligne | Après avoir supprimé la ligne |
---|
Déplacements dans le premier groupe: | Déplacements dans le premier groupe: |
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 |
Les valeurs des hauteurs des lignes du premier groupe: | Les valeurs des hauteurs des lignes du premier groupe: |
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 |
Déplacements dans le deuxième groupe: | Déplacements dans le deuxième groupe: |
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 |
Les valeurs des hauteurs des rangées du deuxième groupe: | Les valeurs des hauteurs des rangées du deuxième groupe: |
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 |
Déplacements dans le troisième groupe: | Déplacements dans le troisième groupe: |
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 |
Les valeurs des hauteurs des rangées du troisième groupe: | Les valeurs des hauteurs des rangées du troisième groupe: |
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 |
Décalages dans le quatrième groupe: | Décalages dans le quatrième groupe: |
00 | 00 |
Les valeurs des hauteurs des rangées du quatrième groupe: | Les valeurs des hauteurs des rangées du quatrième groupe: |
40 | 50 |
En principe, lorsqu'une ligne est supprimée, des opérations inverses se produisent. Dans ce cas, le quatrième groupe continue d'exister et la valeur de la hauteur de ligne y est remplie avec la hauteur standard avec l'indicateur correspondant - 0x8005.
Ces données sont suffisantes pour reproduire cet algorithme 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); } }
Vous pouvez trouver tout le code ci-dessus sur GitHub
Conclusions
Ce n'est pas la première fois que le code Excel est surpris par des astuces intéressantes. Cette fois, j'ai découvert comment il stocke des informations sur les hauteurs des rangées. Si la communauté est intéressée, dans le prochain article, je montrerai comment Excel considère la hauteur de la plage de cellules (spoiler: il y a quelque chose de similaire à la décomposition SQRT, mais pour une raison quelconque sans mettre en cache les sommes), vous pouvez voir comment il applique la mise à l'échelle en nombres entiers .