C# Excel VSTO アドイン開発【Excel内の画像抽出】

C#でExcelの画像抽出をする処理を作ったので、アドインとして実行できるようにしてみる :grinning:

## プロジェクト作成
visual studioで「新しいプロジェクトの作成」の「Excel VSTOアドイン」を選択
※`Microsoft Office Developer Tools`が必須なので、プロジェクトのテンプレートが見つからなければ`visual studio インストーラー`でインストールしておく

## アドインのリボンを作成
プロジェクトを右クリック→新しい項目→リボン(ビジュアルなデザイナー)を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482880/9c299b36-a036-ce02-86fe-c9a9cebd5399.png)

[表示]→[ツールボックス]→[Button]をドラッグ＆ドロップでボタン配置
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482880/1423ea5e-591d-9abc-f211-64ce9fef6ebf.png)

デザイナー上でボタンをダブルクリックすると、クラスの編集画面にジャンプする
```cs
using Microsoft.Office.Tools.Ribbon;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ExcelAddIn
{
	public partial class Ribbon1
	{
		private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
		{

		}

		private void button1_Click(object sender, RibbonControlEventArgs e)
		{

		}
	}
}
```

## アドインを記述する
Excelのブックにある全ての画像を抽出するアドインを作成する
```Services/ImageService.cs
using Microsoft.Office.Interop.Excel;
using System;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Application = Microsoft.Office.Interop.Excel.Application;
using Workbook = Microsoft.Office.Interop.Excel.Workbook;
using Worksheet = Microsoft.Office.Interop.Excel.Worksheet;

namespace ExcelAddIn.Services
{
	public class ImageService
	{
		public bool SaveImagesFromWorkbook(string workbookPath)
		{
			Application excelApp = null;
			Workbook workbook = null;

			try
			{
				excelApp = new Application();
				workbook = excelApp.Workbooks.Open(workbookPath);

				string saveDirectory = Path.Combine(
					Path.GetDirectoryName(workbookPath),
					Path.GetFileNameWithoutExtension(workbookPath) + "_Images");

				if (!Directory.Exists(saveDirectory))
				{
					Directory.CreateDirectory(saveDirectory);
				}

				int imageIndex = 0;
				foreach (Worksheet ws in workbook.Sheets)
				{
					foreach (Shape shape in ws.Shapes)
					{
						if (shape.Type == Microsoft.Office.Core.MsoShapeType.msoPicture)
						{
							string imagePath = Path.Combine(saveDirectory, $"Image_{++imageIndex}.jpg");
							shape.CopyPicture(XlPictureAppearance.xlScreen, XlCopyPictureFormat.xlBitmap);

							if (Clipboard.ContainsImage())
							{
								var image = Clipboard.GetImage();
								image.Save(imagePath, ImageFormat.Png);
							}
						}
					}
				}

				return imageIndex > 0;
			}
			catch (Exception ex)
			{
				MessageBox.Show($"Error: {ex.Message}");
				return false;
			}
			finally
			{
				if (workbook != null)
				{
					workbook.Close(false);
					Marshal.ReleaseComObject(workbook);
				}
				if (excelApp != null)
				{
					excelApp.Quit();
					Marshal.ReleaseComObject(excelApp);
				}
			}
		}

	}
}
```

ボタンクリックから呼び出す
```RibbonImage.cs
using ExcelAddIn.Services;
using Microsoft.Office.Tools.Ribbon;
using System;
using System.Windows.Forms;

namespace ExcelAddIn
{
	public partial class RibbonImage
	{
		private void RibbonImage_Load(object sender, RibbonUIEventArgs e)
		{

		}

		private void buttonImage_Click(object sender, RibbonControlEventArgs e)
		{
			try
			{
				var excelApp = Globals.ThisAddIn.Application;
				var workbook = excelApp.ActiveWorkbook;
				if (workbook != null)
				{
					string workbookPath = workbook.FullName;
					var imageService = new ImageService();
					bool result = imageService.SaveImagesFromWorkbook(workbookPath);
					if (result)
					{
						MessageBox.Show(
							"画像が正常に保存されました。",
							"成功",
							MessageBoxButtons.OK,
							MessageBoxIcon.Information);
					}
					else
					{
						MessageBox.Show(
							"保存する画像が見つかりませんでした。",
							"警告",
							MessageBoxButtons.OK,
							MessageBoxIcon.Warning);
					}
				}
			}
			catch (Exception ex)
			{
				MessageBox.Show($"エラー: {ex.Message}", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}
	}
}

```

## アドインの配布
構成をReleaseに変更し、[ビルド]→[ソリューションのリビルド]を実行
`bin/Release`フォルダにデプロイ用のファイルが作成される
※リリースするExcelに合わせて`32bit`・`64bit`を選択する必要がある

```
└─Release
        ExcelAddIn.dll
        ExcelAddIn.dll.manifest
        ExcelAddIn.pdb
        ExcelAddIn.vsto
        Microsoft.Office.Tools.Common.v4.0.Utilities.dll
```

`.vsto`ファイルを実行すると、Excelにアドインがインストールされる
Excelのタブ[アドイン]から処理を実行できる

## ソースコード
[GitHub](https://github.com/pixcelo/CSharp-app/tree/main/ExcelAddIn)

## Reference
- [チュートリアル: 初めての Excel 用 VSTO アドインを作成する](https://learn.microsoft.com/ja-jp/visualstudio/vsto/walkthrough-creating-your-first-vsto-add-in-for-excel?view=vs-2022&tabs=csharp)
- [VSTOアドインのプログラミングの概要](https://learn.microsoft.com/ja-jp/visualstudio/vsto/getting-started-programming-vsto-add-ins?view=vs-2022&source=recommendations)