Excel のリボンUIを業務アプリとして使う

# 業務アプリ用のリボン UI

![2014-08-30_133544.png](https://qiita-image-store.s3.amazonaws.com/0/52104/c17b3209-8aef-c6b6-e0a6-86a258ba7595.png)

こんな感じでリボン UI の部分を検索フォームにしてみました。
シートは４枚あって、切り替えるとリボンも対応して切り替えたり出来ます。
コンテキストメニューやクイックアクセスツールバーの内容も簡単に制御できるので簡易業務アプリのプラットフォームにも使えたりします。
このブックをそのまま紹介すると記事が長くなりすぎるので、端折って超基本的な部分だけを紹介しますね。

![2014-08-30_134909.png](https://qiita-image-store.s3.amazonaws.com/0/52104/6540b9c3-c692-c5f2-f753-5eac0821e150.png)

上書き保存や名前を付けて保存などを抑止したりするのもコーディングせずに出来ます。

# リボン UI は XML で定義します

拡張子 .xlsx や .xlsm などの最近のファイル形式の実体は ZIP 書庫なので拡張子を変えて解凍することが出来ます。

![2014-08-30_153128.png](https://qiita-image-store.s3.amazonaws.com/0/52104/f933893f-27e8-7025-776a-787a8d0fcdc0.png)

ルートに置いてある [Content_Types].xml はワークブックやワークシートの ContentType が定義されているファイルで今回は触りません。
ルートに customUI というフォルダを作って、その中に customUI.xml と customUI14.xml の２つを作成します（フォルダ名とファイル名は任意です）。

![2014-08-30_153927.png](https://qiita-image-store.s3.amazonaws.com/0/52104/b7a5c4ea-beaf-fedb-a2b8-0fe29da6fcc4.png)

作成した XML ファイルにリボン UI に配置するコントロールや、既存の UI の有効/無効や表示状態などを定義していきます。
まずは XML ファイルの宣言部です。

```xml:customUI.xml
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" xmlns:mso="http://schemas.microsoft.com/office/2006/01/customui" onLoad="CustomUI.OnLoad">
  <ribbon>
    <tabs>
    </tabs>
  </ribbon>
</customUI>
```

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

2つのファイルの違いはこの宣言部だけです。
customUI.xml は Excel2007 用、customUI14.xml は Excel2010 以降の定義です。
2010以降に追加された機能を使う場合はファイルを２つに分ける必要がありますが、内容が全く同じであれば１つのファイルでも問題はありません。

### カスタム UI 定義ファイルを Excel に認識させる

作成した空っぽのカスタム定義を Excel が認識出来るようにするために、_rels フォルダの中にある .rels ファイルを開いて２つの Relationship ノードを追加します。

```xml:.rels
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship id="R0aac9aa500f94de3" type="http://schemas.microsoft.com/office/2006/relationships/ui/extensibility" target="customUI/customUI.xml"/>
  <Relationship id="R4c9562ad923141f4" type="http://schemas.microsoft.com/office/2007/relationships/ui/extensibility" target="customUI/customUI14.xml"/>
</Relationships>
```

id 属性は一意になるように好きな名前を付けます。
type 属性は 2007 用と 2010 以降で決め打ちなので、このままコピーしてください。
target 属性はルートからの相対パスで、先ほど作成した XML ファイルを指定します。

### カスタム UI を定義する

#### タブを作成する

tabs ノードの中に追加します。
コードが長くなるので tabs ノードから上は省略します。

```xml
<tab id="SampleTab" label="タブ名" getVisible="CustomUI.SampleTab_getVisible">
</tab>
```

label 属性はタブの表示名です。ホームとか挿入とかね。
getVisible 属性は表示状態を制御するコールバックメソッドを指定します。
モジュール名.メソッド名の形式で定義し、メソッドは標準モジュールに定義した静的メソッドを割り当てます。コールバックメソッドの戻り値で True／False の Boolean を返すことで表示／非表示を制御します。

#### グループを作成する

以下の画像の赤枠で囲んだ部分が１つのグループです。
ただのセパレータと区別が付きにくいですが、下にグループ名が付いているかどうかで分かります。
![2014-08-30_161337.png](https://qiita-image-store.s3.amazonaws.com/0/52104/8e8db95e-9877-68ef-9e47-12adef4f63c9.png)

```xml:customUI14.xml
<tab ...>
  <group id="SampleGroup" label="グループ名" centerVertically="true">
  </group>
</tab>
```

label 属性はリボンの一番下に表示される文字です。
画像の例ではクリップボード、フォント、配置がそれです。
centerVertically 属性は垂直方向の中央そろえです。

グループの中にコントロールを複数配置すると基本的に縦一列に並んで、入りきらなくなると二列目にあふれます。
中には横に２つ並べて配置したい場合もあると思いますが、そのような場合にはボックスを使用します。

#### ボックスを作成する

```xml
<tab ...>
  <group ...>
    <box id="SampleBox" boxStyle="horizontal">
    </box>
  </group>
</tab>
```

boxStyle 属性は "horizontal" または "vertical" です。
私は今のところ "horizontal" しか使っていません。

#### テキストボックスを定義する

```xml
<tab ...>
  <group ...>
    <editBox id="SampleText" label="サンプルテキスト" sizeString="wwwwwwwwwww" getText="SampleTab.SampleText_getText" onChange="SampleTab.SampleText_onChange"/>
  </group>
</tab>
```

label 属性はテキストボックスの左側に表示されるラベルです。
sizeString 属性はテキストボックスの大きさを決めるための文字列です。
ピクセル数とかで決めるのが普通ですけど、リボン UI では指定の文字がぴったり入る大きさという指定の仕方しか出来ません。
とりあえず "w" で草を生やしてみましたが、日本語はやめた方がいいような気がします。（根拠はない）

getText 属性はテキストボックスに文字を表示する必要があるときに Excel が文字列を取得するために呼び出すコールバックメソッドです。
テキストボックスの値の管理は、プログラマがテキストを格納する領域を確保して保持しておき、必要なときにコールバックメソッド経由で Excel に渡すという方法で制御します。
ListiView の仮想アイテムと似たような方式で、かなり変わっているので最初は戸惑うかもしれません。

onChange 属性はテキストボックスの内容が変更されたときに呼び出されます。イベントの発生するタイミングは結構クセがあったような気がしますが、ちょっと記憶が曖昧です。

#### ボタンを定義する

```xml
<tab ...>
  <group ...>
    <button id="SampleButton" label="検索" imageMso="FindDialog" size="normal" onAction="SampleTab.SampleButton_onAction"/>
  </group>
</tab>
```

label 属性はボタンのラベルです。
imageMso 属性は Excel 組み込みの画像名です。調べ方は下の画像を参考にしてください。
詳しくは "imageMso" でググれば出てきます。
size 属性はボタンの大きさです。"large" もしくは "normal" を指定します。
onAction 属性はボタンがクリックされたときに呼び出されるコールバックメソッドを設定します。

![2014-08-30_162337.png](https://qiita-image-store.s3.amazonaws.com/0/52104/8e0ca15b-6ab4-a16d-ed1e-a27deea8f874.png)

# コールバックメソッドを実装する

これまでの例を総合すると以下のような XML になります。

```xml:customUI14.xml
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" xmlns:mso="http://schemas.microsoft.com/office/2009/07/customui" onLoad="CustomUI.OnLoad">
  <ribbon>
    <tabs>
      <tab id="SampleTab" label="タブ名" getVisible="CustomUI.SampleTab_getVisible">
        <group id="SampleGroup" label="グループ名" centerVertically="true">
          <box id="SampleBox" boxStyle="horizontal">
            <editBox id="SampleText" label="サンプルテキスト" sizeString="wwwwwwwwwww" getText="SampleTab.SampleText_getText" onChange="SampleTab.SampleText_onChange"/>
            <button id="SampleButton" label="検索" imageMso="FindDialog" size="normal" onAction="SampleTab.SampleButton_onAction"/>
          </box>
        </group>
      </tab>
    </tabs>
  </ribbon>
</customUI>
```

XML ファイルを保存して ZIP ファイルにまとめてから拡張子を .xlsx に変更します。
それではコールバックメソッドを実装していきましょう。

```vbnet:CustomUI
Public m_ribbon As IRibbonUI

Private Sub OnLoad(ribbon As IRibbonUI)
    'リボンのインスタンスを取得できる唯一の機会です
    'ここで必ずリボンの参照を変数に保持しておきましょう。
    Set m_ribbon = ribbon
End Sub

Private Sub SampleTab_getVisible(control As IRibbonControl, ByRef returnedVal)
    '常にタブを表示する
    returnedVal = True
End Sub
```

```vbnet:SampleTab
'テキストボックスの内容は自分で保持する
Private m_SampleText As String

Private Sub SampleText_getText(control As IRibbonControl, ByRef Text)
    Text = m_SampleText
End Sub

Private Sub SampleText_onChange(ByRef control As IRibbonControl, ByRef Text As String)
    m_SampleText = Text
End Sub

Private Sub SampleButton_onAction(ByVal control As IRibbonControl)
    'ボタンがクリックされた！
End Sub
```

テキストボックスにこちらから積極的に値をセットする方法が見当たりませんよね。
テキストボックスの内容を任意のタイミングで更新するには、CustomUI.OnLoad イベントで取得できる IRibbonUI の Invalidate メソッドや InvalidateControl メソッドを使用することで、Excel が SampleText_getText を呼び出してくれるので結果的に更新されます。
VBA はコードを書き直してコンパイルが走ると、一定の条件で静的変数が保持している値が初期化されます。
CustomUI.OnLoad で保持しておいたリボンのインスタンスが Nothing になったらブックを開き直しましょう。

# さいごに

リボン UI は相変わらず人気が無いですが、私は結構好きになってきましたよ。
やっぱり慣れって大きいですね。
VBA とリボンという不人気コンビで、この記事の需要は限りなくゼロに近いと思います(´・ω・`)
さらっと紹介程度に書いたつもりだけど、それでも長くなっちゃいましたねｗ