Manchmal langweile ich mich und beginne mit einem Debugger, mich mit verschiedenen Programmen zu beschäftigen. Dieses Mal fiel meine Wahl auf Excel und ich wollte herausfinden, wie es mit Zeilenhöhen umgeht, was es speichert, wie es die Höhe eines Zellbereichs berücksichtigt usw. Ich habe Excel 2010 analysiert (excel.exe, 32 Bit, Version 14.0.4756.1000, SHA1 a805cf60a5542f21001b0ea5d142d1cd0ee00b28).
Beginnen wir mit der Theorie
Wenn Sie sich der VBA-Dokumentation für Microsoft Office zuwenden, können Sie sehen, dass die Zeilenhöhe auf die eine oder andere Weise über zwei Eigenschaften ermittelt werden kann:
- RowHeight - Gibt die Höhe der ersten Zeile in dem angegebenen Bereich zurück oder legt diese fest, gemessen in Punkten. Lesen / Schreiben Double;
- Höhe - Gibt einen doppelten Wert zurück, der die Höhe des Bereichs in Punkten darstellt. Schreibgeschützt.
Und wenn Sie auch hier suchen: Excel-Spezifikationen und Grenzwerte . Sie können feststellen, dass die maximale Zeilenhöhe 409 Punkte beträgt. Dies ist leider bei weitem nicht der einzige Fall, in dem offizielle Microsoft-Dokumente etwas irreführend sind. Tatsächlich wird im Excel-Code die maximale Zeilenhöhe auf 2047 Pixel festgelegt, was 1535,25 Punkten entspricht. Die maximale Schriftgröße beträgt 409,55 Punkte. Es ist unmöglich, eine Zeile mit solch großer Höhe zu erhalten, indem Sie sie einfach in VBA / Interop zuweisen. Sie können jedoch eine Zeile erstellen, die Cambria Math-Schriftart in der ersten Zelle festlegen und die Schriftgröße auf 409,55 Punkte festlegen. Dann berechnet Excel mit seinem gerissenen Algorithmus die Zeilenhöhe basierend auf dem Zellenformat, erhält eine Zahl über 2047 Pixel (glauben Sie dem Wort) und setzt die Zeile auf die maximal mögliche Höhe. Wenn Sie die Höhe dieser Serie über die Benutzeroberfläche erfragen, liegt Excel bei einer Höhe von 409,5 Punkten. Wenn Sie jedoch die Höhe der Serie über VBA anfordern, erhalten Sie ehrliche 1535,25 Punkte, was 2047 Pixel entspricht. Richtig, nach dem Speichern des Dokuments sinkt die Höhe immer noch auf 409,5 Punkte. Diese Manipulation kann hier in diesem Video beobachtet werden: http://recordit.co/ivnFEsELLI
Ich habe die Pixel im vorherigen Absatz aus einem bestimmten Grund erwähnt. Excel speichert und berechnet die Größe von Zellen in Ganzzahlen (im Allgemeinen macht es alles so viel wie möglich in Ganzzahlen). Meistens sind dies Pixel multipliziert mit einem Faktor. Interessanterweise speichert Excel die Skala des Erscheinungsbilds in Form eines gewöhnlichen Bruchs. Beispielsweise wird eine Skala von 75% als zwei Zahlen 3 und 4 gespeichert. Wenn die Zeile angezeigt werden soll, nimmt Excel die Zeilenhöhe als ganzzahlige Anzahl von Pixeln, multipliziert mit 3 und dividiert von 4. Aber er wird diese Operation bereits ganz am Ende ausführen. Daraus entsteht der Effekt, dass alles in Bruchzahlen betrachtet wird. Um dies zu überprüfen, schreiben Sie den folgenden Code in VBA:
w.Rows(1).RowHeight = 75.375 Debug.Print w.Rows(1).Height
VBA wird 75 ausgeben, weil 75,375 Pixel sind 100,5 Pixel, und Excel kann es sich nicht leisten und lässt den Bruchteil auf 100 Pixel fallen. Wenn VBA nach der Zeilenhöhe in Punkten fragt, übersetzt Excel 100 Pixel ehrlich in Punkte und gibt 75 zurück.
Im Prinzip haben wir bereits eine Klasse in C # geschrieben, die Informationen zur Zeilenhöhe beschreibt:
class RowHeightInfo { public ushort Value { get; set; }
Sie müssen vorerst mein Wort nehmen, aber in Excel wird die Zeilenhöhe auf diese Weise gespeichert. Wenn also angegeben wird, dass die Zeilenhöhe 75 Punkte beträgt, beträgt sie 100 in Pixel, dann werden 400 in Wert gespeichert. Ich habe nicht vollständig herausgefunden, was alle Bits in Flags bedeuten (es ist schwierig und lang, die Werte von Flags herauszufinden), aber ich weiß dass 0x4000 für Zeilen festgelegt wird, deren Höhe manuell festgelegt wird, und 0x2000 - für ausgeblendete Zeilen festgelegt wird. Im Allgemeinen entspricht Flags für sichtbare Zeilen mit einer manuell festgelegten Höhe meistens 0x4005, und für Zeilen, in denen die Höhe basierend auf der Flags-Formatierung berechnet wird, ist sie entweder 0xA oder 0x800E.
Wir fragen nach der Reihenhöhe
Im Prinzip können Sie sich jetzt die Methode aus excel.exe ansehen, die die Zeilenhöhe anhand ihres Index zurückgibt (dank HexRays für den schönen Code):
int __userpurge GetRowHeight@<eax>(signed int rowIndex@<edx>, SheetLayoutInfo *sheetLayoutInfo@<esi>, bool flag) { RowHeightInfo *rowHeightInfo;
Was dword1A0 ist, habe ich immer noch nicht herausgefunden. konnte keinen Ort finden, an dem dieses Flag gesetzt ist :(
Was auch für mich defaultRowDelta2 ist, bleibt ein Rätsel. Wenn Excel die Zeilenhöhe basierend auf dem Format berechnet, wird sie als Summe zweier Zahlen dargestellt. defaultRowDelta2 ist die zweite Zahl aus dieser Summe für die Standardzeilenhöhe. Der Wert des Flag-Parameters ist ebenfalls mysteriös, weil Wo immer ich einen Aufruf dieser Methode im Flag sah, wurde false übergeben.
Die SheetLayoutInfo-Klasse wird auch in dieser Methode angezeigt. Ich habe es so genannt, weil es viele Informationen über das Erscheinungsbild des Blattes enthält. In SheetLayoutInfo gibt es folgende Felder:
- DefaultFullRowHeightMul4 - Standardzeilenhöhe;
- MinRowIndexNonDefault - der Index der ersten Zeile, bei dem die Höhe vom Standard abweicht;
- MaxRowIndexNonDefault - Index der Serie nach der letzten, bei der die Höhe vom Standard abweicht;
- DefaultRowDelta2 ist der gleiche Teil der Summe der Standardzeilenhöhe.
- GroupIndexDelta - dazu später mehr
Grundsätzlich ist die Logik dieser Methode verständlich:
- Wenn der Index der Reihe kleiner als der erste mit einer nicht standardmäßigen Höhe ist, geben Sie den Standard zurück.
- Wenn der Index der Reihe größer als der letzte mit einer nicht standardmäßigen Höhe ist, geben Sie den Standard zurück.
- Andernfalls erhalten wir das rowHeightInfo-Objekt für die Serie von der GetRowHeightCore-Methode.
- Wenn rowHeightInfo == null, wird die Standardzeilenhöhe zurückgegeben.
- Es gibt Magie mit Flags, aber im Allgemeinen geben wir zurück, was in rowHeightInfo.Value steht, und setzen das 16. Bit in der Antwort, wenn die Zeilenhöhe nicht manuell festgelegt wurde.
Wenn Sie diesen Code in C # umschreiben, erhalten Sie ungefähr Folgendes:
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; }
Jetzt können Sie einen Blick darauf werfen, was in GetRowHeightCore passiert:
RowHeightInfo *__fastcall GetRowHeightCore(SheetLayoutInfo *sheetLayoutInfo, signed int rowIndex) { RowHeightInfo *result;
- Zu Beginn prüft Excel erneut, ob der Index der Reihe zu den Zeilen mit geänderter Höhe gehört. Andernfalls wird null zurückgegeben.
- Findet die gewünschte Zeilengruppe. Wenn keine solche Gruppe vorhanden ist, wird null zurückgegeben.
- Ruft den Index der Serie in der Gruppe ab.
- Außerdem findet es anhand des Index der Reihe das gewünschte Objekt der RowHeightInfo-Klasse. wordBA, wordBC, wordB8 - einige Konstanten. Sie ändern sich nur mit der Geschichte. Im Prinzip haben sie keinen Einfluss auf das Verständnis des Algorithmus.
Hier lohnt es sich, vom Thema abzuweichen und mehr über RowsGroupInfo zu erzählen. Excel speichert RowHeightInfo in Gruppen von 16 Teilen, wobei die i-te Gruppe, dargestellt durch die RowsGroupInfo-Klasse, Informationen zu Zeilen von i × 16 bis einschließlich i × 16 + 15 speichert.
Informationen zur Zeilenhöhe in RowsGroupInfo werden jedoch auf ungewöhnliche Weise gespeichert. Höchstwahrscheinlich aufgrund der Notwendigkeit, den Verlauf in Excel zu pflegen.
In RowsGroupInfo gibt es drei wichtige Felder: Indizes, HeightInfos und RowsCount. Das zweite Feld ist im obigen Code nicht sichtbar (sollte in dieser Zeile stehen: (rowsGroupInfo + 8 × (...)), da rowInfoIndex sehr unterschiedliche Werte annehmen kann Ich habe sogar mehr als 1000 gesehen und habe keine Ahnung, wie eine solche Struktur in der IDA festgelegt werden soll. Das Feld "RowsCount" wird im obigen Code nicht angezeigt, aber so viele wirklich nicht standardmäßige Zeilen werden in der Gruppe gespeichert.
Darüber hinaus gibt es in SheetLayoutInfo GroupIndexDelta - die Differenz zwischen dem realen Index der Gruppe und dem Index der ersten Gruppe mit einer geänderten Zeilenhöhe.
Im Feld Indizes werden die RowHeightInfo-Offsets für jeden Index der Serie innerhalb der Gruppe gespeichert. Sie werden dort in der Reihenfolge gespeichert, aber in HeightInfos RowHeightInfo werden sie bereits in der Reihenfolge der Änderung gespeichert.
Angenommen, wir haben ein neues leeres Blatt und irgendwie haben wir die Höhe der Zeilennummer 23 geändert. Diese Zeile liegt in der zweiten Gruppe von 16 Zeilen, dann:
- Excel ermittelt den Gruppenindex für diese Serie. Im aktuellen Fall ist der Index 1 und ändert GroupIndexDelta = -1.
- Erstellt ein Objekt der RowsGroupInfo-Klasse für eine Reihe von Zeilen und fügt es in sheetLayoutInfo-> RowsGroups unter dem Index 0 ab (sheetLayoutInfo-> GroupIndexDelta + 1).
- In RowsGroupInfo weist Excel Speicher für 16 4-Byte-Indizes für RowsCount, wordBA, wordBC und wordB8 usw. zu.
- Dann berechnet Excel den Index der Reihe in der Gruppe durch die bitweise UND-Operation (dies ist viel schneller als der Rest der Division): rowIndex & 0xF. Der gewünschte Index in der Gruppe ist: 23 & 0xF = 7;
- Danach erhält Excel den Offset für Index 7: offset = Indices [7]. Wenn offset = 0 ist, weist Excel am Ende von RowsGroupInto 8 Bytes zu, erhöht den RowsCount um eins und schreibt den neuen Offset in Indizes [7]. In jedem Fall schreibt Excel am Ende Informationen über die neue Zeilenhöhe und Flags am Versatz in die RowsGroupInfo.
Die RowsGroupInfo-Klasse in C # selbst würde folgendermaßen aussehen:
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; } } }
Die GetRowHeightCore-Methode würde folgendermaßen aussehen:
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; }
Und so würde SetRowHeight aussehen (ich habe seinen Code aus excel.exe nicht aufgelistet):
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; }
Ein bisschen Übung
Nach dem obigen Beispiel sieht Excel mit einer Änderung der Höhe von Zeile 23 ungefähr so aus (ich habe Zeile 23 auf eine Höhe von 75 Punkten festgelegt):
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 23
- GroupIndexDelta = -1
- Anzahl der Zeilengruppen = 1
- [0] RowsGroupInfo
- HeightInfos Count = 1
- [0] RowHeightInfo
- Flags = 0x4005
- Wert = 100
- Indizes
- [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
Hier und im folgenden Beispiel werde ich eine schematische Darstellung des Aussehens der Daten im Excel-Speicher in Visual Studio aus selbstgeschriebenen Klassen erstellen, da ein direkter Speicherauszug aus dem Speicher nicht sehr informativ ist.
Versuchen wir nun, Zeile 23 auszublenden. Setzen Sie dazu das 0x2000-Bit von Flags. Wir werden die Erinnerung ändern, um zu leben. Das Ergebnis ist in diesem Video zu sehen:
http://recordit.co/79vYIbwbzB .
Immer wenn Sie Zeilen ausblenden, macht Excel dasselbe.
Stellen wir nun die Zeilenhöhe nicht explizit ein, sondern über das Zellenformat. Lassen Sie die Schriftart in Zelle A20 eine Höhe von 40 Punkten erreichen, dann beträgt die Zellenhöhe in Punkten 45,75 und im Excel-Speicher ungefähr so:
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 20
- GroupIndexDelta = -1
- Anzahl der Zeilengruppen = 1
- [0] RowsGroupInfo
- HeightInfos Count = 2
- [0] RowHeightInfo
- Flags = 0x4005
- Wert = 100
- [1] RowHeightInfo
- Flags = 0x800E
- Wert = 244
- Indizes
- [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
Möglicherweise stellen Sie fest, dass Excel die Zeilenhöhe immer speichert, wenn dies nicht Standard ist. Auch wenn die Höhe nicht explizit festgelegt wird, sondern auf der Grundlage des Inhalts der Zellen oder des Formats berechnet wird, berechnet Excel sie dennoch einmal und ordnet das Ergebnis der entsprechenden Gruppe zu.
Wir beschäftigen uns mit dem Einfügen / Löschen von Zeilen
Es wäre interessant zu analysieren, was beim Einfügen / Löschen von Zeilen passiert. Der entsprechende Code in excel.exe ist nicht schwer zu finden, aber es bestand kein Wunsch, ihn zu zerlegen. Sie können sich einen Teil davon ansehen:
sub_305EC930Das Flag a5 bestimmt, welche Operation gerade stattfindet.
int __userpurge sub_305EC930@<eax>(int a1@<eax>, int a2@<edx>, int a3@<ecx>, int a4, int a5, int a6) { int v6;
Darüber hinaus können Sie in der Erscheinung grob verstehen, was dort passiert, und der Rest wird durch indirekte Zeichen abgeschlossen.
Wir versuchen, diese indirekten Merkmale zu identifizieren. Stellen Sie zunächst die Höhe für die Zeilen 16 bis einschließlich 64 in zufälliger Reihenfolge ein. Fügen Sie dann vor der Zeile unter Index 39 eine neue Zeile ein. Die neue Zeile kopiert die Höhe der Zeile 38.
Schauen wir uns die Informationen in den Gruppen von Serien vor und nach dem Hinzufügen einer Serie an. Ich habe die kühnen Unterschiede hervorgehoben:
Vor dem Hinzufügen einer Zeile | Nach dem Hinzufügen einer Zeile |
---|
Verschiebungen in der ersten Gruppe: | Verschiebungen in der ersten Gruppe: |
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 |
Die Werte der Zeilenhöhen in der ersten Gruppe: | Die Werte der Zeilenhöhen in der ersten Gruppe: |
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 |
Verschiebungen in der zweiten Gruppe: | Verschiebungen in der zweiten Gruppe: |
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 |
Die Werte der Zeilenhöhen in der zweiten Gruppe: | Die Werte der Zeilenhöhen in der zweiten Gruppe: |
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 |
Verschiebungen in der dritten Gruppe: | Verschiebungen in der dritten Gruppe: |
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 |
Die Werte der Zeilenhöhen in der dritten Gruppe: | Die Werte der Zeilenhöhen in der dritten Gruppe: |
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 |
Offsets in der vierten Gruppe: | Offsets in der vierten Gruppe: |
_ | 00 |
Die Werte der Zeilenhöhen in der vierten Gruppe: | Die Werte der Zeilenhöhen in der vierten Gruppe: |
_ | 40 |
Dies wurde erwartet: Excel fügt in die zweite Gruppe eine neue Zeile mit Index 7 (39 & 0xF) ein, deren Versatz 0x05 ist, kopiert die Zeilenhöhe bei Index 6, während die letzte Zeile, die Versatz 05 war, zur nächsten verschoben wird Gruppe, und von dort wird die letzte Reihe in die vierte geschoben, etc.
Nun wollen wir sehen, was passiert, wenn Sie die 29. Zeile löschen.
Vor dem Entfernen einer Zeile | Nach dem Entfernen der Reihe |
---|
Verschiebungen in der ersten Gruppe: | Verschiebungen in der ersten Gruppe: |
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 |
Die Werte der Zeilenhöhen in der ersten Gruppe: | Die Werte der Zeilenhöhen in der ersten Gruppe: |
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 |
Verschiebungen in der zweiten Gruppe: | Verschiebungen in der zweiten Gruppe: |
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 |
Die Werte der Zeilenhöhen in der zweiten Gruppe: | Die Werte der Zeilenhöhen in der zweiten Gruppe: |
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 |
Verschiebungen in der dritten Gruppe: | Verschiebungen in der dritten Gruppe: |
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 |
Die Werte der Zeilenhöhen in der dritten Gruppe: | Die Werte der Zeilenhöhen in der dritten Gruppe: |
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 |
Offsets in der vierten Gruppe: | Offsets in der vierten Gruppe: |
00 | 00 |
Die Werte der Zeilenhöhen in der vierten Gruppe: | Die Werte der Zeilenhöhen in der vierten Gruppe: |
40 | 50 |
Wenn eine Zeile gelöscht wird, treten im Prinzip umgekehrte Operationen auf. In diesem Fall bleibt die vierte Gruppe bestehen und der Wert der dortigen Zeilenhöhe wird mit der Standardhöhe mit dem entsprechenden Flag - 0x8005 gefüllt.
Diese Daten reichen aus, um diesen Algorithmus in C # zu reproduzieren:
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); } }
Entferner entfernen 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); } }
Sie finden den gesamten obigen Code auf GitHub
Schlussfolgerungen
Dies ist nicht das erste Mal, dass Excel-Code von interessanten Tricks überrascht wurde. Diesmal habe ich herausgefunden, wie er Informationen über die Höhen der Reihen speichert. Wenn die Community interessiert ist, werde ich im nächsten Artikel zeigen, wie Excel die Höhe des Zellbereichs berücksichtigt (Spoiler: Es gibt etwas Ähnliches wie die SQRT-Zerlegung, aber aus irgendeinem Grund ohne Summen-Caching). Dort können Sie sehen, wie die Skalierung in ganzen Zahlen angewendet wird .