【Revit】Revitの「リボンタブ」「リボンパネル」「ボタン」の作成方法【アドイン開発】

# 概要
Revitのアドイン開発における、「リボンタブ」「リボンパネル」「ボタン」の作成方法をまとめました。

# コード
`IExternalApplication`を実装したクラスの`OnStartup`メソッド (Revit起動時に最初に呼ばれるメソッド) の中で行っています。

```C#:App.cs
internal class App : IExternalApplication
{
    /// <summary>
    /// リボンタブ表示名
    /// </summary>
    private static readonly string TabName = "サンプルタブ";

    /// <summary>
    /// 「サンプルタブ」内のパネル表示名
    /// </summary>
    private static readonly string PanelName = "サンプルパネル";

    /// <summary>
    /// 「サンプルパネル」内のボタンの表示名
    /// </summary>
    public static readonly string ButtonName = "サンプルボタン";
    
    /// <summary>
    /// 「サンプルパネル」内のボタンの内部の名前
    /// </summary>
    /// <remarks>
    /// 表示名ではなく、内部の名前
    /// </remarks>
    public static readonly string InternalButtonName = "samplebutton";

    /// <summary>
    /// ボタン押下で最初に呼び出すコマンドが定義されているアセンブリまでのパス
    /// </summary>
    private static readonly string AssemblyPath =
        Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\hogehoge.dll";
            
    public Result OnStartup(UIControlledApplication application)
    {
        CreateRibbon(application);
    }

    /// <summary>
    /// リボンタブ、リボンパネル、リボンボタンを作成する
    /// </summary>
    /// <param name="application"></param>
    private void CreateRibbon(UIControlledApplication application)
    {
        // リボンタブ作成
        try
        {
            application.CreateRibbonTab(TabName);
        }
        catch
        {
            // 既に作成されている場合はエラーになる
        }

        // リボンパネル作成
        var ribbonPanel = application.GetRibbonPanels(TabName).FirstOrDefault();
        if (ribbonPanel == null)
        {
            // 存在しない場合のみ作成する
            ribbonPanel = application.CreateRibbonPanel(TabName, PanelName);
        }

        // リボンボタン作成

        if (!ribbonPanel.GetItems().Any(x => x.Name == ItemName))
        {
            // ボタンが存在しない場合のみ作成する

            var source = GetButtonImage("SampleButtonImage");            
            var button =
                new PushButtonData(InternalButtonName, ButtonName, AssemblyPath,
                    "HogeNamespace.HogeCommand")
                {
                    Image = source,
                    LargeImage = source
                };
            // リボンパネルにボタン追加
            ribbonPanel.AddItem(button);
        }
    }

    /// <summary>
    ///   リソースに登録されているデータからImageSourceを返却する
    /// </summary>
    /// <param name="resourceName">使用するリソースの名前</param>
    /// <returns></returns>
    private ImageSource GetButtonImage(string resourceName)
    {
        var resource = new ResourceDictionary
        {
           // リソースを指定

            // ここではWPFのパックURIを用いてリソースを指定しています
            Source = new Uri("pack://application:,,,/hogehoge;component/ResourceDictionary.xaml")
        };
        var geometry = resource[resourceName] as Geometry;
        var geometryDrawing = new GeometryDrawing
        {
            Geometry = geometry,
            Brush = new SolidColorBrush(Colors.Aqua),
            Pen = new Pen(Brushes.Aqua, 0.5)
        };

        var drawingImage = new DrawingImage(geometryDrawing);
        drawingImage.Freeze();

        return new Image
        {
            Source = drawingImage,
            Stretch = Stretch.None
        }.Source;
    }
}
```

:::note
`PushButtonData`クラスの`Image` (`LargeImage`) プロパティにボタンのイメージ画像をセットすることができます。
ここでは、別ファイルの`ResourceDictionary.xaml`に定義しておいた[パスデータ](https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/graphics-multimedia/path-markup-syntax?view=netframeworkdesktop-4.8)を用いてイメージをセットしています。
:::

```xml:ResourceDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Geometry x:Key="SampleButtonImage">
        M 10,100～ (※以下略。図形のパスデータを記載)
    </Geometry>
</ResourceDictionary>
```

```C#:hogehoge.dll
namespace HogeNamespace
{
    /// <summary>
    /// 外部コマンド
    /// </summary>
    [Transaction(TransactionMode.Manual)]
    public class HogeCommand : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            // 「サンプルボタン」押下後の処理
        }
    }
}
```

:::note
`App`クラスの`PushButtonData`のコンストラクタの引数で、`AssemblyPath` (ボタン押下後のコマンドを処理するdllまでのパス) 、`"HogeNamespace.HogeCommand"`を指定したので、ボタンを押下すると`hogehoge.dll`の`HogeNamespace.HogeCommand`の処理が流れます。
※クラスには`RevitAPIUI.dll`の`IExternalCommand`の実装が必要です。
:::

# おわりに
本稿の本質とは少し外れますが、xamlで記載できる図形のパスデータはInkScapeというツールを使用して画像をxamlに変換できるそうでした。なので少し使用してみて、もしそれが`PushButtonData`等の画像データの作成に有用そうなら、また記事にしたいと思いました。