Ao escrever macros VBA no Word, você pode notar que o objeto Range e o objeto Selection retornam posições inicial e final diferentes para o que parece ser o mesmo texto. Essa discrepância causa erros de macro, formatação incorreta e comportamento inesperado em tarefas automatizadas. A causa raiz está em como Range e Selection rastreiam o conteúdo: Range usa posições de caracteres no nível da história (story), enquanto Selection usa a janela de exibição atual e limites recolhidos. Este artigo explica por que os limites diferem, demonstra como inspecionar ambos os objetos e fornece métodos confiáveis para alinhá-los para uma execução consistente de macros.
Principais Conclusões: Entendendo as Diferenças de Posição entre Range e Selection
- Range.Start e Range.End usam contagens de caracteres relativas à história: Esses valores incluem todo o texto na história principal, incluindo cabeçalhos, rodapés e caixas de texto.
- Selection.Start e Selection.End usam contagens de caracteres relativas ao documento: Esses valores se referem apenas ao corpo visível do documento e excluem elementos de histórias não principais.
- Use Range.DocumentRange para obter posições consistentes em todas as histórias: Este método retorna um objeto Range que cobre todo o documento, tornando a comparação com Selection confiável.
Como o Word VBA Define Range vs Selection
O objeto Range no VBA do Word representa uma área contígua de um documento, definida por uma posição inicial e final. Essas posições são medidas a partir do início da história (story) que contém o intervalo. Uma história é uma parte do documento que pode conter texto independentemente, como o corpo principal, cabeçalhos, rodapés, notas de rodapé, notas de fim ou caixas de texto. Cada história tem seu próprio índice de caracteres baseado em zero. Por exemplo, a história do corpo principal começa na posição 0, mas uma história de cabeçalho também começa na posição 0 dentro dessa história. Quando você cria um objeto Range usando ActiveDocument.Range(Start, End), as posições são relativas apenas à história do corpo principal.
O objeto Selection, por outro lado, representa a área atualmente selecionada na janela ativa. Suas propriedades Start e End retornam posições relativas ao documento inteiro, mas apenas para a história do corpo principal. Selection não inclui texto de cabeçalhos, rodapés ou outras histórias, a menos que essas histórias sejam ativadas especificamente. Além disso, Selection pode existir em um estado recolhido (nenhum texto selecionado) onde Start é igual a End. Esse estado recolhido é comum após usar comandos como Selection.Collapse ou após inserir texto via Selection.TypeText.
A principal diferença que causa discrepâncias nos limites é o escopo da referência de posição. Range.Start e Range.End podem apontar para qualquer história, enquanto Selection.Start e Selection.End sempre apontam para a história do corpo principal. Se o seu objeto Range aponta para uma história de cabeçalho, suas posições serão completamente diferentes das posições de Selection que apontam para o corpo principal, mesmo que o texto visível pareça semelhante.
Por que as Posições de Range e Selection Diferem para o Mesmo Texto
Considere um documento com um cabeçalho contendo a palavra “Rascunho” e um corpo contendo “Este é o conteúdo principal.” Se você executar uma macro que define um Range para a história do cabeçalho, Range.Start pode retornar 0 e Range.End pode retornar 8 (o comprimento de “Rascunho”). Enquanto isso, Selection.Start pode retornar 0 e Selection.End pode retornar 28 para o texto do corpo. Ambos os objetos se referem a histórias diferentes, então suas posições não são comparáveis. Mesmo se você selecionar o texto do cabeçalho manualmente, Selection ainda reportará posições relativas à história do corpo principal, não à história do cabeçalho. Isso acontece porque Selection sempre opera na história do corpo principal, a menos que você ative explicitamente uma história diferente usando Selection.StoryType.
Outra causa é o estado recolhido de Selection. Após certas operações como Selection.Find.Execute, a Selection pode recolher para o início do texto encontrado. Nesse caso, Selection.Start é igual a Selection.End, mas o objeto Range criado a partir da mesma operação de localização pode reter o intervalo completo encontrado. Isso leva a incompatibilidades ao comparar limites no código.
Passos para Inspecionar e Alinhar os Limites de Range e Selection
Método 1: Usar DocumentRange para Referência Consistente
- Abra o Editor VBA
Pressione Alt+F11 para abrir o editor VBA no Word. Insira um novo módulo no menu Inserir. - Escreva uma macro para exibir os limites de ambos os objetos
Cole o seguinte código:Sub CompararLimites()
Dim rng As Range
Set rng = ActiveDocument.Range
MsgBox "Início do Range: " & rng.Start & vbCrLf & "Fim do Range: " & rng.End
MsgBox "Início da Selection: " & Selection.Start & vbCrLf & "Fim da Selection: " & Selection.End
End Sub - Execute a macro com diferentes seleções
Selecione texto no corpo e execute a macro. Anote os valores. Agora selecione texto em um cabeçalho (clique duas vezes na área do cabeçalho) e execute a macro novamente. O objeto Range mostrará posições para o documento inteiro, enquanto Selection mostrará posições relativas apenas ao corpo principal. - Use DocumentRange para obter o documento completo
Modifique a macro para usarActiveDocument.Range(que retorna a história do corpo principal) e compare com Selection. Para uma referência consistente que inclua todas as histórias, useActiveDocument.StoryRangespara iterar por cada história.
Método 2: Converter Selection em Range para Comparação Direta
- Crie um Range a partir da Selection atual
UseSet rng = Selection.Rangepara criar um objeto Range que espelha a Selection. Esse Range terá o mesmo Start e End que a Selection, mas é um objeto separado que você pode manipular sem afetar a seleção. - Compare o Range com outro Range
Se você tiver um Range de uma história diferente, não pode comparar posições diretamente. Em vez disso, compare o texto ou use o métodoInRange:If rng1.InRange(rng2) Then. - Evite depender de Start e End para comparações entre histórias
Em vez de comparar posições numéricas, use a propriedadeRange.Textpara verificar igualdade de conteúdo, ou useRange.Documentpara acessar o documento pai e depois usar métodos no nível do documento.
Método 3: Forçar Selection a uma História Específica
- Ative a história desejada
UseActiveDocument.StoryRanges(wdPrimaryHeaderStory).Selectpara definir a seleção para a história do cabeçalho principal. Após isso, Selection.Start e Selection.End refletirão posições dentro dessa história. - Verifique a propriedade StoryType
Após ativar, verifique comSelection.StoryTypepara garantir que você está na história correta. Isso ajuda a depurar por que os limites diferem. - Redefina para o corpo principal quando terminar
UseActiveDocument.Range(0, 0).Selectpara retornar a Selection ao início da história do corpo principal. Isso evita que o código subsequente opere na história errada.
Problemas Comuns Quando os Limites de Range e Selection Diferem
Range.Start Retorna um Valor Maior que o Comprimento do Documento
Se o seu objeto Range aponta para uma história diferente do corpo principal, seus valores Start e End podem exceder o comprimento do documento principal. Por exemplo, um Range em uma história de rodapé pode ter Start=0 e End=50, mas o documento principal pode ter apenas 200 caracteres. Para corrigir isso, sempre verifique a propriedade Range.StoryType antes de usar Start ou End para cálculos. Se precisar trabalhar com o corpo principal, use ActiveDocument.Range explicitamente.
Selection.End é Menor que Range.End Após uma Operação de Localizar
Após um Selection.Find.Execute, a Selection frequentemente recolhe para o início do texto encontrado, fazendo com que Selection.End seja igual a Selection.Start. O objeto Range criado a partir de Selection.Range antes da localização retém os limites originais. Para preservar o intervalo encontrado, atribua o intervalo encontrado a uma variável Range imediatamente após a localização: Set rng = Selection.Range. Em seguida, use rng.End para obter a posição final completa.
Range.Start e Selection.Start São Ambos Zero, mas o Texto é Diferente
Isso acontece quando o Range está no início de uma história não principal (como um cabeçalho) e a Selection está no início do corpo principal. Ambos retornam 0, mas se referem a histórias diferentes. Para diferenciar, verifique Range.StoryType e Selection.StoryType. Se forem diferentes, você está comparando posições de histórias diferentes. Use o método Range.DocumentRange para obter um Range que abranja todo o documento para uma referência unificada.
| Item | Objeto Range | Objeto Selection |
|---|---|---|
| Referência de posição | Relativa à história que contém o intervalo | Relativa apenas à história do corpo principal |
| Pode abranger várias histórias | Sim, via coleção StoryRanges | Não, sempre dentro de uma história por vez |
| Estado recolhido possível | Sim, Start igual a End | Sim, após Collapse ou TypeText |
| Afetado pela janela de exibição | Não, independente da janela | Sim, depende da janela ativa e da seleção |
| Melhor para operações entre histórias | Sim, use StoryRanges | Não, deve ativar cada história separadamente |
Agora você pode identificar por que Range e Selection retornam limites diferentes e ajustar seu código VBA de acordo. Use a coleção StoryRanges para iterar por todas as histórias quando precisar de cobertura em todo o documento. Para comparações diretas, converta Selection em um objeto Range e use métodos como InRange em vez de comparar posições numéricas. Como dica avançada, use ActiveDocument.StoryRanges(wdMainTextStory) para sempre obter a história do corpo principal explicitamente, evitando erros acidentais entre histórias em suas macros.