空談録

世界で5人くらいに役立ちたい

VSTOでWordに透かし背景画像を追加する

Wordなんてもう知らない!

というわけでいつも通りの動機でWordの透かし背景をVSTOで自動的に追加するコードを考えます
最終的にMacro完全依存です。わかりづらいWordが悪い

とりあえずやっていきましょう

Document.Backgroundは罠

まあ適当にオブジェクトブラウザ見るとDocument.Backgroundプロパティなるものがあるんですよ、それっぽいですね
さて、これのサンプル通りにDocument.Background.Fill.AddPictureとか呼んでみるんですけど何も起こりません

なんでなのかは知りません
とりあえずVisibleをtrueとかではなさそう

あとは検索してもそれっぽいプロパティもメソッドもヒットしないので、どうやら目的のことを行える便利なものはなさそうです

Macroを使おう!

あまりにもExcelが優秀すぎて存在を忘れていたのですがMacro使えば目的のコードは吐かれるのでこれを使いましょう
今回のようにメソッドの見当がつかない場合の最終手段としてはとても便利です
あくまで最終手段としておかないとアホの子のコード修正ですごい時間食われます
あともう一つ個人的に嫌いな理由があるのですがその辺はあとで

とりあえずMacroで得たコードがこちら

Sub Macro1()
'
' Macro1 Macro
'
'
    ActiveDocument.Sections(1).Range.Select
    ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader
    Selection.HeaderFooter.Shapes.AddPicture(FileName:= _
        "F:\User\Pictures\my_photos\fs140611.jpg", LinkToFile:=False, _
        SaveWithDocument:=True).Select
    Selection.ShapeRange.Name = "WordPictureWatermark1811134546"
    Selection.ShapeRange.PictureFormat.Brightness = 0.85
    Selection.ShapeRange.PictureFormat.Contrast = 0.15
    Selection.ShapeRange.LockAspectRatio = True
    Selection.ShapeRange.Height = MillimetersToPoints(84.2)
    Selection.ShapeRange.Width = MillimetersToPoints(149.7)
    Selection.ShapeRange.WrapFormat.AllowOverlap = True
    Selection.ShapeRange.WrapFormat.Side = wdWrapNone
    Selection.ShapeRange.WrapFormat.Type = 3
    Selection.ShapeRange.RelativeHorizontalPosition = _
        wdRelativeVerticalPositionMargin
    Selection.ShapeRange.RelativeVerticalPosition = _
        wdRelativeVerticalPositionMargin
    Selection.ShapeRange.Left = wdShapeCenter
    Selection.ShapeRange.Top = wdShapeCenter
    ActiveWindow.ActivePane.View.SeekView = wdSeekMainDocument
End Sub

割りとそれっぽく見えますね?
ですがVB.NETにしてもC#にしてもこのままじゃまったく使い物にならないコードです

理由は3つあって
VSTOではthis(Me)に該当するのはそのクラスであってThisDocumentとかじゃない(つまりActiveDocumentとかを直接呼べない)
・列挙子(enum)のメンバーのほうを直接呼び出している
・型の概念が存在しない
という感じ
1は簡単に直せます。Globals.ThisAddin.Applicationとか最初につければ完璧
2はちょっと手間ですがプロパティ自体の定義を見ればなんとかなります
問題は3です。ここがアホすぎるので言語の定義の知識まで必要になってきます。そんなもん知らない…

C#に書き直してみよう

面倒なのでさっきの3番目の問題以外は一気に片付けましょう

private static void setBackgroundPicture()
        {
            var fname = "";
            Globals.ThisAddIn.Application.ActiveDocument.Sections.First.Range.Select();
            Globals.ThisAddIn.Application.ActiveWindow.ActivePane.View.SeekView = Word.WdSeekView.wdSeekCurrentPageHeader;
            var sl = Globals.ThisAddIn.Application.Selection;
            sl.HeaderFooter.Shapes.AddPicture(fname,false,true).Select();
            
            sl.ShapeRange.Name = "testBackground_PictureShape";
            sl.ShapeRange.PictureFormat.Brightness = 0.85F;
            sl.ShapeRange.PictureFormat.Contrast = 0.15F;
            sl.ShapeRange.LockAspectRatio = true; // *
            //Selection.ShapeRange.Height = MillimetersToPoints(84.2)
            //Selection.ShapeRange.Width = MillimetersToPoints(149.7)
            sl.ShapeRange.WrapFormat.AllowOverlap = true; // *
            sl.ShapeRange.WrapFormat.Side = Word.WdWrapType.wdWrapNone; // *
            sl.ShapeRange.WrapFormat.Type = 3; // *
            sl.ShapeRange.RelativeHorizontalPosition = Word.WdRelativeVerticalPosition.wdRelativeVerticalPositionMargin; // *
            sl.ShapeRange.RelativeVerticalPosition = Word.WdRelativeVerticalPosition.wdRelativeVerticalPositionMargin;
            sl.ShapeRange.Left = wdShapeCenter; // *
            sl.ShapeRange.Top = wdShapeCenter; // *
            Globals.ThisAddIn.Application.ActiveWindow.ActivePane.View.SeekView = Word.WdSeekView.wdSeekMainDocument;
        }

行の最後に // * がついてるところが残りの問題点です
HeightとWidthは固定されてしまっていますがここは固定されても困るのでコメントアウト
Selectionはいちいち書くのが面倒なので…

残りの変更点は順に見ます
LockAspectRatioはVSTO常連のmsoTriStateです。msoTrueでいけます

続いて厳しさのAllowOverlap
これもともともBooleanのTrueが入ってるんですけどAllowOverlapのプロパティの型は"int"です
はい、intです。true、falseのみを許容しますがintです。決してBooleanなんていう優しい型になっていません
数字で入れろと言われてもVBAの定義なんて知らないので、msoTrueをintにキャストして対応

WrapFormat.Sideは列挙子の種類が違う状態です。なぜ別の列挙子を入れた
WdWrapTypeじゃなくてWdWrapSideTypeなんですけどWdWrapSideTypeにはwdWrapNoneみたいな名前のがないのでしょうがなく定義を確認
wdWrapNoneは3なので同じ3となるWdWrapSideType.wdWrapLargestにします

WrapFormat.Typeのところは列挙子なのに数字が入っている状態
WdWrapTypeなのと3というさっき見た数字なのでWdWrapType.wdWrapNoneにします

RelativeHorizontalPositionは下のコピペしたら別の列挙子になってたけど動いてるしいいよね(テヘペロ)って言われた感じ
まったくよくないですが普通に直せばいけます
長ったらしいので違うの選ばなければなんとか

そして最後のLeftとTop
アホの子のコードの意味が理解できなくて一番苦しむ場所

LeftとTopの型はfloat(Single)です。
当然ですがenumはintですね。なぜfloatにenumが入っているのか

どういう値が許容されているのかも不明ですが同じByte値の小数にしてほしいわけではないらしいです
wdShapeCenterの定義は-999995なのでLeftとTopに-999995.0Fって入れればとりあえず動きます
もはや魔法のコード状態

最終結果はこちら

private static void setBackgroundPicture()
{
    var fname = "";
    Globals.ThisAddIn.Application.ActiveDocument.Sections.First.Range.Select();
    Globals.ThisAddIn.Application.ActiveWindow.ActivePane.View.SeekView = Word.WdSeekView.wdSeekCurrentPageHeader;
    var sl = Globals.ThisAddIn.Application.Selection;
    sl.HeaderFooter.Shapes.AddPicture(fname,false,true).Select();
    
    sl.ShapeRange.Name = "testBackground_PictureShape";
    sl.ShapeRange.PictureFormat.Brightness = 0.85F;
    sl.ShapeRange.PictureFormat.Contrast = 0.15F;
    sl.ShapeRange.LockAspectRatio = Office.MsoTriState.msoTrue;
    sl.ShapeRange.WrapFormat.AllowOverlap = (int)Office.MsoTriState.msoTrue;
    sl.ShapeRange.WrapFormat.Side = Word.WdWrapSideType.wdWrapLargest;
    sl.ShapeRange.WrapFormat.Type = Word.WdWrapType.wdWrapNone;
    sl.ShapeRange.RelativeHorizontalPosition = Word.WdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin
    sl.ShapeRange.RelativeVerticalPosition = Word.WdRelativeVerticalPosition.wdRelativeVerticalPositionMargin;
    float wdsCenter = -999995.0F;
    sl.ShapeRange.Left = wdsCenter;
    sl.ShapeRange.Top = wdsCenter;
    Globals.ThisAddIn.Application.ActiveWindow.ActivePane.View.SeekView = Word.WdSeekView.wdSeekMainDocument;
    Globals.ThisAddIn.Application.ActiveDocument.Saved = true;
}

で、まあこれで動くんですけど何がつらいってなんで動いてるのかまったく理解できないんですよね
つまるところこうしてるからちゃんと動くんだぜ!みたいなのがさっぱりわかりません
ある程度推測はできますがActivePane周りの動作を行う必要性が割と理解できていない感じ

そんな感じなんでなるべくMacroには頼らないコーディングを目指してますが、特にこだわらなければMacroでガンガンコード書いていってもいいかなーと
いやまあキャスト周りが壊滅的なのさえ許容出来ればですけども…

実行結果

上のところまでで終わりにしてたら結果がどうなるのかよくわからないって言われたので
とりあえず下の画像みたいな感じになります

https://portalvhdsrp3qt9v47nzbn.blob.core.windows.net/publicphoto/fspic141109-7.png

プロ生ちゃん画像、全部ありますけど横長画像しか集めてなかったのでつらまり
この画像の状態でBrightness 0.65、Contrast 0.35です。さっきのコードの状態だと白すぎてみえません

ちなみに透かし背景画像の原理やっとわかりました、ヘッダーフッターのノートの位置に画像のShapeを追加することで通常時は触れないようにしているっぽいです
初見で分かるわけがなかった


というだけのお話です
あんまり調べることもなくコードだらだら~って変換しただけ

ただまあ問題はこれだけじゃないんですけどね
ApplicationClassのDocument周りのイベントにAfter系がないので保存終了とかを検知できません
Beforeはあるので事故は避けれそうですが復元できないと自動化って言わないよなぁとも

とりあえず一番の問題が攻略できたのであとはちょこちょこいじるだけですね

この辺で