今回のテーマ
Word.Application.ActiveDocument.Table(1)
品名 | 数量 | 単位 | 単価 | 本体価格 | 税率 | 8 | 10 |
---|---|---|---|---|---|---|---|
コーヒー | 100 | 杯 | 100 | 10000 | 8 | * |
まずこの表(table)はドキュメントに一つだけとします。
カーソルは今*の位置にあるとします。
もし税率が8%ならこのセルに本体価格が入るIF文をVBAで入力するにはどうすればいいか。
Excelなら。。。
WordでIf文を入れるときはA1形式になります。
Excelなら
=IF(F2=8,D2,0)
という式になります。
Wordなら
Wordは
{IF { =F2) = 8 {=D2} "0"}
と書きます。
まず他のセルを参照するときは必ずフィールドコードで参照します。
単純に=F2
ではありません。
次にセルを参照したフィールドコードのあとに比較演算子を入れますが、スペースが必要です。
VBAはスペースではなく移動
実はここが罠です。
手動で入力するときはスペースですが、VBAで構築するときはカーソルの移動になります。
しかも2つ移動します。
また、値は""
で囲みますが、フィールドコードは不要です。
コンマで区切りません。
TrueとFalseの間も2つ移動です。単位はキャラクターです。
IFフィールドではなく計算式のIF関数を使う
Word2010:IFフィールドとIF関数の違い
IFフィールド(コード)と計算式のフィールドコードのIF関数には違いがあります。
それは計算式のIFフィールドはそのセルの値として参照できますが、IFフィールドコードは0となるということです。もしくは文字列としてカウントされるようです。このときは{3000}
のように中カッコが表示されます。こうならない時もあり、再現性が難しいのですが。
ここがあとの計算式のフィールドコードのSum関数とは真逆の結論になりますので注意してください。
余談ですが、この記事の
{=IF(a1>=80 , 1 , -1) \# " 'OK' ; 'NG' " }
これですが、マイナスは-1
ではなく(1)
がいいです。文字列として扱われる危険があるためです。またハイフンかそうではないか間違うと大変なので、(1)と半角のカッコで包む方が良いです。
また当方では、 {IF (C4 = 10 , NG , 0) \* MERGEFORMAT}
というのは成功します。これはWordのバージョンの違いかもしれません。このため、やはり計算式のフィールドを使います。
コード
今上記の*の欄にカーソルがあるとすると、F2が8ならE2を持ってくることになります
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
PreserveFormatting:=False
Selection.TypeText Text:="IF "
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
PreserveFormatting:=False
Selection.TypeText Text:="=F2" '手入力はスペースを挿入するが、VBAはIFのあとは移動しない。
Selection.MoveRight Unit:=wdCharacter, Count:=2 ' VBAはキャラクター単位で移動。1文字目がフィールドの選択を外し、次の文字が実際の移動になる。次以降も同じ
Selection.TypeText Text:=" = 8 "
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
PreserveFormatting:=False
Selection.TypeText Text:="=E2"
Selection.MoveRight Unit:=wdCharacter, Count:=2
Selection.TypeText Text:=" ""0"""
Selection.Fields.Update 'コードが計算される
Selection.ParagraphFormat.Alignment = wdAlignParagraphRight ’右詰めになる
計算式のSumフィールドはフィールドコードのままになる
上の表より簡略化されていますが、原理は同じです。
ここで10%対象の10列欄を御覧ください。
Sumは中括弧でくくられています[3000]
の[]
はブックマークです。一旦無視して
{3000}
と表記されていると考えて下さい。
{=Sum(E2:E4) \#"#,0"}
となっています。
すると式はこのように中括弧が出てきたまま表示されます。この画像の表のSUmが入っている位置は、E5です。
ブックマークの名称はSum10です。
さて税率をE5で引っ張ったとします。
=INT(E5*0.1+0.000001)
Ctrl+A --> F9で更新します。
ご覧のように結果は0です。
つまりフィールドコードのままでは値が参照できません。
ブックマークの付け方にも要注意
ブックマークをフィールド全体を選択して挿入すると[{3000}]
となります。
すると{=Sum10}の結果はやはり0です。
どうするかと言うと数式を入力して更新したあと、1文字右に移動して数字だけ選択してブックマークを挿入します。
すると値が持ってこれます。
カーソルが画像の表の3000の位置にあり、今から数式を入力し、10%対象の税率列に税額を計算させます。
税額は円未満切捨てですが、微小値を加算して、演算誤差を防ぎます。
Sub Macro16()
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
PreserveFormatting:=False
Selection.InsertFormula Formula:="=SUM(E2:E4)", NumberFormat:="#,0"
Selection.Fields.Update
Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend '数字だけブックマークするため、
With ActiveDocument.Bookmarks
.Add Range:=Selection.Range, Name:="Sum10"
.DefaultSorting = wdSortByName
.ShowHidden = True
End With
ActiveDocument.Tables(1).Cell(5, 3).Range.Select 'セルを番地で選択
Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend '番地で選択した状態では式が入らないので、1文字左に移動し、セル全体を選択している状態を解除
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
PreserveFormatting:=False
Selection.InsertFormula Formula:="=INT(Sum10*0.1+0.0000000001)", NumberFormat _
:="#,0" '演算誤差を防ぐため、税率を乗じて微小値を加算して切り捨てる。また本体価格*1.1-本体価格は、つまり本体価格*(1.1-1)
End Sub
これもコツがあります。数式の結果だけをブックマークするには左に1文字移動しています。
つまり結果が何桁でもこれでいいらしいです。またこの結果を保証するため、表示形式を設定します。
Wordの表示形式の設定はRoundを計算しているのと同じだからです。
また今回はセル番地で直に指定して移動してみました。
このActiveDocument.Tables(1).Cell(5, 3).Range.Select
はRangeを省略してActiveDocument.Tables(1).Cell(5, 3).Select
としても良いようです。このセルのR1C1形式はExcelのR1C1形式と同じで、行、列の順に表記します。
数式のフィールドはA1形式だけみたいなので、めちゃくちゃですね。
また、Table(1)という設定なのでTable(1)ですが、違うTableにも飛べます。
ただし、参照は同じテーブルの中でしかできません。例えばSum(Table(1)!C2:Table(1)!C4)
というような表記をTable(2)ですることはできません。
次にジャンプをしたあとですが、セルが選択状態になっています。このときフィールドを挿入しようとするとエラーになるので、それを解除します。
また、ActiveDocumentになっていますが、複数の文書を開いていると誤作動をこすので、Dim wDoc As Word.Document : Set WDoc = ThisDocument
などと冒頭で行ってActiveDocumentではなくWdocを使ったほうが安全です。
おわりに
このようにWordのフィールドコードは手入力でもVBAでも格段に難しいので、今までだれもやらなかったはずです。
とくに連続して数式を複写できないため、使い物にならない。
しかしながら、VBAが使えれば、まだ連続入力の可能性が湧いてきます。
すごく余談ですが、Wordのフィールドコードは英語では単純にFieldです。
もっとも探してもそんなにSampleはありません。
Wordのフィールドコードのお約束
入力が終わり、あとは脚注や目次を整え、フッターやヘッダーを入力して文書として完成したら、フィールドコードやブックマークは削除しましょう。
前々回くらいにサンプルを載せているとおりです。
ブックマークについて
また、WordのブックマークはしおりでもなくIEやEdgeのURLでもありません。
Wordの文書のページや位置ではなく、特定のフィールドを指します。
なぜかと言うとWordではExcelと違い、アドレスが設定できません。また文字を挿入するとページも移動します。
このため位置ではなくあるフィールドを設定し、それに名前をつけているわけです。
また、Officeやブラウザではしおりとして、その位置にジャンプするという機能としてブックマークがありますが、Wordは指定の位置にジャンプでなく、指定した文字列という扱いになります。なので、同じ文字列を繰り返し入力する場合などは、1度入力してブックマークをつけて
結合について
表についてはA1形式で参照しますが、じゃあ結合させたらどうなるかと言うと左からカウントします。
3列目が必ずCになるという感じです。
A列とB列を結合させると、C列だったものはB列になります。
なおセルの番地も同様に(row,2)になります。
この辺も数式を入れづらい理由の一つです。
VBAでも手動でも、数式を入れる前にまず表のデザインを決定してからではないと数式は入力できません。
ネスト(入れ子)について
表中に表を作るとネストされます。もとの表が1とすると、ネストするとレベル2になります。
しかし参照設定はめちゃくちゃややこしくなるので、やりません。
素直にExcelを使いましょう。もしくは電卓で計算しましょう。
しょせんWordなど、電卓と紙と鉛筆以下の低級低能なツールに過ぎません。
なので、時間を書けるのはもったいないです。
セルの値を取得は必ずLeftかRePlaceを使う
Excelのように表の値を取得する場合は
Dim buf as striing
buf = Acticedocument.Table(1).Cell(1,1).Range.Txt
Debug.Print Replace(buf, chr(13) & chr(7) ,1,1)
'もしくは
Debug.Print Left(buf, Len(buf)-2)
これは文字列の末尾の2文字がChr(13)とChr(7)が含まれるためです。
Chr(13)はvbCr キャリッジリターンです
Chr(7)はBELのハズですが、表内の改行記号です。
キャリッジ・リターンしてチーンですかね???
だれがそんなことを決めたんだという気もしますね。
ただ、これが決まっているため、ある文字列をWordで取得して末尾がChr(13) & Chr(7)なら、それはセル内の文字列であると判定できます。
より厳密に言うと行区切りchr(8)
が含まれる(http://www.dastec.org/vov/wd01/vovWD03.htm)ため、それも削除が必要な場合があります。
Dim buf as striing
buf = Acticedocument.Table(1).Cell(1,1).Range.Txt
Debug.Print Replace(buf, chr(13) & chr(7), "",1,1)
'もしくは
Debug.Print Left(buf, Len(buf)-2)
'厳密には
Debug.Print Replace(Left(buf, Len(buf)-2), Chr(8), "", 1, -1,)
というのがWordのセルから文字列を取得するときの書き方になります。改行を活かす場合もあるのでchr(8)を必須にするか、あるいは書き換えるかをするのは微妙です。ケースバイケースでしょう。
参考文献
ワード2010基本講座:表内のデータを計算する
http://www4.synapse.ne.jp/yone/word2010/word2010_keisan.html#bookmark
IFフィールドの使い方
http://office-qa.com/Word/wd498.htm
IFフィールドとIF関数の違い
http://office-qa.com/Word/wd590.htm
Office-QAは2010となっていますが、2019まで変わりません。文字列が扱えないかどうかだけ違うみたいですが。
https://www.relief.jp/docs/word-vba-get-strings-nesting-tables.html
入れ子はReliefもStackOverFlowもお手上げ。。。
https://stackoverflow.com/questions/39327399/accessing-nested-tables-in-ms-word-by-vba
最終段落をみつける https://www.wordvbalab.com/code/5768/
Word文書の表内の全テキストを取得するサンプルマクロ https://www.relief.jp/docs/word-vba-get-strings-in-table-without-double-loop.html
表の中の表のデータを取得するサンプルマクロ https://www.relief.jp/docs/word-vba-get-strings-nesting-tables.html
NestingLevelプロパティ
https://docs.microsoft.com/ja-jp/office/vba/api/word.cell.nestinglevel
Wordにおける表の取り扱い http://www.dastec.org/vov/wd01/vovWD03.htm <<<ここは必見かも。基本的なサンプルが多いです。