Wordドキュメントの編集中のページをサクッとPDF化する

WordドキュメントのアクティブなページのみPDF化

以前、

akashi-keirin.hatenablog.com

コチラの記事で、クリック一発で編集中のドキュメントをPDF化するマクロを紹介した。

今回は、題名のとおり、

現在アクティブなページのみPDF化

するマクロを考える。

使い道があるかどうかは不明なれどw

DocumentオブジェクトのExportAsFixedFormatメソッド

「PDF化」といえば、DocumentオブジェクトのExportAsFixedFormatメソッドの出番。

このメソッド、

f:id:akashi_keirin:20170930184535j:plain

実は、ご覧のように異様にたくさんの引数を持っている。その数実に 15。まさに引数祭りw

たいていは必須の「OutPutFileName」と「ExportFormat」の2つしか指定しないので、

Document.ExportAsFixedFormat "hogehoge.pdf",wdExportFormatPDF

みたいに記述すると思う。

実は、このメソッドの引数の中には、

Range

という引数がある。

面白いのは、この引数、データ型が

WdExportRange型 という独自の型なんである。

ちょっと調べてみると、WdExportRangeというのは列挙体で、

名前 説明
wdExportAllDocument 0 文書全体
wdExportCurrentPage 1 現在のページ
wdExportFromTo 2 開始・終了ページを指定
wdExportSelection 3 現在の選択範囲

それぞれ実際の値と意味するところはこうなっている。

つまり、ExportAsFixedFormatメソッドの引数「Range」に「wdExportCurrentPage(正体は整数の"2")」を渡してやれば、現在のページのみPDF化できるということだ。

ちなみに、コーディングの際は、

f:id:akashi_keirin:20170930184607j:plain

こんなふうにインテリセンスが働くので、楽ちん。

ところで、列挙体だけど

WdExportRange.wdExportAllDocument

みたいに書かなくても良いのはなぜなんでしょうね?(←素人丸出しの疑問?)

ファイル名にページ番号を付加する

現在選択中のページのみをPDF化するのだから、できあがったPDFファイルのファイル名にページ番号を付加するぐらいの気は利かせたいところ。

となると、現在選択中のページのページ番号を取得しなければならない。

SelectionオブジェクトのInformationプロパティ

結論から言うと、SelectionオブジェクトのInformationプロパティを参照すれば良い。

このInformationプロパティには引数が何と

41種類

もあった。

そのうち、「wdActiveEndPageNumber」を渡すと、Informationプロパティは、現在カーソルのあるページのページ番号を返してくれる。

Selection.Information(wdActiveEndPageNumber)

こう書く。

ちなみに、「Selection.Information(」まで入力すると、

f:id:akashi_keirin:20170930184550j:plain

このようにインテリセンスが働くので、入力は簡単。

コーディング

以上のことを踏まえてコーディングしてみる。

リスト1 標準モジュール
Public Sub convertActivePageToPDF()
'///アクティブドキュメントをPDF化する。'
  Dim objDoc As Document
  Set objDoc = ActiveDocument
  Dim pathStr As String
  pathStr = objDoc.FullName
  Dim pageNum As Integer
  pageNum = Selection.Information(wdActiveEndPageNumber)    '……(1)'
  'フルパスから拡張子の「.」の位置を取得。'
  Dim n As Integer
  n = InStrRev(pathStr, ".")
  '「.」の位置を元に、PDFファイルのフルパスを作る。'
  'ページ番号3ケタゼロ埋め文字列をファイル名に付加する。'
  pathStr = Left(pathStr, n - 1) & _
            "_P." & Format(pageNum, "00#") & ".pdf"    '……(2)'
  With objDoc
    .ExportAsFixedFormat OutputFileName:=pathStr, _
                         ExportFormat:=wdExportFormatPDF, _
                         Range:=wdExportCurrentPage    '……(3)'
  End With
  Set objDoc = Nothing
End Sub

(1)の

pageNum = Selection.Information(wdActiveEndPageNumber)

では、SelectionオブジェクトのInformationプロパティに引数として「wdActiveEndPageNumber」を渡すことによって、現在カーソルのあるページの番号を取得し、変数pageNumに格納している。

(2)の

pathStr = Left(pathStr, n - 1) & _
          "_P." & Format(pageNum, "00#") & ".pdf"

について。

この行にたどり着いた段階で、変数 pathStr には編集中のドキュメントのフルパスが格納されており、変数 n には拡張子の「.」(ピリオド)の位置(前から何番目にあるか)が格納されている。

まず

Left(pathStr, n - 1) 

で、Left関数を用いて、フルパスのうち、拡張子の「.」(ピリオド)の左側の文字列を取得する。

次に

& "_P."

で文字列「_P.」を連結し、

さらに

Format(pageNum, "00#")

でFormat関数を用いてページ番号を3ケタ0埋めにした文字列を連結し、最後に「.pdf」を連結して、出力PDFファイルのファイルフルパスを作成している。1000ページを超すようなドキュメントを扱うことはまあないので3ケタにしているが、4ケタとか5ケタとか必要だったら、Format関数の第2引数を変えたら良いと思う。

(3)の

objDoc.ExportAsFixedFormat OutputFileName:=pathStr, _
                           ExportFormat:=wdExportFormatPDF, _
                           Range:=wdExportCurrentPage

で、ExportAsFixedFormatメソッドを実行する。引数「Range」に「wdExportCurrentPage」を指定しているので、現在カーソルのあるページだけがPDF化されることになる。

実行

実験用として、

f:id:akashi_keirin:20170930184713j:plain

f:id:akashi_keirin:20170930184619j:plain

f:id:akashi_keirin:20170930184739j:plain

こんなドキュメントを用意して、3ページ目にカーソルを置いて実行。

f:id:akashi_keirin:20170930184756j:plain

実に分かりにくいけど、3ページ目だけがPDF化された。

おわりに

現在のページのみPDF化する、という場面がそんなにあるかどうかは不明だけれど、そういう作業が頻繁に発生するようなら、アドインとして登録しておいたら便利だと思う。

アドイン登録の方法については、

akashi-keirin.hatenablog.com

コチラをどうぞ。

Word初心者なので、なかなかオブジェクト構造が飲み込みづらいけれど、分かってくると結構いじり甲斐がありそうな気もするなあ。

@akashi_keirin on Twitter

ユーザーフォームへのコントロール配置――ControlsコレクションのAddメソッド

ユーザーフォームへのコントロール配置――ControlsコレクションのAddメソッド

ControlsコレクションのAddメソッドによるコントロールの動的配置

f:id:akashi_keirin:20170923211800j:plain

この本の162ページによると、

ユーザーフォームにコントロールを追加するには、Controlsコレクション(集合体)のAddメソッドを使います。第1引数には、コントロールのクラス文字列を指定します。第2引数はコントロール名を指定します。

とのこと。ふむふむ、なるほど。

んで、

クラス文字列

については、同書184ページによると、

コントロール クラス文字列
コマンドボタン Forms.CommandButton.1
テキストボックス Forms.TextBox.1
ラベル Forms.Label.1
コンボボックス Forms.ComboBox.1
リストボックス Forms.ListBox.1
チェックボックス Forms.CheckBox.1
オプションボタン Forms.OptionButton.1
イメージ Forms.Image.1
トグルボタン Forms.ToggleButton.1
スクロールバー Forms.ScrollBar.1
スピンボタン Forms.SpinButton.1
タブストリップ Forms.TabStrip.1
マルチページ Forms.MultiPage.1

とのこと。(ふう。しっかしHTMLで表書くのしんどいな。)

つまり、

Dim ctrl As Control
Set ctrl = Controls.Add("Forms.CommandButton.1","BtnHogeHoge")

としてやれば、「BtnHogeHoge」という名前のCommandButtonコントロールを追加することができる、ということだ。

で、やってみた。

前回

akashi-keirin.hatenablog.com

のフォーム及びコードを流用する。

まずは、前回のリスト1の書き換えから。

リスト1 フォームモジュール
Public Sub init_ver2(ByVal numOfBtns As Integer, _
                     ByVal btnHeight As Double, _
                     ByVal btnWidth As Double, _
                     ByVal offsetBtnsRow As Double, _
                     ByVal offsetBtnsCol As Double, _
                     ByVal numOfCols As Integer, _
                     ByVal btnName As String)
  numOfBtns_ = numOfBtns
  Dim i As Integer
  Dim ctrl As Control    '……(1)'
  For i = 1 To numOfBtns_
    Set ctrl = Controls.Add("Forms.CommandButton.1", _
                            "Btn" & Format(i, "0#"))    '……(2)'
    With ctrl
      .height = btnHeight
      .width = btnWidth
      .Top = offsetBtnsRow + ((offsetBtnsRow + btnHeight) * ((i - 1) \ numOfCols))
      .Left = offsetBtnsCol + ((offsetBtnsCol + btnWidth) * ((i - 1) Mod numOfCols))
      .Caption = btnName & StrConv(i, vbWide) & "号"
    End With
  Next
  With Me
    .height = offsetBtnsRow _
              + ((btnHeight + offsetBtnsRow) * (((numOfBtns_ - 1) \ numOfCols) + 1)) _
              + TOP_BOTTOM_MARGIN
    .width = offsetBtnsCol + ((btnWidth + offsetBtnsCol) * numOfCols) + offsetBtnsCol
  End With
End Sub

変えたのは(1)からの12行(実質11行)

Dim ctrl As Control
For i = 1 To numOfBtns_
  Set ctrl = Controls.Add("Forms.CommandButton.1", _
                          "Btn" & Format(i, "0#"))    '……(2)'
  With ctrl
    .height = btnHeight
    .width = btnWidth
    .Top = offsetBtnsRow + ((offsetBtnsRow + btnHeight) * ((i - 1) \ numOfCols))
    .Left = offsetBtnsCol + ((offsetBtnsCol + btnWidth) * ((i - 1) Mod numOfCols))
    .Caption = btnName & StrConv(i, vbWide) & "号"
  End With
Next

Control型の変数に、ControlsコレクションのAddメソッドを用いて、新しいボタンをセット。With以下のところは前回同様。Forループでボタンの数だけ繰り返している。

(2)の

Set ctrl = Controls.Add("Forms.CommandButton.1", _
                        "Btn" & Format(i, "0#"))

が今回の主役。

Addメソッドの第1引数に、コマンドボタンのクラス変数「Forms.CommandButton.1」を指定。最後の「1」まで必要なので注意。最初、オブジェクトの通し番号だと思って

Controls.Add("Forms.CommandButton." & i,"Btn" & Format(i, "0#"))

と書いて、i = 2 になったところでエラーが出たw

第2引数では、変数 i を用いて、名前が「Btn01」、「Btn02」、……となるようにした。

実行

ついでに実行方法も変更した。

f:id:akashi_keirin:20170923211847j:plain

ワークシート上に、こんなふうにボタンと表を作って、次のコードで実行するようにした。

スト2 標準モジュール
Public Sub controlTest2()
  Dim testFrm As TestForm
  Set testFrm = New TestForm
  Dim num As Integer
  Dim str As String
  Dim height As Double
  Dim width As Double
  Dim frmName As String
  Dim numOfButtons As Integer
  With ActiveSheet    '……(1)'
    num = .Range("C2").Value
    str = .Range("C3").Value
    height = .Range("C4").Value
    width = .Range("C5").Value
    frmName = .Range("C6").Value
    numOfButtons = .Range("C7").Value
  End With
  If num = 0 Or num > 10 Then Exit Sub    '……(2)'
  If height < 10 Or height > 400 Then Exit Sub
  If width < 10 Or width > 400 Then Exit Sub
  If numOfButtons < 1 Or numOfButtons > 100 Then Exit Sub
  With testFrm
    .init_ver2 numOfBtns:=numOfButtons, _
               btnHeight:=height, _
               btnWidth:=width, _
               offsetBtnsRow:=10, _
               offsetBtnsCol:=5, _
               numOfCols:=num, _
               btnName:=str
    .Caption = frmName
    .Show
  End With
End Sub

(1)からの8行

With ActiveSheet
  num = .Range("C2").Value
  str = .Range("C3").Value
  height = .Range("C4").Value
  width = .Range("C5").Value
  frmName = .Range("C6").Value
  numOfButtons = .Range("C7").Value
End With

で、init_ver2メソッドに渡す引数のうち5つと、フォームのCaptionプロパティに渡す文字列をシートから取得する。

変数の名前を見てもらったら、どれが何に当たるかは分かると思う。

(2)からの4行

If num = 0 Or num > 10 Then Exit Sub
If height < 10 Or height > 400 Then Exit Sub
If width < 10 Or width > 400 Then Exit Sub
If numOfButtons < 1 Or numOfButtons > 100 Then Exit Sub

はガード節。不適切な値が設定されていたら処理をやめるようにしている。

ただし、条件設定はかなりテキトー。

実行結果

f:id:akashi_keirin:20170923211813j:plain

なんと、最初に設置した10個のコマンドボタンが設置されたまま、今回、Addメソッドで追加したコマンドボタンが上から配置されている……。

試しに、リスト1の最後に

For Each ctrl In Controls
  Debug.Print ctrl.name
Next

を追加して実行してみると、イミディエイト・ウインドウは、

f:id:akashi_keirin:20170923211909j:plain

こうなる。

どうやら、「Btn01」とか「Btn02」というのは「オブジェクト名」のことではないらしい。ま、当たり前か。

f:id:akashi_keirin:20170923211923j:plain

こんなふうに、もともと置いてあったコマンドボタンを全部削除してから実行すると、

f:id:akashi_keirin:20170923211933j:plain

無事、思い通りの結果が得られた。

おわりに

ついついムキになってユーザーフォーム・コントロール関係にハマってしまったが、実用的なものにするには、動的に配置したコントロールのイベントをどうするか、ということだと思う。ただ、ちょっとまだ今の私の力では手に負えない気もする。

まだまだ勉強が足りないね。

ユーザーフォームへのコントロール配置――このやり方があったじゃないか!

Controlsコレクションの引数

このやり方があったじゃないか!

前回

akashi-keirin.hatenablog.com

コチラの記事で、

オブジェクト名を「Object1」、「Object2」、……とかにしておいて、「Object & i」とかで指定できるか
ということなんだが、当然そんなことはできない。

などと、テキトーなことをぶっこいてしまったが、そんなことはない。

Controls("Object" & i)

という指定の仕方があったじゃないか!

コードの書き換え

というわけで、コードを修正。

前回記事のリスト1を次のように書き換える。

リスト1 フォームモジュール
Public Sub init(ByVal btnHeight As Double, _
                ByVal btnWidth As Double, _
                ByVal offsetBtnsRow As Double, _
                ByVal offsetBtnsCol As Double, _
                ByVal numOfCols As Integer, _
                ByVal btnName As String)
  numOfBtns_ = 10
  Dim i As Integer
  For i = 1 To numOfBtns_
    With Controls("Btn" & Format(i, "0#"))
      .Height = btnHeight
      .Width = btnWidth
      .Top = offsetBtnsRow + ((offsetBtnsRow + btnHeight) * ((i - 1) \ numOfCols))
      .Left = offsetBtnsCol + ((offsetBtnsCol + btnWidth) * ((i - 1) Mod numOfCols))
      .Caption = btnName & StrConv(i, vbWide) & "号"
    End With
  Next
  With Me
    .Height = offsetBtnsRow _
              + ((btnHeight + offsetBtnsRow) * (((numOfBtns_ - 1) \ numOfCols) + 1)) _
              + TOP_BOTTOM_MARGIN
    .Width = offsetBtnsCol + ((btnWidth + offsetBtnsCol) * numOfCols) + offsetBtnsCol
  End With
End Sub

ちょっとスッキリした。

実行

次のコードで実行。

スト2 標準モジュール
Public Sub controlTest()
  Dim testFrm As TestForm
  Set testFrm = New TestForm
  With testFrm
    .init btnHeight:=20, _
          btnWidth:=50, _
          offsetBtnsRow:=10, _
          offsetBtnsCol:=5, _
          numOfCols:=5, _
          btnName:="クズ"
    .Caption = "ち~んw"
    .Show
  End With
End Sub

第6引数だけちょっと変えてみたw

実行結果

f:id:akashi_keirin:20170923162917j:plain

うまくいった。

おわりに

同じ種類のコントロールを規則正しく配置するだけならこのやり方が一番いいかも。

ただ、実用的なものにするには当然イベント処理を追加していく必要があるわけで、そういうところも工夫する必要がありそうだ。

@akashi_keirin on Twitter

ユーザーフォームへのコントロール配置の効率化に挑む

コントロールの配置の効率化

コントロールを規則的に配置する

前回、

akashi-keirin.hatenablog.com

は、コントロールを配列にぶち込んでまとめて処理、という方法を試みた。

配列にぶち込みさえすれば、後はループを回すだけなのだが、そもそも配列にぶち込む段階で、

Dim btns(2) As CommandButton
Set btns(0) = Me.BtnLeft
Set btns(1) = Me.BtnCenter
Set btns(2) = Me.BtnRight

こんな書き方をするなんて、エレガントさのかけらもない。もっと言えば、ダサい。

かと言って、Array関数を使っても、

Dim btns As Variant
btns = Array(Me.BtnLeft, Me.BtnCenter, Me.BtnRight)

になるだけなので、ダサいことには違いない。

オブジェクト名を変数で合成して呼び出せるか

分かりにくい見出しだが、要するに、

オブジェクト名を「Object1」、「Object2」、……とかにしておいて、「Object & i」とかで指定できるか

ということなんだが、当然そんなことはできない。

For Each を使えばいいんじゃね?

オブジェクト名は、Control型オブジェクトのNameプロパティで取得できるから、For Each XX In Controlsで全てのコントロールをループして、オブジェクト名が条件に合うときだけ処理したら良いと考えた。

準備

f:id:akashi_keirin:20170923120516j:plain

ユーザーフォームを挿入して、オブジェクト名を「TestForm」にし、コマンドボタンを10個配置した。画面では整然と並べているけれども、別にグチャグチャに並べておいても良い。

f:id:akashi_keirin:20170923120527j:plain

それぞれのコマンドボタンには、「Btn01」~「Btn10」と通し番号付きのオブジェクト名を付けておく。めんどくさいけど。

あとは、コーディング。

リスト1 フォームモジュール
Option Explicit

Private numOfBtns_ As Integer

Public Sub init(ByVal btnHeight As Double, _
                ByVal btnWidth As Double, _
                ByVal offsetBtnsRow As Double, _
                ByVal offsetBtnsCol As Double, _
                ByVal numOfCols As Integer, _
                ByVal btnName As String)    '……(*)'
  Dim ctrl As Control
  Dim i As Integer
  numOfBtns_ = 0
  For Each ctrl In Controls    '……(1)'
    If Left(ctrl.name, 3) = "Btn" Then    '……(2)'
      numOfBtns_ = numOfBtns_ + 1
      With ctrl    '……(3)'
        .Height = btnHeight
        .Width = btnWidth
        i = numOfBtns_
        .Top = offsetBtnsRow + ((offsetBtnsRow + btnHeight) * ((i - 1) \ numOfCols))
        .Left = offsetBtnsCol + ((offsetBtnsCol + btnWidth) * ((i - 1) Mod numOfCols))
        .Caption = btnName & StrConv(i, vbWide) & "号"
      End With
    End If
  Next
  With Me    '……(4)'
    .Height = offsetBtnsRow _
              + ((btnHeight + offsetBtnsRow) * (((numOfBtns_ - 1) \ numOfCols) + 1)) _
              + TOP_BOTTOM_MARGIN
    .Width = offsetBtnsCol + (btnWidth + offsetBtnsCol) * numOfCols + offsetBtnsCol
  End With
End Sub

まずは(*)のところ。まさに引数祭りwww

ByVal btnHeight As Double, _
ByVal btnWidth As Double, _
ByVal offsetBtnsRow As Double, _
ByVal offsetBtnsCol As Double, _
ByVal numOfCols As Integer, _
ByVal btnName As String

上から順に、

  • btnHeight――ボタンの高さ
  • btnWidth――ボタンの幅
  • offsetBtnsRow――タテ方向のボタンとボタンの間隔
  • offsetBtnsCol――ヨコ方向のボタンとボタンの間隔
  • numOfCols――1行あたりのボタンの数
  • btnName――ボタンの名前

とまあ、これだけの引数を渡して初期化することにしている。

(1)の

For Each ctrl In Controls

からNextまでのブロックで、フォーム内の全コントロールをループ。

(2)の

If Left(ctrl.name, 3) = "Btn" Then

でコントロールのオブジェクト名を調べる。今回配置したコマンドボタンは、全て最初の3字が「Btn」となっているので、この条件がTrueになるということは、Control型変数ctrlにコマンドボタンがぶち込まれているということ。

(3)からの8行

 With ctrl
  .Height = btnHeight
  .Width = btnWidth
  i = numOfBtns_
  .Top = offsetBtnsRow + ((offsetBtnsRow + btnHeight) * ((i - 1) \ numOfCols))
  .Left = offsetBtnsCol + ((offsetBtnsCol + btnWidth) * ((i - 1) Mod numOfCols))
  .Caption = btnName & StrConv(i, vbWide) & "号"
End With

は、ボタンに対する処理。

Top及びLeftプロパティでは、一見ややこしそうな計算をしているようだが、中身は実に単純。図を書いてみたら良いと思う。説明はめんどくさいので省略。

(4)からの6行(実際は4行)

With Me
  .Height = offsetBtnsRow _
            + ((btnHeight + offsetBtnsRow) * (((numOfBtns_ - 1) \ numOfCols) + 1)) _
            + TOP_BOTTOM_MARGIN
  .Width = offsetBtnsCol + (btnWidth + offsetBtnsCol) * numOfCols + offsetBtnsCol
End With

は、フォーム本体の設定。

高さ、幅ともに、
[上下 or 左右の余白]プラス
([ボタンの高さ or 幅]+[ボタン間の隙間の寸法])×[行数 or 列数]
で良いはずなんだが、その通りに式を書くと、

f:id:akashi_keirin:20170923120535j:plain

このような残念な形になってしまう。

よって、定数TOP_BOTTOM_MARGIN(中身は「20」)をプラスすることにしている。

実行

次のコードで実行する。

スト2 標準モジュール
Public Sub controlTest()
  Dim testFrm As TestForm
  Set testFrm = New TestForm
  With testFrm
    .init btnHeight:=20, _
          btnWidth:=50, _
          offsetBtnsRow:=10, _
          offsetBtnsCol:=5, _
          numOfCols:=5, _
          btnName:="アホ"
    .Caption = "ち~んw"
    .Show
  End With
End Sub

コードについては特に説明はいらないと思う。

実行結果

f:id:akashi_keirin:20170923120545j:plain

ボタンが美しく整列している。

おわりに

ただ、このやり方でも、そもそも各コマンドボタンにオブジェクト名をポチポチ書いていかないといけないのはメンドクサイ。配置するコントロールの数を指定して、一気に大量のコントロールを配置する方法もあるんだろうなあ……。

やっぱり、現状では何かの役には立ちそうもないねえ。

@akashi_keirin on Twitter

ユーザーフォームへのコントロール配置を効率的に行う?

ユーザーフォームを効率的に作る

コントロールはNewできるか

やってみた。

なお、ユーザーフォームは、

akashi-keirin.hatenablog.com

このときのものを使う。

f:id:akashi_keirin:20170923093524j:plain

オブジェクト名が「BtnLeft」なので、BtnLeft型の変数が宣言できると思ったが、

f:id:akashi_keirin:20170923093534j:plain

あえなく撃沈w Newする以前の問題www

コントロールをNewして量産し、フォーム上に動的に配置していく、ということはできない模様。

コントロールをまとめて扱う

ならば、複数のコントロールを配列にぶち込んで、まとめて設定できないか、やってみた。

f:id:akashi_keirin:20170923093547j:plain

CommandButton型の変数が宣言できるので、

Dim btns(2) As CommandButton
Set btns(0) = Me.BtnLeft
Set btns(1) = Me.BtnCenter
Set btns(2) = Me.BtnRight

みたいにしたらぶち込めるんじゃないかと思ってやってみたら、

f:id:akashi_keirin:20170923093610j:plain

おお、無事通過! できたみたい。

ということは、まとめて取り扱えるということか。

各コマンドボタンのプロパティをまとめて設定する

無事に3つのコマンドボタンを配列にぶち込むことができたので、Forループを使えば、規則的にプロパティを設定できるはず。

次のようなコードで設定を試みた。

リスト1 フォームモジュール
Public Sub init(ByVal formCaption As String, _
                ByVal btnLeftName As String, _
                ByVal btnCenterName As String, _
                ByVal btnRightName As String)
  With Me
    .Caption = formCaption
    .BtnLeft.Caption = btnLeftName
    .BtnCenter.Caption = btnCenterName
    .BtnRight.Caption = btnRightName
  End With
  Dim btns(2) As CommandButton
  Set btns(0) = Me.BtnLeft
  Set btns(1) = Me.BtnCenter
  Set btns(2) = Me.BtnRight
  Dim i As Integer
  For i = 0 To 2
    With btns(i)    '……(1)'
      .Height = 25    '……(2)'
      .Width = 90
      .Top = 10    '……(3)'
      .Left = 5 + ((90 + 5) * i)
    End With
  Next
  Me.Height = 65    '……(4)'
  Me.Width = 295
End Sub

Private Sub UserForm_Terminate()_    '……(5)'
  MsgBox Me.secretName
End Sub

このユーザーフォームの擬似コンストラクタ。引数を渡したいので、UserForm_Initializeは使わない。

(1)からの6行

With btns(i)
  .Height = 25
  .Width = 90
  .Top = 10
  .Left = 5 + ((90 + 5) * i)
End With

は全てWithでまとめているので、配列btnsにぶち込んだそれぞれのコマンドボタンに対する処理。

(2)からの2行

.Height = 25
.Width = 90

でボタンの大きさを設定し、

(3)からの2行

.Top = 10
.Left = 5 + ((90 + 5) * i)

でフォーム上の位置を設定。

Topプロパティは固定で良いが、Leftプロパティはボタンの横幅とボタン同士の間隔分だけ右へずらしていかないといけないので、こんなふうに設定している。まあ、中学校レベルの数学ですわな。

あとは、(4)からの2行

Me.Height = 65
Me.Width = 295

でフォーム全体の大きさを設定しておしまい。

あと、ユーザーフォーム終了時に、追加したプロパティ「secretName」をメッセージ表示するように、(5)の

Private Sub UserForm_Terminate()
  MsgBox Me.secretName
End Sub

デストラクタを作った。

実行

次のコードで実行。

スト2 標準モジュール
Option Explicit

Public Sub userFormTest()
  Dim uFrm As UserFormTemplate
  Set uFrm = New UserFormTemplate
  With uFrm
    .init "アホ", "ボケ", "クズ", "デコスケ"
    .secretName = "ち~んw"
    .Show
  End With
End Sub

これは、このときとほとんど同じ。追加プロパティ「secretName」をイミディエイトに表示する代わりに、ユーザーフォーム終了時のメッセージ表示にした関係でDebug.Printがなくなっただけ。

実行結果

f:id:akashi_keirin:20170923093626j:plain

おおっ、ボタンが整然と配置されておる!

で、フォームを閉じると、

f:id:akashi_keirin:20170923093635j:plain

むかつくwww

おわりに

未だに便利な使いどころは思いつかないけれど、何らかの有効な使い道はある……はず……だよね?(弱気)

@akashi_keirin on Twitter

大発見!

プロパティ・ウインドウの表示

項目別?

VBEのプロパティ・ウインドウ。私は常時表示しているんですが、あまり気に掛けることはなかった。

ユーザーフォームを使うときに、フォームやコントロールのプロパティを、コードで設定するのがメンドウなときに見ていじくるぐらいだった。

だから、全然気づいていなかったんだが、

表示方法が「全体」と「項目別」の2種類ある

んですよ。知ってました?(吉高由里子風

私は知りませんでした。

当然、ずっと表示形式は「全体」。

んで、「項目別」に切り替えてみたんですよ。そしたらね……、

f:id:akashi_keirin:20170923073525j:plain

f:id:akashi_keirin:20170923073648j:plain

メチャクチャ分かりやすい!!!!!!!!
便利!!!!!!!!!

今まで、

なんでプロパティ名のアルファベット順なんだよ!?
探しにくくてしょうがねえじゃんかよ!!


だなんて思っていましたけど、何だよ、いいとこあるじゃんかよお!

おわりに

「それだけかよ?」って感じですか?

はい。それだけです!!!!!!!!

@akashi_keirin on Twitter

編集中のWordドキュメントをサクッとPDF化するアドイン

素早く手軽にPDF化

WordドキュメントのPDF化

f:id:akashi_keirin:20170922220226p:plain

大量のWordドキュメントを自動でPDF化する、というのは、かなり前に対応済みだった。

akashi-keirin.hatenablog.com

Wordドキュメント1つとか2つだけをPDF化する、などというシチュエーションはほとんどなかったので、そういうときは[印刷]メニューでプリンタをJUST PDFとかCubePDFとかに設定してPDFを吐かせていた。

ただコレ、意外とヒマかかるんですよ。

編集中のWordドキュメントをサクッとPDF化したい、というときには不便だった。たった1つや2つのためにわざわざExcelで作ったPDF化ツールを立ち上げるのもばかげているし。

そこで、アドインですよ!

そこでひらめいたのが、最近ハマり気味の「アドイン」。

で、やってみた……んですけどね、

なんでExcelとアドインの登録方法が違うねん!

あれこれ調べまくって、やっとある程度方法が分かったので、紹介する。もっといいやり方があったら教えてください。

Wordへのアドインの登録

f:id:akashi_keirin:20170922220243j:plain

まず、マクロを書いたファイルを、「Word マクロ有効テンプレート(*.dotm)」の形式で保存する。

f:id:akashi_keirin:20170922220303j:plain

保存先は、

C:\Users\[ユーザー名]\AppData\Roaming\Microsoft\Word\STARTUP

フォルダ。

f:id:akashi_keirin:20170922220336j:plain

テキトーにWordドキュメントを開いて、[ファイル]タブをクリック。

f:id:akashi_keirin:20170922220345j:plain

[オプション]をクリック。

f:id:akashi_keirin:20170922220403j:plain

[アドイン]をクリック。

f:id:akashi_keirin:20170922220415j:plain

「管理」のところのドロップダウンリストで「Wordアドイン」を選択。

f:id:akashi_keirin:20170922220428j:plain

[設定]をクリック。

f:id:akashi_keirin:20170922220437j:plain

アドイン化したいマクロ名を選んで、

f:id:akashi_keirin:20170922220447j:plain

[OK]をクリック。

ここまでで登録はおk。

次に、クイック アクセス ツール バーに登録する。ここからはExcelのときと全く同じだけれど、一応書いとく。

f:id:akashi_keirin:20170922220500j:plain

[ファイル]タブ→「クイック アクセス ツール バー」をクリック。

f:id:akashi_keirin:20170922220510j:plain

「コマンドの選択」ドロップダウンリストから「マクロ」を選び、

f:id:akashi_keirin:20170922220524j:plain

アドイン化したいマクロ名を選択して、

f:id:akashi_keirin:20170922220547j:plain

[追加]をクリック。

f:id:akashi_keirin:20170922220601j:plain

アイコンが気に食わなければ、テキトーに変更して、

f:id:akashi_keirin:20170922220611j:plain

[OK]をクリック。

f:id:akashi_keirin:20170922220624j:plain

クイック アクセス ツール バーに登録された。

f:id:akashi_keirin:20170922220634j:plain

フォルダ内に「test.docx」1つだけがある状態で、こいつを開いてクイック アクセス ツール バーのアイコンをクリックすると、

f:id:akashi_keirin:20170922220644j:plain

次の瞬間に、PDFファイルが生まれている。

結構一瞬でPDF化できる。

もちろん、特定のページだけをPDF化、みたいな手の込んだことはできない。そんなときは素直にフツーのやり方をすればよろしい。

今回使用したマクロのコード

リスト1 標準モジュール
Option Explicit

Public Sub convertToPDF()
'///アクティブドキュメントをPDF化する。'
  Dim objDoc As Document
  Set objDoc = ActiveDocument
  Dim pathStr As String
  pathStr = objDoc.FullName
  'フルパスから拡張子の「.」の位置を取得。'
  Dim n As Integer
  n = InStrRev(pathStr, ".")
  '「.」の位置を元に、PDFファイルのフルパスを作る。'
  pathStr = Left(pathStr, n) & "pdf"
  With objDoc
    .ExportAsFixedFormat OutputFileName:=pathStr, _
                         ExportFormat:=wdExportFormatPDF
  End With
  Set objDoc = Nothing
End Sub

コードの説明は省略。お望みでしたらコメントください。

おわりに

こういうちょっとした処理をアドイン化しといたら便利だなあ。

@akashi_keirin on Twitter