PowerPointアドインを作る手順の概要

# PowerPointアドインを作る手順の概要

## 0. はじめに

本記事はPowerPointアドインの作成記録である。現状、PowerPointアドインの作成方法の流れを丁寧に解説した記事は残念ながらほとんどない。筆者は苦心の末にアドインの作成に成功したので、後の人のために、丁寧に解説という目標は果たせていないかもしれないが、とりあえず一連の流れを残すことにした（完璧を目指すよりまず終わらせろの精神で）。単にアドインを作成するのでなく、それを定期的に更新することを見据えている。

アドインにはC#を用いるものとVBAを用いるものとがあるが、後者の方が作成が簡単であるようなので（ただし柔軟性には劣るのかもしれない）、VBAを利用する。

なお、巷ではアドインを作成するにあたって「Custom UI Editor」「Office RibbonX Editor」などのGUI補助ツールが紹介されているが、ここではPowerPointアプリ（VBAエディタ含む）と汎用テキストエディタのみを用いて作成できるようになることを目指す。

本記事はVBAについては既にある程度知識のあることを前提とする。つまり、当然ながら、簡単なマクロを作成できる程度の技能がなければ自作アドインを作ることはできないが、VBAの解説は既に無数に存在するのでここでは説明しない。PowerPointマクロ（Excelでもいいが）に馴染みのある人ならば問題ないだろう。

## 1. アドインファイルの構造を知る

### 1.1. `.ppam`ファイルを作る

PowerPointアドインファイルは`.ppam`という拡張子を持つ。とりあえずこのファイルがどういうものなのかを見てみよう。

とりあえず空白の`.ppam`ファイルを作ることにする。具体的に説明すると、

1. PowerPointファイルを新規作成
2. メニューの「開発 > コード > Visual Basic」（または`Alt + F11`）でエディタ立ち上げ
3. プロジェクトウィンドウで右クリックして「挿入 > 標準モジュール (M)」
4. スクリプトは空白のままエディタを終了
5. パワポファイルを名前をつけて保存、その際にファイルの種類を`PowerPoint アドイン (*.ppam)`にし、任意の場所に保存

※手順5でファイルの種類を`PowerPoint アドイン (*.ppam)`にすると、保存場所のデフォルトがアドインを最終的に配置するアドレスになる。今は編集段階なのでここには保存しないほうが良いだろう。

保存したファイルの拡張子を`.ppam`から`.zip`に変更して、解凍すると、おそらく次のようになっている。

```text:アドインファイルの構造
ppamファイル
│  [Content_Types].xml
│
├─ppt/
│  │  presentation.xml
│  │  vbaProject.bin
│  │
│  └─_rels/
│          presentation.xml.rels
│
└─_rels/
        .rels
```

`./ppt/vbaProject.bin`がスクリプトの本体で、上記の手順の4でスクリプトになにか適当なマクロを書いてから（コンパイルエラーに注意）保存すると、このファイルのサイズが変わっていることがわかるだろう。

逆に、スクリプトになにを書いても`vbaProject.bin`以外のファイルは何も変わらないはずである。

### 1.2. `.ppam`ファイルの構造

上で見た`.ppam`ファイルにはリボンの定義が存在しない。最終的に我々が作るものは、リボンに関するファイルが詰め込まれた`customUI`フォルダが追加されることになる。

結論を先取りすると、我々が作る`.ppam`ファイルは最終的に以下のような構造になる。

```text:アドインファイルの構造
ppamファイル
│  [Content_Types].xml
│
├─customUI/
│  │  customUI14.xml
│  │
│  ├─images/
│  │      ~~~.png
│  │
│  └─_rels/
│          customUI14.xml.rels
│
├─ppt/
│  │  presentation.xml
│  │  vbaProject.bin
│  │
│  └─_rels/
│          presentation.xml.rels
│
└─_rels/
        .rels
```

全てのファイルについて簡単に説明する。

- `./[Content_Types].xml`: 全体の定義（ほぼ編集不要）
- `./customUI/customUI14.xml`: リボンの定義
- `./customUI/images/~~~.png`: アイコンの画像ファイル（任意）
- `./customUI/_rels/customUI14.xml.rels`: アイコンの定義
- `./ppt/presentation.xml`: 知らん（編集不要）
- `./ppt/vbaProject.bin`: VBスクリプトがコンパイルされたもの
- `./ppt/_rels/presentation.xml.rels`: 知らん（編集不要）
- `./_rels/presentation.xml.rels`: 知らん（編集不要）

前節で見たように、`./ppt/vbaProject.bin`（と今説明した`./customUI/`フォルダ）以外のファイルは共通であるため、基本的に触る必要がない。ただし`./[Content_Types].xml`については「俺はリボン定義するぞ」という行を追加する必要がある。詳細は追って説明する。

## 2. どのように`.ppam`ファイルを作るか

§1.1で見たように、PowerPointでVBAエディタを立ち上げてそこにスクリプトを書き、`.ppam`として保存すれば完成とはいかない。なぜなら、PowerPointアプリ（VBAエディタ含む）の中ではリボンUIに関する設定ができず、さらに、一度作成した`.ppam`ファイルを直接編集することすらできないからである。

そこで、まず§1.1の方法で`./customUI/`フォルダ以外の部分を作成し、`./customUI/`フォルダの中身のファイルについては別途テキストエディタで（アイコンは適宜画像編集ソフトで）作成することとする。

このようにして各ファイルを個別に作成した後、§1.2の通りの構造でファイルを配置する。それを`.zip`で圧縮し、最後に拡張子を`.ppam`にを変更すれば完成である。

以下、作成手順を簡単に手順を説明する（ここでは作業手順のみを紹介し、各ファイルに何を記述すれば良いかについては後述する）。まずは§1.1の要領で`.ppam`ファイルを作成して解凍、作業フォルダに配置する。

```text:作業フォルダ構造（例）
任意の作業フォルダ
│
└─my_addin/
   │  [Content_Types].xml
   │
   ├─ppt/
   │      （略）
   │
   └─_rels/
           .rels
```

### 2.1. `[Content_Types].xml`の修正

手始めに`[Content_Types].xml`を修正する必要がある。

`[Content_Types].xml`を開くと次のようになっているはずである。

```xml:[Content_Types].xml （編集前）
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="bin" ContentType="application/vnd.ms-office.vbaProject"/><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/ppt/presentation.xml" ContentType="application/vnd.ms-powerpoint.addin.macroEnabled.main+xml"/></Types>
```

中身を以下に入れ替える。改行を追加したためめちゃくちゃ変わっているように見えるかもしれないが、実際には4行目（`Extension="png"`の行）を追加しただけである。

```xml:[Content_Types].xml （編集後）
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
    <Default Extension="bin" ContentType="application/vnd.ms-office.vbaProject"/>
    <Default Extension="png" ContentType="image/.png"/>
    <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
    <Default Extension="xml" ContentType="application/xml"/>
    <Override PartName="/ppt/presentation.xml" ContentType="application/vnd.ms-powerpoint.addin.macroEnabled.main+xml"/>
</Types>
```

この作業は、次節以降で紹介するものと違って、一度行えば以降`[Content_Types].xml`の編集は不要である（アドインの中身を更新するたびに手を加えるということはないという意味）。自作アイコンを全く使わない場合には、この`[Content_Types].xml`の編集作業は不要かもしれない。

### 2.2. スクリプト本体（`vbaProject.bin`）

#### 2.2.1. `vbaProject.bin`の編集方針

`vbaProject.bin`はスクリプト本体が格納されたアドインの「核」なので、この編集作業はおそらく最も頻繁に行う作業である。

しかし§2で軽く触れたが、PowerPointアプリ（VBAエディタ含む）で`.ppam`ファイルのスクリプトを直接編集することはできない。この問題の解決策は、`.pptm`ファイルを使うことである。`.pptm`ファイルは通常のパワポ（`.pptx`）にマクロ機能を備えたもので、エクセルで言えば通常`.xlsx`に対するマクロ付き`.xlsm`に相当する。

`.pptm`ファイルを`.zip`ファイルとして開いて中身を見ると、その構造は`.ppam`にスライド情報が追加されただけでほとんど同じであることがわかる。`.pptm`ファイルはPowerPointアプリ（VBAエディタ）で直接編集できるので、スクリプトの保存・編集が簡単にできる。`.pptm`ファイルのスクリプトを編集したら、`vbaProject.bin`を抽出して作業フォルダに貼り付けるのである。

#### 2.2.2. `vbaProject.bin`を編集するための`.pptm`ファイルの配置

では、§1.1の方法で作業フォルダ直下に今度は`.pptm`ファイルを保存しよう。名前はもちろんなんでも良いが、説明のためにここでは`vbs.pptm`として保存した。VBAエディタ以外でスクリプトを編集したい人や、特にスクリプトをgit等で管理したい人は（二度手間になるが）`.bas`ファイルを別途保存してもよいだろう。

```diff:作業フォルダ構造（例）
  任意の作業フォルダ
+ │  vbs.pptm
+ │  vbs.bas （任意）
  │
  └─my_addin/
     │  [Content_Types].xml
     │
     ├─ppt/
     │      （略）
     │
     └─_rels/
             .rels
```

この`.pptm`内のスクリプトに記述する内容の詳細については後述するが、例えば空白の状態から次のように追記したとしよう。

```vb:vbs.bas
Private myRibbon As IRibbonUI

' onLoad関数
Public Sub myRibbonOnLoad(ribbon As IRibbonUI)
    Set myRibbon = ribbon
    myRibbon.Invalidate
End Sub

' 右移動（※選択したオブジェクトを右に100pt移動させるだけの関数）
Public Sub MoveRight()
    Dim SelectedShapes As ShapeRange
    
    ' オブジェクト以外が選択されている場合は無視する
    If ActiveWindow.Selection.Type = ppSelectionNone _
    Or ActiveWindow.Selection.Type = ppSelectionSlides Then
        Exit Sub
    End If
    
    ' 移動
    SelectedShapes.IncrementLeft 100
End Sub
```

`.pptm`の編集が終わったら上書き保存してパワポアプリを終了し、その`.pptm`ファイルを`.zip`ファイルとして開き、`./ppt/vbaProject.bin`をコピーして作業フォルダの同じ位置（この記事の例では`任意の作業フォルダ/my_addin/ppt/vbaProject.bin`）に貼り付ける。この作業はスクリプトを編集するたびに行う必要がある（以降、この作業手順を行う必要があることは省略する）。

### 2.3. `CustomUI`フォルダの作成

§1.2で触れたように、このままだとリボンUI要素が足りていない。アドイン本体となるフォルダ（本記事の例では`任意の作業フォルダ/my_addin/`フォルダ）の下に`CustomUI`フォルダを追加し、その下に`_rels`フォルダと`images`フォルダを追加する。

```diff:作業フォルダ構造（例）
  任意の作業フォルダ
  │  vbs.pptm
  │  vbs.bas （任意）
  │
  └─my_addin/
     │  [Content_Types].xml
     │
+    ├─CustomUI/
+    │  │
+    │  ├─images/
+    │  │
+    │  └─_rels/
+    │
     ├─ppt/
     │      （略）
     │
     └─_rels/
             .rels
```

### 2.4. ツールアイコン

リボンに表示するアイコンを`.png`形式で作成し、`images`フォルダに投入する。アイコンに合わせて`CustomUI/_rels/customUI14.xml.rels`を編集する。

```diff:作業フォルダ構造（例）
  任意の作業フォルダ
  │  vbs.pptm
  │  vbs.bas （任意）
  │
  └─my_addin/
     │  [Content_Types].xml
     │
     ├─CustomUI/
     │  │
     │  ├─images/
+    │  │      icon1.png
+    │  │      icon2.png
     │  │
     │  └─_rels/
+    │          customUI14.xml.rels
     │
     ├─ppt/
     │      （略）
     │
     └─_rels/
             .rels
```

### 2.5. リボンUIのデザイン

リボンのデザインを定義した`customUI14.xml`を`CustomUI`フォルダの下に作成する。

```diff:作業フォルダ構造（例）
  任意の作業フォルダ
  │  vbs.pptm
  │  vbs.bas （任意）
  │
  └─my_addin/
     │  [Content_Types].xml
     │
     ├─CustomUI/
+    │  │  customUI14.xml
     │  │
     │  ├─images/
     │  │      icon1.png
     │  │      icon2.png
     │  │
     │  └─_rels/
     │          customUI14.xml.rels
     │
     ├─ppt/
     │      （略）
     │
     └─_rels/
             .rels
```

中身についての詳細は後述するが、とりあえず最小限の内容として次のように書いておく。

```xml:customUI14.xml
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="">
    <ribbon startFromScratch="false">
        <tabs>
        </tabs>
    </ribbon>
</customUI>
```

### 2.6. 作成完了

（この説明でいう）`my_addin`フォルダを圧縮して`.zip`ファイルにし、拡張子を`.ppam`に変更すれば完成である。

実際に使用する際の手順は以下の通り。

1. `%APPDATA%/Microsoft/AddIns`（つまり`C:/Users/ユーザー名/AppData/Roaming/Microsoft/AddIns`）に`.ppam`ファイルをコピー
2. PowerPointアプリを開き、「ファイル > オプション（一番左下） > “アドイン”タブ > 管理：Powerpointアドイン > 設定(G)」でアドイン設定画面を開く
3. 「使用可能なアドイン」に作成したアドインがない場合、「新規追加」を押して1.で追加したファイルを選択
4. 追加したアドインにチェックを入れて「閉じる(C)」

## 3. onLoad関数の設定

§2で各ファイル・フォルダを正しい位置に配置できたので、あとは各ファイルを必要に応じて編集するだけである。

さて、パワポアプリを立ち上げたときにリボンを読み込んで表示するための関数を定義する必要がある。これを自分はonLoad関数と呼んでいる。

onLoad関数でやることは、引数名`ribbon`で`IRibbonUI`型オブジェクトを受け取って、その`.Invalidate`メソッドを実行するだけである。以下のコードをスクリプトにコピペしよう（`myRibbon`の部分は任意の名前でよい）。

```vb:vbs.bas
Private myRibbon As IRibbonUI

Public Sub myRibbonOnLoad(ribbon As IRibbonUI)
    Set myRibbon = ribbon
    myRibbon.Invalidate
End Sub
```

onLoad関数をスクリプトに書いたら、`customUI14.xml`の1行目の`onLoad=""`の部分にその関数の名前を書く。

```xml:customUI14.xml
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="myRibbonOnLoad">
    <ribbon startFromScratch="false">
        <tabs>
        </tabs>
    </ribbon>
</customUI>
```

## 4. リボンの設定

### 4.1. リボンの基礎

リボンは階層構造になっている。下図は最初からあるホームタブのキャプチャである。その「タブ」の中に、下図青枠で囲った「グループ」があり、さらにグループの中にオレンジ枠で囲った「ボタン」がある。

![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394333/65c30c7f-4b12-9250-234c-9a1571867a87.png)

ボタンには大中小3種類のサイズがある。上図の「貼り付け」「新しいスライド」は大サイズで、32x32サイズのアイコンの下にラベルが表示される。「切り取り」「コピー」などは中サイズで16x16サイズのアイコンの横にラベルが表示される。上図で見切れている文字の太字斜体下線ボタン（16x16サイズのアイコンのみでラベル無し）が小サイズである。

中サイズと小サイズは縦に3つ並べることができるが、大サイズはそれだけで3段分消費するので縦に並べることはできない。横にはどのサイズのアイコンも無限に並べることができる。

リボンUIは、これらの情報を含めて`customUI14.xml`で設定する。下記のように、タブの中にグループがあり、グループの中にボタンがある。タブ、グループ、ボタンは固有のID（`id`）をもたせる必要がある。それ以外の属性については以降で説明する。

```xml:customUI14.xml
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="positionsRibbonOnLoad">
    <ribbon startFromScratch="false">
        <tabs>
            <tab id="mytab1">
                <group id="mygroup1">
                    <button id="mybutton1" />
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>
```

### 4.2. タブの設定

タブは下記のように`<tab></tab>`を適切な位置（`<tabs>`～`</tabs>`の間）に配置することで設定する。ところで個人的には、1つのアドインに複数のタブをもたせることは、無意味に複雑化させていると感じるため推奨しない。2つのタブには2つのアドインを別々に作ったほうが良いだろう。

`<tab>`の中に以下の属性を記入する。

- `id`= 固有のID
- `label`= 表示されるラベル
- `keytip`= アクセスキー。Powerpointアプリにおいて、`Alt`キーを押したあとにここで設定したキーを押すとこのタブにジャンプできる。設定されているキーは`Alt`長押しで確認できる。単一のキーでは、`A`, `B`, `C`, `F`, `G`, `H`, `K`, `L`, `N`, `Q`, `R`, `S`, `W`と数字はデフォルトで使われているようだ。

```xml:customUI14.xml
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="positionsRibbonOnLoad">
    <ribbon startFromScratch="false">
        <tabs>
            <tab id="mytab1" label="タブ1" keytip="T">
                ……
            </tab>
        </tabs>
    </ribbon>
</customUI>
```

### 4.3. グループの設定

グループは下記のように`<group></group>`を適切な位置（`<tab>`～`</tab>`の間）に配置することで設定する。

`<group>`の中に以下の属性を記入する。

- `id`= 固有のID
- `label`= 表示されるラベル。§4.1の図でいう「クリップボード」や「スライド」がそれである。
- `image`= アイコン画像。§4.1の図を見ればわかるようにデフォルトでは表示されないが、クイックアクセスツールバーにグループを登録すると表示されるので設定した方が良い。

```xml:customUI14.xml
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="positionsRibbonOnLoad">
    <ribbon startFromScratch="false">
        <tabs>
            <tab id="mytab1" label="タブ1" keytip="T">
                <group id="mygroup1" label="グループ1" keytip="A">
                    ……
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>
```

### 4.4. ボタンの設定

ボタンは下記のように`<button />`を適切な位置（`<group>`～`</group>`の間）に配置することで設定する。

`<button>`の中に以下の属性を記入する。

- `id`= 固有のID
- `onAction`= ボタンを押したときに動かす関数
- `label`= 表示されるラベル。§4.1の図でいう「切り取り」や「貼り付け」がそれである。
- `image`= アイコン画像。大サイズでは32x32、中小サイズでは16x16で表示される。
- `keytip`= アクセスキー、すなわちタブのアクセスキーの次に求められる入力。（任意）
- `size`= 表示サイズ（§4.1参照）、`large`, 

```xml:customUI14.xml
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="positionsRibbonOnLoad">
    <ribbon startFromScratch="false">
        <tabs>
            <tab id="mytab1" label="タブ1" keytip="T">
                <group id="mygroup1" label="グループ1" keytip="A">
                    <button id="mybutton1" onAction="myfunc1" label="ボタン1" image="SwapIcon" size="large"/>
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>
```

このボタンが押されると、`onAction`で指定したVBスクリプトの関数が動くことになる。

### 4.6. ボタン以外（例えばチェックボックス）の設定

リボンにはボタン以外のオブジェクトを配置することができる。ここでは例としてチェックボックスを紹介しよう。

チェックボックスの実装は、リボンへ配置するのは簡単だが、VBスクリプトへにおける記述がやや難解である。そこには、「onAction関数」（すなわちチェックボックスを押下したときに発動する関数）に加えて、「チェックボックスの状態を格納する変数」「getPressed関数」を準備する必要がある。

1. チェックボックスは常に「`True`:チェックが入っている状態」「`False`:チェックが入っている状態」のどちらかの状態でなければならない。この状態を格納する変数（以下の例では`checkBoxState`）を`Boolean`型で定義する。
2. 常にどちらかの状態でなければならないとは、リボンが読み込まれるonLoad関数の`myRibbon.Invalidate`よりも前にデフォルトの状態（例では`False`）を設定しなければならないということである。
3. getPressed関数（例では`checkBoxFunc_getPressed`）は、状態を格納した変数をそのまま返すだけの関数。これは変数の状態を実際にリボンに反映させる役割を持つ。
4. onAction関数は、getPressed関数とは逆に、ボタンが押されたとき（すなわちチェックボックスの状態が変わったとき）にリボンの状態を取得して変数に格納させる役割を持つ。`pressed`引数を受け取って変数に代入する。

```vb:vbs.bas
Private myRibbon As IRibbonUI
Private checkBoxState As Boolean  '←チェックボックスの状態変数

' onLoad関数
Public Sub myRibbonOnLoad(ribbon As IRibbonUI)
    Set myRibbon = ribbon
    checkBoxState = False  '←チェックボックスのデフォルト値の設定
    myRibbon.Invalidate
End Sub

' getPressed関数
Public Sub checkBoxFunc_getPressed(control As IRibbonControl, ByRef RetVal)
    RetVal = checkBoxState
End Sub

' onAction関数
Public Sub checkBoxFunc_onAction(control As IRibbonControl, pressed As Boolean)
    checkBoxState = pressed
End Sub
```

状態を格納した変数（上の例の`checkBoxState`）を他の関数のIF条件にすれば、チェックボックスの状態に合わせて異なる挙動を示す関数を作ることができる。

リボンUIの設定は、ボタンと同様に`<checkBox />`を適切な位置（`<group>`～`</group>`の間）に配置する。

`<checkBox>`の中に以下の属性を記入する。

- `id`= 固有のID
- `getPressed`= チェックボックスの状態を保持する変数
- `onAction`= ボタンを押したときに動かす関数
- `label`= 表示されるラベル

```xml:customUI14.xml
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="positionsRibbonOnLoad">
    <ribbon startFromScratch="false">
        <tabs>
            <tab id="mytab1" label="タブ1" keytip="T">
                <group id="mygroup1" label="グループ1" keytip="A">
                    <checkBox id="mycheckbox1" getPressed="checkBoxFunc_getPressed" onAction="checkBoxFunc_onAction" label="チェックボックス" />
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>
```