C#でExcelアドインを作ってみる

# Excel拡張
今回はC#でExcelアドオンを作ってみます。
個人的に大量にデータがあるときに今自分がどこのセルに書き込んでいるのか知るハイライターが欲しいなと思っていたので、簡単そうなのでチャレンジしてみました。

完成品は以下の画像のようなものになります。
<iframe width="560" height="315" src="https://www.youtube.com/embed/6woqsShIvSQ?si=py9ydgvli05ZT6Oh" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

# Excel VSTOテンプレート
Visual Studioの新規作成画面から`Excel VSTO アドイン`を選択して作成します。
このテンプレートが現在ない場合、`Visual Studio Installer`の`変更` `ワークロード`で`Office拡張機能`にチェックを入れると追加されます。

# 処理を書く
テンプレートを開いたとき一番最初にあるクラスに処理を書いていきます。
```csharp
public partial class ThisAddIn
{
    private Excel.Worksheet currentWorksheet;
    private Excel.Range previousSelection;
    private List<Excel.Shape> previousShapes = new List<Excel.Shape>();

    // StartUp処理。現在のシートを取得します。
    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
        currentWorksheet = this.Application.ActiveSheet as Excel.Worksheet;
        if (currentWorksheet != null)
        {
            // シートを変えたとき、以下の処理を呼び出す。
            currentWorksheet.SelectionChange += new Excel.DocEvents_SelectionChangeEventHandler(CurrentWorksheet_SelectionChange);
        }

    }
    
    // シートを変えたときに呼び出す処理
    private void CurrentWorksheet_SelectionChange(Excel.Range target)
    {
        // 現在オーバーレイしているハイライトをすべて消す
        ClearPreviousOverlay();
        // 新しくオーバーレイのハイライトを作る
        CreateOverlay(target);
    }

    // 現在のオーバーレイをすべて消す
    public void ClearPreviousOverlay()
    {
        // オーバーレイ要素をすべて削除する。(前回引いた線を削除したいため)
        foreach(var shape in previousShapes)
        {
            shape.Delete();
        }
        previousShapes.Clear();
    }

    // 新しくオーバーレイを作ってハイライト表示を行う
    public void CreateOverlay(Excel.Range selectedCell)
    {
        if (selectedCell != null)
        {
            // 現在のワークシートをとってくる
            Excel.Worksheet worksheet = selectedCell.Worksheet;
            // 現在選択しているセルを取得
            previousSelection = selectedCell;

            // 現在選択しているセルのRow情報を取得
            Excel.Range entireRow = worksheet.Rows[selectedCell.Row];
            // 現在選択しているセルのColumn情報を取得
            Excel.Range entireColumn = worksheet.Columns[selectedCell.Column];
            // その他描画に必要な要素を取得
            float rowTop = (float)entireRow.Top;
            float columnLeft = (float) entireColumn.Left;
            float Top = 0.0f;
            float Left = 0.0f;
            float Width = (float)(entireColumn.Left + worksheet.UsedRange.Width);
            float Hight = (float)(entireRow.Top + worksheet.UsedRange.Height);
            // 4要素を指定し線を引く((1x,1y)→(2x,2y))の4要素で線を引く
            Excel.Shape rowShapeTop = worksheet.Shapes.AddLine(
                Left, rowTop, Width, rowTop);
            // 4要素を指定し線を引く((1x,1y)→(2x,2y))の4要素で線を引く
            // ワークシートの現在使っているレンジの幅を足すことで、下のラインを描画している
            Excel.Shape rowShapeBottom = worksheet.Shapes.AddLine(
                Left, rowTop + worksheet.UsedRange.Height, Width, rowTop + worksheet.UsedRange.Height);
            // 4要素を指定し線を引く((1x,1y)→(2x,2y))の4要素で線を引く
            Excel.Shape columnShapeLeft = worksheet.Shapes.AddLine(
                columnLeft, Top, columnLeft, Hight);
            // 4要素を指定し線を引く((1x,1y)→(2x,2y))の4要素で線を引く
            // ワークシートの現在使っているレンジの幅を足すことで、右のラインを描画している
            Excel.Shape columnShapeRight = worksheet.Shapes.AddLine(
                columnLeft + worksheet.UsedRange.Width, Top, columnLeft + worksheet.UsedRange.Width, Hight);
            // 線種を変更
            SetDottedBorder(rowShapeTop);
            SetDottedBorder(rowShapeBottom);
            SetDottedBorder(columnShapeLeft);
            SetDottedBorder(columnShapeRight);
            // 前回引いた線ということで、Listに保持する
            previousShapes.Add(rowShapeTop);
            previousShapes.Add(rowShapeBottom);
            previousShapes.Add(columnShapeLeft);
            previousShapes.Add(columnShapeRight);
        }
    }
    // 点線、赤色、太さ1.5という線種に設定
    private void SetDottedBorder(Excel.Shape shape)
    {
        shape.Line.Visible = Office.MsoTriState.msoTrue;
        shape.Line.DashStyle = Office.MsoLineDashStyle.msoLineDash;
        shape.Line.ForeColor.RGB = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red);
        shape.Line.Weight = 1.5f;
    }


    private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
    {
    }

    #region VSTO で生成されたコード

    /// <summary>
    /// デザイナーのサポートに必要なメソッドです。
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InternalStartup()
    {
        this.Startup += new System.EventHandler(ThisAddIn_Startup);
        this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
    }
    
    #endregion
}
```
これが基本的な選択したセルまでのハイライトを表示する処理になります。
なぜ線を引くことになったかというと、
1. 最初はセルの背景色を変えるだけにしようとしていた
 ↓
1. ハイライトする前に設定していた背景色が上書きされるので没
 ↓
1. 背景色ではなく図形(四角)のオーバーレイで背景色を変えなくて済むようにした
 ↓
1. 図形なので、ハイライトしている上から選択したいセルをクリックすると図形が選択されてしまい、アクセスできなくなり没
 ↓
1. 図形ではなく線なら、クリックはできるが、セルの選択の邪魔にならないだろうということでこの方法を採用した

このような経緯で設計しています。

# RibbonUIについて
いくらハイライトとは言っても常時起動しておくのはめんどうなため、任意でチェックボックスの入り切りで、動作させようと思います。
UIを作るには、`プロジェクトを右クリック`,`追加`,`新しい項目`から、`Office/SharePoint`,`リボン(ビジュアルなデザイナー)`からリボンを追加します。
今回は名前を`ExtensionRibbon.cs`と付けました。
開くとこのような形がでます。(これは最初についていたボタンを削除してチェックボックスを配置している状態)
 ![スクリーンショット 2024-12-14 184918.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949075/f16c05d4-bc9c-100b-8d79-fb43bab1f91e.png)

最初に配置されていたボタンを削除して`ツールボックス`から`CheckBox`をドラッグアンドドロップで配置します。
`ツールボックス`はVisual Studioの上部メニューの`表示`タブから表示できます。

配置が完了したら、アイテムをクリックして名前などのプロパティを設定します。(ここでは割愛)

設定し終わったら、配置した`CheckBox`を`ダブルクリック`します。

するとC#のファイルが開きますので、以下のように記述していきます。

```csharp
 public partial class ExtensionRibbon
 {
     private bool isHighlightEnabled = false;
     private void ExtensionRibbon_Load(object sender, RibbonUIEventArgs e)
     {
     }

    // 先ほどのプロパティで設定した内容でメソッド名が変わります。
    // チェックボックスをクリックしたときに呼ばれる処理
     private void checkHighlight_Click(object sender, RibbonControlEventArgs e)
     {
         // チェックボックスにチェックが入っているか確認
         if (checkHighlight.Checked)
         {
             // 入っていたら
             // SelectionChangeイベントのハンドラーを設定
             Globals.ThisAddIn.Application.SheetSelectionChange += new Excel.AppEvents_SheetSelectionChangeEventHandler(SelectionChangeHandler);
         }
         else
         {
             // 入ってなかったら
             // SelectionChangeイベントのハンドラーを解除
             Globals.ThisAddIn.Application.SheetSelectionChange -= SelectionChangeHandler;
             // 既存のハイライトをクリア
             Globals.ThisAddIn.ClearPreviousOverlay();

         }
     }

     // 上部の処理で登録や解除をしてるハンドラー
     private void SelectionChangeHandler(object sh, Excel.Range target)
     {
         // 先ほど作ったハイライト処理を呼び出す
         // 最初に現在描かれている可能性のあるハイライトを一旦削除して新しくハイライトを作る。
         Globals.ThisAddIn.ClearPreviousOverlay();
         Globals.ThisAddIn.CreateOverlay(target);
     }
 }
```
これでVisualStudioの`開始`ボタンを押すとExcelが立ち上がり、リボンのタブに追加したタブが表示され、そのリボンからこのチェックボックスにアクセスできるので、これを使い、Excelをハイライトさせてください。