Rangeオブジェクトの終端があるParagraphオブジェクトのインデックス番号を返すFunction(Word)

Rangeオブジェクトの終端があるParagraphオブジェクトのインデックス番号を返すFunction

更新頻度ガタ落ちですが、またしてもWordVBAネタです。

まずはコードを

お急ぎの方は、コードをコピッペして使ってください。

リスト1
Public Function getParagraphIndex( _
            ByVal tgtRange As Range) As Long
  Dim ret As Long
  tgtRange.Start = 0
  ret = tgtRange.Paragraphs.Count
  getParagraphIndex = ret
End Function

たったこんだけ。正味2行w

まさかこんなに簡単にできるとは思っていませんでした。

使ってみる

たとえば、テキトーなドキュメントを用意して、

f:id:akashi_keirin:20200704095029j:plain

こんなふうにテキトーに範囲を選択しておく。

画像では、4~5段落にまたがった範囲を選択している。

で、イミディエイト・ウィンドウに次のコードを書いて[Enter]を押す。

?getParagraphIndex(Selection.Range)

すると、

f:id:akashi_keirin:20200704095032j:plain

ちゃんと「5」が出力されておる。

バッチリ!

解説

こんなしょうもないコードだが、二つも発見があった。

短いコードなので再掲する。

リスト1(再掲)
Public Function getParagraphIndex( _
            ByVal tgtRange As Range) As Long
  Dim ret As Long
  tgtRange.Start = 0  '……(1)'
  ret = tgtRange.Paragraphs.Count  '……(2)'
  getParagraphIndex = ret
End Function

Start(End)プロパティはRead/Writeだった

これは、完全に思い込み。

勝手にRead onlyだと固く信じて疑っていなかった。

Microsoft Docsの「Range.Start Property」の項にも、

Range.Start property (Word)

Returns or sets the starting character position of a range. Read/write Long.

と明記してあるし。

(1)の

tgtRange.Start = 0

によって、tgtRangeが指し示すRangeオブジェクトの始端をドキュメントの先頭にしているわけだ。

RangeオブジェクトにもParagraphsコレクションがある

これも全然知らなかった。

Paragraphs」というぐらいだからてっきりDocumentオブジェクトの直参だと思っていた。

これまた、Microsoft Docsの「Range.Paragraphs Property」の項にはっきりと

Range.Paragraphs property (Word)

Returns a Paragraphs collection that represents all the paragraphs in the specified range. Read-only.

と書いてある。

つまり、(1)を実行した段階で、tgtRangeが指し示すRangeオブジェクトの範囲は、〈ドキュメントの始端~選択範囲の終端〉になっているわけなので、(2)の

ret = tgtRange.Paragraphs.Count

によって、〈ドキュメントの始端~選択範囲の終端〉に含まれる段落数、すなわち選択範囲の終端がある段落が先頭から数えて何番目か、を表す数値が変数retに返る、というわけだ。

最初にこのアイディアを考えついたやつは天才だと思う。

おわりに

世の中、天才だらけでいやになるぜ。

追記(2022/02/18)

実は、ここで示したコードには重大なバグがあります。

なんと、段落の先頭にカーソルを置いて、引数tgtRangeSelection.Rangeを渡すと、一つ前の段落のインデックスを返してしまうのです。

取り急ぎ、応急処置をしたものを次に挙げておきます。

Public Function GetParagraphIndex( _
            ByVal a_Target As Range) As Long
  Dim ret As Long
  Dim rng As Range
  Set rng = a_Target
  rng.Start = 0
  ret = rng.Paragraphs.Count
  Dim pos As Long
  pos = rng.End
  If pos = 0 Then GoTo ReturnValue
  Dim char As String
  char = rng.Parent.Range(pos - 1, pos).Text
  If char = Chr(13) Then
    ret = ret + 1
  End If
ReturnValue:
  GetParagraphIndex = ret
End Function

この記事を書いた当時から、コーディングスタイルが変わったので、ちょっと書きぶりは違いますが。

一応、引数a_Targetで渡されたRangeオブジェクトのStartプロパティを0にしたときに、Rangeオブジェクトの終端が改段落だったら1を加算して返すようにしました。

これで良いのかどうか、引き続き検証します……。