Kadang-kadang saya bosan dan, dengan dipersenjatai dengan debugger, saya mulai menggali berbagai program. Kali ini, pilihan saya jatuh pada Excel dan ada keinginan untuk mencari tahu bagaimana menangani ketinggian baris, apa yang menyimpannya, bagaimana ia mempertimbangkan ketinggian berbagai sel, dll. Saya mem-parsing Excel 2010 (excel.exe, 32bit, versi 14.0.4756.1000, SHA1 a805cf60a5542f21001b0ea5d142d1cd0ee00b28).
Mari kita mulai dengan teorinya
Jika Anda membuka dokumentasi VBA untuk Microsoft Office, Anda dapat melihat bahwa ketinggian baris dapat diperoleh dengan satu atau lain cara melalui dua properti:
- RowHeight - Mengembalikan atau menetapkan ketinggian baris pertama dalam rentang yang ditentukan, diukur dalam poin. Baca / tulis Ganda;
- Tinggi - Mengembalikan nilai ganda yang mewakili tinggi, dalam poin, dari kisaran. Hanya baca.
Dan jika Anda melihat juga di sini: Spesifikasi dan batasan Excel . Anda dapat menemukan bahwa ketinggian baris maksimum adalah 409 poin. Sayangnya, ini jauh dari satu-satunya kasus ketika dokumen resmi Microsoft sedikit licik. Bahkan, dalam kode Excel, tinggi baris maksimum diatur ke 2047 piksel, yang akan menjadi 1535,25 poin. Dan ukuran font maksimum adalah 409,55 poin. Tidak mungkin mendapatkan deretan tinggi seperti itu hanya dengan menetapkannya di VBA / Interop, tetapi Anda dapat mengambil satu baris, mengatur font Cambria Math di sel pertama, dan mengatur ukuran font menjadi 409,55 poin. Kemudian Excel dengan algoritma liciknya akan menghitung tinggi baris berdasarkan format sel, mendapatkan angka lebih dari 2.047 piksel (percaya kata), dan mengatur baris ke ketinggian maksimum yang mungkin. Jika Anda bertanya ketinggian seri ini melalui UI, maka Excel akan berbohong bahwa tingginya adalah 409,5 poin, tetapi jika Anda meminta tinggi seri melalui VBA, Anda mendapatkan poin 1535,25 jujur, yang sama dengan 2047 piksel. Benar, setelah menyimpan dokumen, tingginya masih akan turun menjadi 409,5 poin. Manipulasi ini dapat diamati di sini di video ini: http://recordit.co/ivnFEsELLI
Saya menyebutkan piksel pada paragraf sebelumnya karena suatu alasan. Excel benar-benar menyimpan dan menghitung ukuran sel dalam bilangan bulat (umumnya melakukan segalanya sebanyak mungkin dalam bilangan bulat). Paling sering ini adalah piksel yang dikalikan dengan beberapa faktor. Menariknya, Excel menyimpan skala tampilan dalam bentuk fraksi biasa, misalnya skala 75% akan disimpan sebagai dua angka 3 dan 4. Dan ketika diperlukan untuk menampilkan baris, Excel akan mengambil tinggi baris sebagai jumlah integer piksel, dikalikan 3 dan dibagi oleh 4. Tapi dia akan melakukan operasi ini sudah di akhir .. Dari sini efeknya dibuat bahwa semuanya dianggap dalam angka fraksional. Untuk memverifikasi ini, tulis kode berikut dalam VBA:
w.Rows(1).RowHeight = 75.375 Debug.Print w.Rows(1).Height
VBA akan memberikan 75 karena 75,375 piksel akan menjadi 100,5 piksel, dan Excel tidak mampu membelinya dan akan menjatuhkan bagian pecahan hingga 100 piksel. Ketika VBA meminta tinggi baris dalam poin, Excel dengan jujur โโmenerjemahkan 100 piksel menjadi poin dan mengembalikan 75.
Pada prinsipnya, kita telah menulis kelas di C # yang akan menjelaskan informasi tentang tinggi baris:
class RowHeightInfo { public ushort Value { get; set; }
Anda harus mengambil kata saya untuk saat ini, tetapi di Excel, tinggi baris disimpan dengan cara itu. Yaitu, jika ditentukan bahwa tinggi baris adalah 75 poin, itu akan menjadi 100 dalam piksel, maka 400 akan disimpan dalam Nilai. Saya belum sepenuhnya memahami apa arti semua bit dalam Bendera (sulit dan lama untuk mengetahui nilai-nilai bendera), tetapi saya tahu 0x4000 diatur untuk baris yang tingginya diatur secara manual, dan 0x2000 - diatur untuk baris tersembunyi. Secara umum, untuk baris yang terlihat dengan ketinggian yang diatur secara manual, Flags paling sering sama dengan 0x4005, dan untuk baris di mana tinggi dihitung berdasarkan format Flags, itu bisa 0xA atau 0x800E.
Kami meminta tinggi baris
Sekarang, pada prinsipnya, Anda dapat melihat metode dari excel.exe, yang mengembalikan tinggi baris dengan indeksnya (terima kasih kepada HexRays untuk kode yang indah):
int __userpurge GetRowHeight@<eax>(signed int rowIndex@<edx>, SheetLayoutInfo *sheetLayoutInfo@<esi>, bool flag) { RowHeightInfo *rowHeightInfo;
Apa itu dword1A0, saya masih belum menemukan jawabannya. tidak dapat menemukan tempat di mana bendera ini diset :(
Apa defaultRowDelta2 bagi saya juga masih menjadi misteri. Ketika excel menghitung tinggi baris berdasarkan format, itu menyatakannya sebagai jumlah dari dua angka. defaultRowDelta2 adalah angka kedua dari jumlah ini untuk tinggi baris standar. Nilai parameter flag juga misterius, karena di mana pun saya melihat panggilan ke metode ini di flag lewat false.
Kelas SheetLayoutInfo juga muncul dalam metode ini. Saya menamakannya seperti itu, karena menyimpan banyak semua jenis informasi tentang penampilan lembar. Di SheetLayoutInfo ada bidang seperti:
- DefaultFullRowHeightMul4 - tinggi baris standar;
- MinRowIndexNonDefault - indeks baris pertama, di mana ketinggian berbeda dari standar;
- MaxRowIndexNonDefault - indeks dari seri mengikuti yang terakhir, di mana ketinggian berbeda dari standar;
- DefaultRowDelta2 adalah bagian yang sama dari jumlah tinggi baris standar.
- GroupIndexDelta - lebih lanjut tentang ini nanti
Pada prinsipnya, logika metode ini dapat dimengerti:
- Jika indeks seri kurang dari yang pertama dengan tinggi non-standar, maka kembalikan standar;
- Jika indeks seri lebih besar dari yang terakhir dengan tinggi non-standar, maka kembalikan standar;
- Kalau tidak, kita mendapatkan objek rowHeightInfo untuk seri dari metode GetRowHeightCore;
- Jika rowHeightInfo == null mengembalikan tinggi baris standar;
- Ada keajaiban dengan flag, tetapi secara umum, kami mengembalikan apa yang ada di rowHeightInfo.Value dan mengatur bit ke-16 dalam respons jika tinggi baris tidak diatur secara manual.
Jika Anda menulis ulang kode ini dalam C #, Anda mendapatkan sesuatu seperti berikut:
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; }
Sekarang Anda dapat melihat apa yang terjadi di dalam GetRowHeightCore:
RowHeightInfo *__fastcall GetRowHeightCore(SheetLayoutInfo *sheetLayoutInfo, signed int rowIndex) { RowHeightInfo *result;
- Sekali lagi, di awal, Excel memeriksa untuk melihat apakah indeks seri berada di antara baris dengan ketinggian yang dimodifikasi, dan jika tidak, mengembalikan nol.
- Menemukan grup baris yang diinginkan, jika tidak ada grup seperti itu, ia mengembalikan nol.
- Mendapat indeks seri dalam grup.
- Selanjutnya, dengan indeks seri, ia menemukan objek yang diinginkan dari kelas RowHeightInfo. wordBA, wordBC, wordB8 - beberapa konstanta. Mereka berubah hanya dengan sejarah. Pada prinsipnya, mereka tidak mempengaruhi pemahaman algoritma.
Di sini ada baiknya menyimpang dari topik dan menceritakan lebih banyak tentang RowsGroupInfo. Excel menyimpan RowHeightInfo dalam grup yang terdiri dari 16 buah, di mana grup ke-i, yang diwakili oleh kelas RowsGroupInfo, akan menyimpan informasi tentang baris dari i ร 16 hingga i ร 16 + 15 inklusif.
Tetapi informasi tinggi baris di RowsGroupInfo disimpan dengan cara yang agak tidak biasa. Kemungkinan besar karena kebutuhan untuk mempertahankan riwayat di Excel.
Ada tiga bidang penting dalam RowsGroupInfo: Indeks, HeightInfos, dan RowsCount, yang kedua tidak terlihat dalam kode di atas (harus di baris ini: (rowsGroupInfo + 8 ร (...)), karena rowInfoIndex dapat mengambil nilai yang sangat berbeda , Saya telah melihat lebih dari 1000 dan saya tidak tahu cara mengatur struktur seperti itu di IDA. Bidang RowsCount tidak muncul dalam kode di atas, tetapi ini adalah berapa banyak baris yang benar-benar non-standar disimpan dalam grup.
Selain itu, di SheetLayoutInfo ada GroupIndexDelta - perbedaan antara indeks nyata grup dan indeks grup pertama dengan tinggi baris yang dimodifikasi.
Bidang Indeks menyimpan offset RowHeightInfo untuk setiap indeks seri dalam grup. Mereka disimpan di sana secara berurutan, tetapi di HeightInfos RowHeightInfo sudah disimpan dalam urutan perubahan.
Misalkan kita memiliki lembaran kosong baru dan entah bagaimana kita mengubah ketinggian nomor baris 23. Baris ini terletak pada kelompok kedua dari 16 baris, lalu:
- Excel akan menentukan indeks grup untuk seri ini. Dalam kasus saat ini, indeks akan menjadi 1 dan akan mengubah GroupIndexDelta = -1.
- Membuat objek kelas RowsGroupInfo untuk serangkaian baris dan meletakkannya di sheetLayoutInfo-> RowsGroups di bawah indeks 0 (sheetLayoutInfo-> GroupIndexDelta + 1);
- Dalam RowsGroupInfo, Excel akan mengalokasikan memori untuk 16 Indeks 4-byte, untuk RowsCount, wordBA, wordBC dan wordB8, dll.;
- Kemudian Excel menghitung indeks seri dalam grup melalui operasi AND bitwise (ini jauh lebih cepat daripada mengambil sisa divisi): rowIndex & 0xF. Indeks yang diinginkan dalam grup adalah: 23 & 0xF = 7;
- Setelah itu, Excel mendapatkan offset untuk indeks 7: offset = Indeks [7]. Jika offset = 0, maka Excel mengalokasikan 8 byte di akhir RowsGroupInto, menambah RowsCount per satu, dan menulis offset baru ke Indeks [7]. Dalam hal apa pun, pada akhirnya, Excel menulis informasi tentang tinggi baris baru dan tanda pada offset di RowsGroupInfo.
Kelas RowsGroupInfo di C # itu sendiri akan terlihat seperti ini:
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; } } }
Metode GetRowHeightCore akan terlihat seperti ini:
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; }
Dan inilah yang akan terlihat oleh SetRowHeight (saya tidak mencantumkan kodenya dari 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; }
Sedikit latihan
Setelah contoh di atas, dengan perubahan ketinggian baris 23, Excel akan terlihat seperti ini (saya menetapkan baris 23 ke ketinggian 75 poin):
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 23
- GroupIndexDelta = -1
- Hitungan RowsGroups = 1
- [0] RowsGroupInfo
- Hitungan HeightInfos = 1
- [0] RowHeightInfo
- Bendera = 0x4005
- Nilai = 100
- Indeks
- [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
Di sini dan dalam contoh berikut, saya akan memberikan gambaran skematis tentang bagaimana data dalam memori Excel terlihat, dibuat dalam Visual Studio dari kelas yang ditulis sendiri, karena pembuangan langsung dari memori tidak terlalu informatif.
Sekarang mari kita coba sembunyikan baris 23. Untuk melakukan ini, atur bit 0x2000. Kami akan mengubah memori untuk hidup. Hasilnya dapat dilihat di video ini:
http://recordit.co/79vYIbwbzB .
Setiap kali Anda menyembunyikan baris, Excel melakukan hal yang sama.
Sekarang mari kita atur tinggi baris tidak secara eksplisit, tetapi melalui format sel. Biarkan font di sel A20 menjadi ketinggian 40 poin, maka tinggi sel dalam poin akan menjadi 45,75 dan dalam memori Excel akan menjadi sesuatu seperti ini:
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 20
- GroupIndexDelta = -1
- Hitungan RowsGroups = 1
- [0] RowsGroupInfo
- Hitungan HeightInfos = 2
- [0] RowHeightInfo
- Bendera = 0x4005
- Nilai = 100
- [1] RowHeightInfo
- Bendera = 0x800E
- Nilai = 244
- Indeks
- [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
Anda mungkin memperhatikan bahwa Excel selalu menyimpan tinggi baris jika tidak standar. Sekalipun tingginya tidak disetel secara eksplisit, tetapi dihitung berdasarkan konten sel atau format, Excel masih akan menghitungnya sekali dan meletakkan hasilnya di grup yang sesuai.
Kami berurusan dengan penyisipan / penghapusan baris
Akan menarik untuk menguraikan apa yang terjadi ketika memasukkan / menghapus baris. Kode yang sesuai di excel.exe tidak sulit ditemukan, tetapi tidak ada keinginan untuk membongkarnya, Anda dapat melihat bagian darinya:
sub_305EC930Bendera a5 menentukan operasi mana yang sedang berlangsung.
int __userpurge sub_305EC930@<eax>(int a1@<eax>, int a2@<edx>, int a3@<ecx>, int a4, int a5, int a6) { int v6;
Selain itu, dalam penampilan, Anda dapat secara kasar memahami apa yang terjadi di sana, dan sisanya berakhir dengan tanda tidak langsung.
Kami mencoba mengidentifikasi fitur-fitur tidak langsung ini. Pertama, atur ketinggian untuk baris 16 hingga 64 inklusif dalam urutan acak. Kemudian, di depan baris di bawah indeks 39, masukkan baris baru. Baris baru akan menyalin ketinggian baris 38.
Mari kita lihat informasi dalam kelompok seri sebelum dan sesudah menambahkan seri, saya menyoroti perbedaan yang mencolok:
Sebelum menambahkan baris | Setelah menambahkan satu baris |
---|
Perpindahan di grup pertama: | Perpindahan di grup pertama: |
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 |
Nilai-nilai ketinggian baris di grup pertama: | Nilai-nilai ketinggian baris di grup pertama: |
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 |
Perpindahan di grup kedua: | Perpindahan di grup kedua: |
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 |
Nilai ketinggian baris pada grup kedua: | Nilai ketinggian baris pada grup kedua: |
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 |
Perpindahan di kelompok ketiga: | Perpindahan di kelompok ketiga: |
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 |
Nilai ketinggian baris pada grup ketiga: | Nilai ketinggian baris pada grup ketiga: |
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 |
Offset di grup keempat: | Offset di grup keempat: |
_ | 00 |
Nilai-nilai ketinggian baris di grup keempat: | Nilai-nilai ketinggian baris di grup keempat: |
_ | 40 |
Inilah yang diharapkan: Excel memasukkan ke grup kedua baris baru dengan indeks 7 (39 & 0xF), yang offsetnya 0x05, menyalin tinggi baris pada indeks 6, sedangkan baris terakhir, yang diimbangi 05, didorong ke berikutnya grup, dan dari sana baris terakhir didorong ke keempat, dll.
Sekarang mari kita lihat apa yang terjadi jika Anda menghapus baris ke-29.
Sebelum menghapus satu baris | Setelah menghapus baris |
---|
Perpindahan di grup pertama: | Perpindahan di grup pertama: |
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 |
Nilai-nilai ketinggian baris di grup pertama: | Nilai-nilai ketinggian baris di grup pertama: |
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 |
Perpindahan di grup kedua: | Perpindahan di grup kedua: |
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 |
Nilai ketinggian baris pada grup kedua: | Nilai ketinggian baris pada grup kedua: |
10, 15, 20, 25, 30, F0, 85 , 90, 9B, A0, C5, CB, D0, D5, E5, F0 | 10, 15, 20, 25, F0, 75 , 90, 9B, A0, C5, CB, D0, D5, E5, F0 |
Perpindahan di kelompok ketiga: | Perpindahan di kelompok ketiga: |
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 |
Nilai ketinggian baris pada grup ketiga: | Nilai ketinggian baris pada grup ketiga: |
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 |
Offset di grup keempat: | Offset di grup keempat: |
00 | 00 |
Nilai-nilai ketinggian baris di grup keempat: | Nilai-nilai ketinggian baris di grup keempat: |
40 | 50 |
Pada prinsipnya, ketika sebuah baris dihapus, operasi terbalik terjadi. Dalam hal ini, grup keempat terus ada dan nilai tinggi baris di sana diisi dengan tinggi standar dengan bendera yang sesuai - 0x8005.
Data ini cukup untuk mereproduksi algoritma ini dalam C #:
Masukkan 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); } }
Anda dapat menemukan semua kode di atas di GitHub
Kesimpulan
Ini bukan pertama kalinya kode Excel terkejut dengan trik menarik. Kali ini saya menemukan bagaimana dia menyimpan informasi tentang ketinggian baris. Jika komunitas tertarik, maka pada artikel berikutnya saya akan menunjukkan bagaimana Excel mempertimbangkan ketinggian rentang sel (spoiler: ada sesuatu yang mirip dengan dekomposisi SQRT, tetapi untuk beberapa alasan tanpa caching jumlah), di sana Anda dapat melihat bagaimana itu menerapkan penskalaan dalam bilangan bulat .