Às vezes fico entediado e, armado com um depurador, começo a pesquisar em diferentes programas. Dessa vez, minha escolha foi no Excel e havia um desejo de descobrir como ele lida com as alturas das linhas, o que as armazena, como considera a altura de um intervalo de células etc. Analisei o Excel 2010 (excel.exe, 32 bits, versão 14.0.4756.1000, SHA1 a805cf60a5542f21001b0ea5d142d1cd0ee00b28).
Vamos começar com a teoria
Se você recorrer à documentação do VBA para Microsoft Office, poderá ver que a altura da linha pode ser obtida de uma maneira ou de outra através de duas propriedades:
- RowHeight - Retorna ou define a altura da primeira linha no intervalo especificado, medido em pontos. Leitura / gravação dupla;
- Altura - Retorna um valor Duplo que representa a altura, em pontos, do intervalo. Somente leitura.
E se você também procurar aqui: Especificações e limites do Excel . Você pode descobrir que a altura máxima da linha é 409 pontos. Infelizmente, isso está longe de ser o único caso em que os documentos oficiais da Microsoft são um pouco enganadores. De fato, no código do Excel, a altura máxima da linha é definida como 2047 pixels, que será 1535,25 em pontos. E o tamanho máximo da fonte é 409,55 pontos. É impossível obter uma linha com uma altura tão grande atribuindo-a simplesmente no VBA / Interop, mas você pode fazer uma linha, definir a fonte Cambria Math em sua primeira célula e definir o tamanho da fonte para 409,55 pontos. Em seguida, o Excel, com seu algoritmo de astúcia, calculará a altura da linha com base no formato da célula, obterá um número superior a 2047 pixels (acredite na palavra) e definirá a linha para a altura máxima possível. Se você perguntar a altura desta série por meio da interface do usuário, o Excel indicará que a altura é 409,5 pontos, mas se você solicitar a altura da série por meio do VBA, obterá 1535,25 pontos honestos, o que equivale a 2047 pixels. É verdade que, depois de salvar o documento, a altura ainda cairá para 409,5 pontos. Essa manipulação pode ser observada aqui neste vídeo: http://recordit.co/ivnFEsELLI
Mencionei os pixels no parágrafo anterior por um motivo. Na verdade, o Excel armazena e calcula o tamanho das células em números inteiros (geralmente faz tudo o máximo possível em números inteiros). Na maioria das vezes, esses pixels são multiplicados por algum fator. Curiosamente, o Excel armazena a escala de aparência na forma de uma fração comum, por exemplo, uma escala de 75% será armazenada como dois números 3 e 4. E quando for necessário exibir a linha, o Excel assumirá a altura da linha como um número inteiro de pixels, multiplique por 3 e divida por 4. Mas ele executará essa operação já no final, a partir disso, cria-se o efeito de que tudo seja considerado em números fracionários. Para verificar isso, escreva o seguinte código no VBA:
w.Rows(1).RowHeight = 75.375 Debug.Print w.Rows(1).Height
VBA vai dar 75 porque 75.375 pixels serão 100,5 pixels, e o Excel não pode pagar e reduzirá a parte fracionária em até 100 pixels. Quando o VBA solicita a altura da linha em pontos, o Excel honestamente converte 100 pixels em pontos e retorna 75.
Em princípio, já começamos a escrever uma classe em C # que descreverá informações sobre a altura da linha:
class RowHeightInfo { public ushort Value { get; set; }
Você terá que aceitar minha palavra por enquanto, mas no Excel, a altura da linha é armazenada dessa maneira. Ou seja, se for especificado que a altura da linha é 75 pontos, será 100 em pixels e 400 serão armazenados em Valor. Ainda não entendi o que todos os bits em Flags significam (é difícil e longo descobrir os valores dos sinalizadores), mas tenho certeza 0x4000 é definido para linhas cuja altura é definida manualmente e 0x2000 - é definido para linhas ocultas. Em geral, para linhas visíveis com uma altura definida manualmente, Flags geralmente é igual a 0x4005 e para linhas nas quais a altura é calculada com base na formatação de Flags, é 0xA ou 0x800E.
Pedimos a altura da linha
Agora, em princípio, você pode dar uma olhada no método em excel.exe, que retorna a altura da linha por seu índice (graças a HexRays pelo código bonito):
int __userpurge GetRowHeight@<eax>(signed int rowIndex@<edx>, SheetLayoutInfo *sheetLayoutInfo@<esi>, bool flag) { RowHeightInfo *rowHeightInfo;
O que é dword1A0, ainda não descobri. não foi possível encontrar um local em que esse sinalizador está definido :(
O que é defaultRowDelta2 para mim também continua sendo um mistério. Quando o Excel calcula a altura da linha com base no formato, ele representa a soma de dois números. defaultRowDelta2 é o segundo número dessa soma para a altura da linha padrão. O valor do parâmetro flag também é misterioso, pois onde quer que eu vi uma chamada para esse método na flag passou false.
A classe SheetLayoutInfo também aparece nesse método. Eu o nomeei assim, porque ele armazena muitas informações sobre a aparência da planilha. No SheetLayoutInfo, existem campos como:
- DefaultFullRowHeightMul4 - altura da linha padrão;
- MinRowIndexNonDefault - o índice da primeira linha na qual a altura difere do padrão;
- MaxRowIndexNonDefault - índice da série após a última, na qual a altura difere da padrão;
- DefaultRowDelta2 é a mesma parte da soma da altura da linha padrão.
- GroupIndexDelta - mais sobre isso mais tarde
Em princípio, a lógica deste método é compreensível:
- Se o índice da série for menor que o primeiro com uma altura não padrão, retorne o padrão;
- Se o índice da série for maior que o último com uma altura não padrão, retorne o padrão;
- Caso contrário, obteremos o objeto rowHeightInfo da série do método GetRowHeightCore;
- Se rowHeightInfo == null retorne a altura da linha padrão;
- Existe mágica com sinalizadores, mas, em geral, retornamos o que está em rowHeightInfo.Value e configuramos o 16º bit na resposta se a altura da linha não foi definida manualmente.
Se você reescrever esse código em C #, obterá algo como o seguinte:
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; }
Agora você pode dar uma olhada no que está acontecendo no GetRowHeightCore:
RowHeightInfo *__fastcall GetRowHeightCore(SheetLayoutInfo *sheetLayoutInfo, signed int rowIndex) { RowHeightInfo *result;
- Novamente, no início, o Excel verifica se o índice da série está entre as linhas com uma altura alterada e, se não, retorna nulo.
- Localiza o grupo de linhas desejado; se não houver um grupo, ele retornará nulo.
- Obtém o índice da série no grupo.
- Além disso, pelo índice da série, ele encontra o objeto desejado da classe RowHeightInfo. wordBA, wordBC, wordB8 - algumas constantes. Eles mudam apenas com a história. Em princípio, eles não afetam o entendimento do algoritmo.
Aqui vale a pena desviar do tópico e contar mais sobre o RowsGroupInfo. O Excel armazena RowHeightInfo em grupos de 16 partes, onde o i-ésimo grupo, representado pela classe RowsGroupInfo, armazenará informações sobre linhas de i × 16 a i × 16 + 15, inclusive.
Mas as informações de altura da linha no RowsGroupInfo são armazenadas de uma maneira um tanto incomum. Provavelmente devido à necessidade de manter o histórico no Excel.
Existem três campos importantes no RowsGroupInfo: Indices, HeightInfos e RowsCount, o segundo não é visível no código acima (ele deve estar nesta linha: (linesGroupInfo + 8 × (...)), porque rowInfoIndex pode assumir valores muito diferentes , Já vi mais de 1000 e não tenho ideia de como definir essa estrutura na IDA.O campo RowsCount não aparece no código acima, mas é quantas linhas realmente não padrão são armazenadas no grupo.
Além disso, em SheetLayoutInfo, há GroupIndexDelta - a diferença entre o índice real do grupo e o índice do primeiro grupo com uma altura de linha modificada.
O campo Índices armazena as compensações de RowHeightInfo para cada índice da série dentro do grupo. Eles são armazenados lá em ordem, mas em HeightInfos RowHeightInfo já estão armazenados na ordem de alteração.
Suponha que tenhamos uma nova planilha em branco e, de alguma forma, alteramos a altura do número da linha 23. Essa linha está no segundo grupo de 16 linhas;
- O Excel determinará o índice do grupo para esta série. No caso atual, o índice será 1 e mudará GroupIndexDelta = -1.
- Cria um objeto da classe RowsGroupInfo para uma série de linhas e o coloca em sheetLayoutInfo-> RowsGroups sob o índice 0 (sheetLayoutInfo-> GroupIndexDelta + 1);
- No RowsGroupInfo, o Excel alocará memória para 16 índices de 4 bytes, para RowsCount, wordBA, wordBA, wordBC e wordB8, etc.;
- Em seguida, o Excel calcula o índice da série no grupo através da operação AND bit a bit (isso é muito mais rápido do que o restante da divisão): rowIndex & 0xF. O índice desejado no grupo será: 23 & 0xF = 7;
- Depois disso, o Excel obtém o deslocamento para o índice 7: deslocamento = índices [7]. Se deslocamento = 0, o Excel aloca 8 bytes no final de RowsGroupInto, aumenta o RowsCount em um e grava o novo deslocamento nos Índices [7]. De qualquer forma, no final, o Excel grava informações sobre a nova altura da linha e sinalizadores no deslocamento no RowsGroupInfo.
A classe RowsGroupInfo no próprio C # ficaria assim:
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; } } }
O método GetRowHeightCore ficaria assim:
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; }
E é assim que o SetRowHeight se pareceria (eu não listei seu código no 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; }
Um pouco de prática
Após o exemplo acima, com uma alteração na altura da linha 23, o Excel se parecerá com isso (eu defino a linha 23 com 75 pontos de altura):
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 23
- GroupIndexDelta = -1
- Contagem de RowsGroups = 1
- [0] RowsGroupInfo
- HeightInfos Count = 1
- [0] RowHeightInfo
- Sinalizadores = 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
Aqui e no exemplo a seguir, apresentarei uma representação esquemática de como são os dados na memória do Excel, criados no Visual Studio a partir de classes auto-escritas, porque um despejo direto da memória não é muito informativo.
Agora vamos tentar ocultar a linha 23. Para fazer isso, defina o bit 0x2000 dos sinalizadores. Vamos mudar a memória para viver. O resultado pode ser visto neste vídeo:
http://recordit.co/79vYIbwbzB .
Sempre que você oculta linhas, o Excel faz o mesmo.
Agora vamos definir a altura da linha não explicitamente, mas através do formato da célula. Deixe a fonte na célula A20 se tornar uma altura de 40 pontos, então a altura da célula em pontos será 45,75 e na memória do Excel será algo como isto:
sheetLayoutInfo- DefaultFullRowHeightMul4 = 80
- DefaultRowDelta2 = 5
- MaxRowIndexNonDefault = 24
- MinRowIndexNonDefault = 20
- GroupIndexDelta = -1
- Contagem de RowsGroups = 1
- [0] RowsGroupInfo
- HeightInfos Count = 2
- [0] RowHeightInfo
- Sinalizadores = 0x4005
- Valor = 100
- [1] RowHeightInfo
- Sinalizadores = 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
Você pode perceber que o Excel sempre armazena a altura da linha, se não for padrão. Mesmo que a altura não seja definida explicitamente, mas seja calculada com base no conteúdo das células ou formato, o Excel ainda a calculará uma vez e colocará o resultado no grupo apropriado.
Lidamos com a inserção / exclusão de linhas
Seria interessante analisar o que acontece ao inserir / excluir linhas. O código correspondente no excel.exe não é difícil de encontrar, mas não havia o desejo de desmontá-lo; você pode dar uma olhada em uma parte dele:
sub_305EC930O sinalizador a5 determina qual operação está ocorrendo atualmente.
int __userpurge sub_305EC930@<eax>(int a1@<eax>, int a2@<edx>, int a3@<ecx>, int a4, int a5, int a6) { int v6;
Além disso, na aparência, você pode entender aproximadamente o que está acontecendo lá e o restante para terminar com sinais indiretos.
Tentamos identificar esses recursos indiretos. Primeiro, defina a altura das linhas 16 a 64, inclusive em ordem aleatória. Em seguida, na frente da linha no índice 39, insira uma nova linha. A nova linha copiará a altura da linha 38.
Vejamos as informações nos grupos de séries antes e depois de adicionar uma série, destaquei as diferenças arrojadas:
Antes de adicionar uma linha | Depois de adicionar uma linha |
---|
Deslocamentos no primeiro grupo: | Deslocamentos no primeiro 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 |
Os valores das alturas das linhas no primeiro grupo: | Os valores das alturas das linhas no primeiro 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 |
Deslocamentos no segundo grupo: | Deslocamentos no 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 |
Os valores das alturas das linhas no segundo grupo: | Os valores das alturas das linhas no 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 |
Deslocamentos no terceiro grupo: | Deslocamentos no terceiro 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 |
Os valores das alturas das linhas no terceiro grupo: | Os valores das alturas das linhas no terceiro 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 |
Compensações no quarto grupo: | Compensações no quarto grupo: |
_ | 00 |
Os valores das alturas das linhas no quarto grupo: | Os valores das alturas das linhas no quarto grupo: |
_ | 40. |
Era o que era esperado: o Excel insere no segundo grupo uma nova linha com o índice 7 (39 & 0xF), cujo deslocamento é 0x05, copia a altura da linha no índice 6, enquanto a última linha, que foi deslocada 05, é empurrada para a próxima grupo, e a partir daí a última linha é empurrada para a quarta, etc.
Agora vamos ver o que acontece se você excluir a 29ª linha.
Antes de remover uma linha | Depois de remover a linha |
---|
Deslocamentos no primeiro grupo: | Deslocamentos no primeiro 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 |
Os valores das alturas das linhas no primeiro grupo: | Os valores das alturas das linhas no primeiro 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 |
Deslocamentos no segundo grupo: | Deslocamentos no 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 |
Os valores das alturas das linhas no segundo grupo: | Os valores das alturas das linhas no 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 |
Deslocamentos no terceiro grupo: | Deslocamentos no terceiro 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 |
Os valores das alturas das linhas no terceiro grupo: | Os valores das alturas das linhas no terceiro 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 |
Compensações no quarto grupo: | Compensações no quarto grupo: |
00 | 00 |
Os valores das alturas das linhas no quarto grupo: | Os valores das alturas das linhas no quarto grupo: |
40. | 50. |
Em princípio, quando uma linha é excluída, ocorrem operações reversas. Nesse caso, o quarto grupo continua a existir e o valor da altura da linha é preenchido com a altura padrão com o sinalizador correspondente - 0x8005.
Esses dados são suficientes para reproduzir esse algoritmo em 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); } }
Removow 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); } }
Você pode encontrar todo o código acima no GitHub
Conclusões
Esta não é a primeira vez que o código do Excel é surpreendido por truques interessantes. Dessa vez, descobri como ele armazena informações sobre as alturas das linhas. Se a comunidade estiver interessada, no próximo artigo, mostrarei como o Excel considera a altura do intervalo de células (spoiler: existe algo semelhante à decomposição do SQRT, mas por algum motivo sem cache de soma), é possível ver como ele aplica o dimensionamento em números inteiros .