空談録

http://artfulplace.net/blogs/ からひっこしつつ

OfficeのリボンXMLを別アセンブリのクラスから拡張する

ぐぬぬぬぬ、月一更新はしたいと思っていたのに…

OfficeのリボンカスタマイズにはXMLを書く場合とWinFormsもどきで頑張っていじる場合があると思います。
しかしXMLで書くのは面倒…インテリセンスがそこそこ効くけど…

というあれそれを解決するためにxamlで書けるようにしたい!というライブラリを作ろうとした話です。
完成?いつかするかなって…一応根本部分はできたしあとは生やせば……

なおあまりxaml関係ない記事です。IRibbonExtensibilityとかいうやつの話です

XAMLで扱えるクラスでリボンを拡張する

リボンの拡張にはIRibbonExtensibility (Microsoft.Office.Core)を使います。これさえ実装していればおっけー!という感じだと思います
ついでにXAMLで読み込むためにDependencyObjectを継承しておきましょう。

サクサク実装するとこんなんでしょうか

using System.Windows;
using System.Windows.Markup;
using Office = Microsoft.Office.Core;

// ~~~~~~

[ComVisible(true)]
public class RibbonExtensibility : DependencyObject, Office.IRibbonExtensibility
{
    public string GetCustomUI(string RibbonID)
    {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?> ...";
    }
}

とまあいつも通りダメコードなのですが、ひとまずこれで作ってみる感じかなと
DependencyObjectを継承してXAMLで扱えるようにして、IRibbonExtensibilityを実装してリボンを読み込んでもらえるようにします。

ちなみにIDTExtensibility2がいるとかどこかにあるんですけどVSTOアドインの場合は不要で、COMアドインの場合は普通に必須です。
(というよりはCOMアドインそのものの読み込みにIDTExtensibility2が必要なのでリボン関係ないのでは…とは思う)

GetCustomUIの中身は最初は動くXMLを入れておきましょう。結構検証で詰まりますし。

で、読み込み部分はThisAddin.csに次のコードをコピペします。
読み込み部分は単純で、オーバーライドしてIRibbonExtensibilityを返せばいい感じです

protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
    return new RibbonExtensibility();
}

やべえ!XAML全然関係ない!

で、アドインを実行するともれなく次のようなメッセージが出ます。

A QueryInterface call was made requesting the class interface of COM visible managed class 'RibbonExtensibility'. However since this class derives from non COM visible class 'DependencyObject', the QueryInterface call will fail. This is done to prevent the non COM visible base class from being constrained by the COM versioning rules.

DependencyObjectがCOMで参照できないんですけど!!って言われます。
参照できなくてもいいんだけど…と思って続行していると読み込んでくれません。つらい。

ちなみにDependencyObjectを継承するアホなことをしていなければ別ライブラリでIRibbonExtensibilityだけ実装で読み込めるはずです。なんと!このブログ記事の存在価値やいかに

というわけで答えを書いてしまいましたがComVisibleじゃないクラスを継承していなければいいだけなのでRibbonExtensibilityからDependencyObjectを消します。
代わりにRibbonExtensibilityを使っていた部分を置き換える別のクラスを作って、こちらに「DependencyObjectのみ」を継承します。IRibbonExtensibilityは不要です。

今回だと次のようなコードで対処できます。

[ComVisible(true)]
public class RibbonExtensibility : Office.IRibbonExtensibility
{
    public RibbonExtensibility(CustomUI ui)
    {
        customUI = ui.GetRibbonXml();
    }
    private string customUI { get; set; }
    public string GetCustomUI(string RibbonID)
    {
        return customUI;
    }
}

public class CustomUI : DependencyObject
{
    public string GetRibbonXml() { ... }
}

つまるところ適当に継承して手抜きをするなって話ですね。
別ライブラリとか関係なくね?って感じがしていますが、実質その通りでどこにあっても動きます。

XAML読み込みはどうなったのよ

こちらの顛末を書いておきます。

先ほどのCustomUIみたいなクラスを大量にはやすことでこんなxamlが記述できます。

<CustomUI x:Class="NereidTestAddin.RibbonData"
             xmlns="clr-namespace:artfulplace.Nereid;assembly=artfulplace.Nereid"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:NereidTestAddin">
    <Ribbon>
        <Tabs>
            <Tab IdMso="TabAddIns">
                <Group Id="MyGroup" Label="My Group">
                    <Button Id="aaaa" Label="aaaa" />
                </Group>
            </Tab>
        </Tabs>
    </Ribbon>
</CustomUI>

で、これを普通にXMLとして作るだけです。
作るとこんな感じになります。

<?xml version="1.0" encoding="UTF-8"?>
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" onLoad="Ribbon_Load">
  <ribbon>
    <tabs>
      <tab idMso="TabAddIns">
        <group id="MyGroup" label="My Group">
          <button id="aaaa" label="aaaa" />
        </group>
      </tab>
    </tabs>
  </ribbon>
</customUI>

ええまあいろいろ大変でしたが、読み込めない理由がただのタイプミスだったとかそんな話しかあとは残ってない…
上のを読み込むと普通にaaaaってボタンが増えます。もう画像はいいよね…


というわけでXAMLで書ける何かを作っている話でした。
文章が崩壊しまくっていてブログ書かなすぎの弊害を受けている…
それ以上にこれブログに書く意味あったのか大変に怪しい…

github.com
上の何かでそれっぽい作業をしてます。心優しい方はDependencyPropertyをはやしてくれると大変助かります。。。。。
ぶっちゃけすでに飽きかけているのもあり、それ以上にDataContextがFrameworkElement以上にしかないことを知ったり、UIの更新にはInvalidateでリボンそのものを読み込み直すしかなかったりでXAMLでやる意味あるのか?という疑問が出てきている…

この辺で