VC++ でエクセルを操作する (文字やセルの色を変更 etc)

# 概要
今まで `VC++` でエクセルを操作して、数値データを入力して保存、データからグラフを作成するプログラムを紹介してきました。
今回は少し細かい操作についてのプログラムを幾つか紹介したいと思います。

# 文字色・フォント・サイズとセルの背景色を変更
まず、文字列の色・フォント・サイズとセルの背景色を変更したいと思います。
とりあえずソースコードを見てください。

```C++
#define  WIN32_LEAN_AND_MEAN
#define  _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <string>
#include <comdef.h>

//Excelを操作するためのタイプライブラリを読みこむ (オフィスのバージョンに依存しない)
//Microsoft Office Object Library
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" no_auto_exclude auto_rename dual_interfaces
//Microsoft Visual Basic for Applications Extensibillity
#import "libid:0002E157-0000-0000-C000-000000000046" dual_interfaces
//Mcrosoft Excel Object Library
#import "libid:00020813-0000-0000-C000-000000000046" no_auto_exclude auto_search auto_rename dual_interfaces

using namespace Excel;										//Excel ネームスペースを使う

struct StartOle {
	StartOle() { CoInitialize(NULL); }						//COMを初期化
	~StartOle() { CoUninitialize(); }						//COMを閉じる
} _inst_StartOle;

void dump_com_error(_com_error& e)
{
	std::cerr << "Com error!\n";
	std::cerr << "\tCode = " << std::setw(8) << std::hex << e.Error() << '\n';
	std::cerr << "\tCode meaning = " << e.ErrorMessage() << '\n';
	_bstr_t bstrSource(e.Source());
	_bstr_t bstrDescription(e.Description());
	LPCSTR	source = (LPCSTR)bstrSource;
	std::cerr << "\tSource = " << (source ? source : "(NULL)") << "\n";
	LPCSTR descript = (LPCSTR)bstrDescription;
	std::cerr << "\tDescription = " << (descript ? descript : "(NULL)") << "\n";
}

int main(void)
{
	_ApplicationPtr excelApp;								//エクセル インスタンス

	//---------------------------------------------------------
	//Excelの起動
	HRESULT hr =  excelApp.CreateInstance(L"Excel.Application");

	if SUCCEEDED(hr) {										//エクセル インスタンスの生成を確認
		excelApp->Visible[0] = VARIANT_TRUE;				//エクセルを表示する
		excelApp->DisplayAlerts[0] = VARIANT_FALSE;			//警告が出ないように

		try {												//例外を捕捉
			//-------------------------------------------------
			//既存のワークブックを開く
			WorkbooksPtr workbooks = excelApp->Workbooks;	//ワークブック コレクション
			_WorkbookPtr workbook = workbooks->Open("Sample01.xlsx");

			//ワークシートを取得
			SheetsPtr worksheets = workbook->Worksheets;	//シート コレクション
			_WorksheetPtr worksheet = worksheets->Item[1];	//ワークシート (１枚目)

			int colors[] = {
				3,		//赤
				5,		//青
				7,		//ピンク
				8,		//水色
				10,		//緑
				22,		//コーラル
				23,		//オーシャンブルー
				29,		//紫
			};
			
			//-------------------------------------------------
			for (int i = 0; i < 8; ++i) {
				RangePtr cell = worksheet->Cells->Item[i+1][1];
				variant_t data = cell->Value2;				//データを読込む

				if (data.vt == VT_EMPTY)					//セルにデータなし
					continue;

				if (i < 4) {
					//文字色・フォント・サイズを変更する
					cell->Characters->Font->ColorIndex = colors[i];
					cell->Characters->Font->Name = bstr_t("游明朝 Demibold");
					cell->Characters->Font->Size = 21;
				} else {
					//セルの背景色を変更
					cell->Interior->ColorIndex = colors[i];
				}
			}

			worksheet.Release();							//COM オブジェクトを解放
			worksheets.Release();							//COM オブジェクトを解放

			//確認のために一時停止
			::Sleep(5 * 1000);
			excelApp->WindowState[0] = xlMinimized;			//ウィンドウを最小化
			std::cout << "文字列のフォントおよびセルの色の変更を確認:";
			std::string s;
			std::getline(std::cin, s);

			//ワークブックを閉じる
			workbook->Close();
			workbook.Release();								//COM オブジェクトを解放
			workbooks.Release();							//COM オブジェクトを解放
		}
		catch (_com_error& e) {								//例外処理
			dump_com_error(e);
		}

		//Excelを閉じる
		excelApp->Quit();
		excelApp.Release();									//COM オブジェクトを解放
	} else {												//エクセルを起動できない
		std::cerr << "エクセルを起動できません\n";
	}

	std::cout << "テストプログラムを終了:";
	std::string s;
	std::getline(std::cin, s);
}
```

エクセルの起動など以前のプログラムと同様です。
以前の記事で使用した以下のようなワークブックを読込み、文字列の色・フォント・サイズとセルの背景色を変更します。
![Excel20220318_000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207428/ebd8b59e-0434-020e-b6d7-38555c338045.png)

文字列のフォント関連は `Characters` オブジェクトで文字列を指定し、`Font` オブジェクトで色・フォント名・サイズ等を設定します。

あまり使う機会はないとは思いますが、`Characters[start][length]` で開始位置と文字数を指定すれば、文字単位で変更することも可能です。

セルの背景色の変更は `Range::Interior::ColorIndex` で行います。
複数のセルを選択して一括で変更することも可能です。

文字色もセルの背景色も `ColorIndex` を使用しています。これは旧来の方法で色数も限られますが、手軽に色を変更することができます。

結果はこのようなワークブックになります。
![Excel20230209_000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207428/70268040-1345-986b-bcf7-0907da793adc.png)

文字色等の変更が確認できたらそのまま、エクセルを終了します。
ワークブックの保存は行いません。

# ワークシートの追加とセルのコピー
次のサンプルは、ワークシートを追加してセルの内容をコピーします。

```C++
#define  WIN32_LEAN_AND_MEAN
#define  _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <string>
#include <comdef.h>

//Excelを操作するためのタイプライブラリを読みこむ (オフィスのバージョンに依存しない)
//Microsoft Office Object Library
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" no_auto_exclude auto_rename dual_interfaces
//Microsoft Visual Basic for Applications Extensibillity
#import "libid:0002E157-0000-0000-C000-000000000046" dual_interfaces
//Mcrosoft Excel Object Library
#import "libid:00020813-0000-0000-C000-000000000046" no_auto_exclude auto_search auto_rename dual_interfaces

using namespace Excel;										//Excel ネームスペースを使う

struct StartOle {
	StartOle() { CoInitialize(NULL); }						//COMを初期化
	~StartOle() { CoUninitialize(); }						//COMを閉じる
} _inst_StartOle;

void dump_com_error(_com_error& e)
{
	std::cerr << "Com error!\n";
	std::cerr << "\tCode = " << std::setw(8) << std::hex << e.Error() << '\n';
	std::cerr << "\tCode meaning = " << e.ErrorMessage() << '\n';
	_bstr_t bstrSource(e.Source());
	_bstr_t bstrDescription(e.Description());
	LPCSTR	source = (LPCSTR)bstrSource;
	std::cerr << "\tSource = " << (source ? source : "(NULL)") << "\n";
	LPCSTR descript = (LPCSTR)bstrDescription;
	std::cerr << "\tDescription = " << (descript ? descript : "(NULL)") << "\n";
}

int main(void)
{
	_ApplicationPtr excelApp;								//エクセル インスタンス

	//---------------------------------------------------------
	//Excelの起動
	HRESULT hr =  excelApp.CreateInstance(L"Excel.Application");

	if (SUCCEEDED(hr)) {									//エクセル インスタンスの生成を確認
		excelApp->Visible[0] = VARIANT_TRUE;				//エクセルを表示する
		excelApp->DisplayAlerts[0] = VARIANT_FALSE;			//警告が出ないように

		try {												//例外を捕捉
			//-------------------------------------------------
			//既存のワークブックを開く
			WorkbooksPtr workbooks = excelApp->Workbooks;	//ワークブック コレクション
			_WorkbookPtr workbook = workbooks->Open("Sample01.xlsx");

			//ワークシートを取得
			SheetsPtr worksheets = workbook->Worksheets;	//シート コレクション
			_WorksheetPtr worksheet = worksheets->Item[1];	//ワークシート (１枚目)

			//ワークシートを追加
			variant_t affter = (IDispatch *)worksheet;		//現在のシートの後に追加
			_WorksheetPtr new_sheet =
				worksheets->Add(vtMissing, affter, 1);		//ワークシートを追加

			//ワークシートの名前を変更
			new_sheet->Name = bstr_t("New_Sheet");

			//セル範囲を指定してコピー
			variant_t bg = worksheet->Cells->Item[1][1];	//開始セル
			variant_t ed = worksheet->Cells->Item[4][1];	//終了セル

			worksheet->Range[bg][ed]->Copy();				//セル範囲を指定してコピー
			
			new_sheet->Range["B2"]->Select();				//ペースト先を選択
			new_sheet->Paste();								//追加したシートにペースト

			new_sheet.Release();							//COM オブジェクトを解放
			worksheet.Release();							//COM オブジェクトを解放
			worksheets.Release();							//COM オブジェクトを解放

			//確認のために一時停止
			::Sleep(5 * 1000);
			excelApp->WindowState[0] = xlMinimized;			//ウィンドウを最小化
			std::cout << "ワークシートの追加およびセルのコピーを確認:";
			std::string s;
			std::getline(std::cin, s);

			//ワークブックを閉じる
			workbook->Close();
			workbook.Release();								//COM オブジェクトを解放
			workbooks.Release();							//COM オブジェクトを解放
		}
		catch (_com_error& e) {								//例外処理
			dump_com_error(e);
		}

		//Excelを閉じる
		excelApp->Quit();
		excelApp.Release();									//COM オブジェクトを解放
	} else {												//エクセルを起動できない
		std::cerr << "エクセルを起動できません\n";
	}

	std::cout << "テストプログラムを終了:";
	std::string s;
	std::getline(std::cin, s);
}
```

ワークシートを追加するには、`Sheets`コレクションの `Add` メソッドで行います。
`Add` の引数は
```C++
Worksheets::Add(variant_t &Before, variant_t &Affter, variant_t &Copy, variant_t &Type);
```
- Before 指定したシートの前に追加
- Affter してしてシートの後に追加
- Copy 追加するシートの枚数 (既定値 1)
- Type 追加するシートの種類

です。
`Before` と `Affter` はどちらか一方を指定します。
ここでは `Affter` に1枚目のワークシートを指定しているので、2枚目のワークシートとして追加されます。。

 `Add` メソッドの引数は全て `variant_t` なのですが、`Before` と `Affter` に指定するワークシートは `(IDispatch *)` キャスとしなければならないことに注意してください。

また `Add` メソッドは `IDispatchPtr` を返しますが、これを `_WorksheetPtr` 型の変数に代入すれば、ワークシートとして使用できます。

# ワークシートの削除
ワークシートを削除するときは
```C++
	worksheet = worksheets->Item["Sheet3"];
	worksheet->Delete();
```
このように行います。

# セルの内容のクリアとセルの削除
セル内容のクリアとセルの削除を行うサンプルです。
このようなワークブックを対象にします。
![Excel20230209_100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207428/88719573-1087-138c-4687-dd74e8fdbfe4.png)

```C++
#define  WIN32_LEAN_AND_MEAN
#define  _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <string>
#include <comdef.h>

//Excelを操作するためのタイプライブラリを読みこむ (オフィスのバージョンに依存しない)
//Microsoft Office Object Library
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" no_auto_exclude auto_rename dual_interfaces
//Microsoft Visual Basic for Applications Extensibillity
#import "libid:0002E157-0000-0000-C000-000000000046" dual_interfaces
//Mcrosoft Excel Object Library
#import "libid:00020813-0000-0000-C000-000000000046" no_auto_exclude auto_search auto_rename dual_interfaces

using namespace Excel;										//Excel ネームスペースを使う

struct StartOle {
	StartOle() { CoInitialize(NULL); }						//COMを初期化
	~StartOle() { CoUninitialize(); }						//COMを閉じる
} _inst_StartOle;

void dump_com_error(_com_error& e)
{
	std::cerr << "Com error!\n";
	std::cerr << "\tCode = " << std::setw(8) << std::hex << e.Error() << '\n';
	std::cerr << "\tCode meaning = " << e.ErrorMessage() << '\n';
	_bstr_t bstrSource(e.Source());
	_bstr_t bstrDescription(e.Description());
	LPCSTR	source = (LPCSTR)bstrSource;
	std::cerr << "\tSource = " << (source ? source : "(NULL)") << "\n";
	LPCSTR descript = (LPCSTR)bstrDescription;
	std::cerr << "\tDescription = " << (descript ? descript : "(NULL)") << "\n";
}

int main(void)
{
	_ApplicationPtr excelApp;								//エクセル インスタンス

	//---------------------------------------------------------
	//Excelの起動
	HRESULT hr =  excelApp.CreateInstance(L"Excel.Application");

	if (SUCCEEDED(hr)) {									//エクセル インスタンスの生成を確認
		excelApp->Visible[0] = VARIANT_TRUE;				//エクセルを表示する
		excelApp->DisplayAlerts[0] = VARIANT_FALSE;			//警告が出ないように

		try {												//例外を捕捉
			//-------------------------------------------------
			//既存のワークブックを開く
			WorkbooksPtr workbooks = excelApp->Workbooks;	//ワークブック コレクション
			_WorkbookPtr workbook = workbooks->Open("Sample02.xlsx");

			//ワークシートを取得
			SheetsPtr worksheets = workbook->Worksheets;	//シート コレクション
			_WorksheetPtr worksheet = worksheets->Item[1];	//ワークシート (１枚目)

			//指定範囲のセルのクリアと削除
			variant_t bg = worksheet->Cells->Item[2][4];	//開始セル
			variant_t ed = worksheet->Cells->Item[3][5];	//終了セル

			RangePtr range = worksheet->Range[bg][ed];		//セル範囲
			range->ClearContents();							//セルの内容をクリア

			//確認のために一時停止
			excelApp->WindowState[0] = xlNormal;
			::Sleep(5 * 1000);
			excelApp->WindowState[0] = xlMinimized;			//ウィンドウを最小化
			std::string s;
			std::cout << "セルのクリアを確認:";
			std::getline(std::cin, s);

			//指定範囲のセルを削除
			range->Delete(xlShiftToLeft);					//左へシフト
			range.Release();								//COM オブジェクトを解放

			//確認のために一時停止
			excelApp->WindowState[0] = xlNormal;			//ウィンドウ復元
			::Sleep(5 * 1000);
			excelApp->WindowState[0] = xlMinimized;			//ウィンドウを最小化
			std::cout << "セルの削除を確認:";
			std::getline(std::cin, s);

			worksheet.Release();							//COM オブジェクトを解放
			worksheets.Release();							//COM オブジェクトを解放

			//ワークブックを閉じる
			workbook->Close();
			workbook.Release();								//COM オブジェクトを解放
			workbooks.Release();							//COM オブジェクトを解放
		}
		catch (_com_error& e) {								//例外処理
			dump_com_error(e);
		}

		//Excelを閉じる
		excelApp->Quit();
		excelApp.Release();									//COM オブジェクトを解放
	} else {												//エクセルを起動できない
		std::cerr << "エクセルを起動できません\n";
	}

	std::cout << "テストプログラムを終了:";
	std::string s;
	std::getline(std::cin, s);
}
```
セル範囲を指定して、クリア (`ClearContents`) と削除 (`Delete`) を行います。
`ClearContents` はセルの内容 (入力された値) をクリアします。書式やコメント (もしあれば) などはそのままです。書式などもクリアするときには `Clear` を使用します。

`Delete` はセルそのものを削除します。セルを削除するので、空いた部分には右もしくは下のセルからシフトして入ります。シフトする方向は引数で指定します。既定では `xlShiftToLeft` (左へシフト) です。`xlShiftUp` を指定することで上へシフトします。

# 行の削除と挿入
行を削除する場合は次のようにします
```C++
//行を選択して削除
RangePtr rows = worksheet->Rows->Item["2:3"][vtMissing];	//行を選択
rows->Delete();
```
行を挿入するには次のようにします
```C++
RangePtr row = worksheet->Rows->Item[3][vtMissing];
row->Insert();
```

列の削除と追加は基本的に同じで `Rows` が `Columns` になります。

# エクセルの関数を使用する
エクセルの関数を `VC++` のプログラムから使用することができます。
`SUM` や `AVERAGE` などの基本的な関数の他に、財務関数なども使用することができるので `PMT` 関数でローン計算をすることも可能です。
ここでは、比較的使用頻度が高い `VLOOKUP` 関数を使ったサンプルを紹介します。
このようなワークブックを読込み、商品IDから単価を取得します。
![Excel20230212_000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207428/5cde5456-f2f6-a637-fde9-ee1219067bec.png)
```C++
	//既存のワークブックを開く
	WorkbooksPtr workbooks = excelApp->Workbooks;	//ワークブック コレクション
	_WorkbookPtr workbook = workbooks->Open("Sample04.xlsx");

	//ワークシートを取得
	SheetsPtr worksheets = workbook->Worksheets;	//シート コレクション
	_WorksheetPtr worksheet = worksheets->Item[1];	//ワークシート (１枚目)

	//商品一覧のセル範囲取得
	variant_t st = worksheet->Cells->Item[2][1]; 	//開始セル
	variant_t ed = worksheet->Cells->Item[21][3]; 	//終了セル
	variant_t range = (IDispatch *)worksheet->Range[st][ed];
			
	//エクセル関数を取得
	WorksheetFunctionPtr function = excelApp->WorksheetFunction;

	//標品一覧から価格取得
	bstr_t id("A003");
	variant_t price = function->VLookup(id, range, 3);
	if (price.vt == VT_R8) {						//price が double 型であれば
		std::cout << (const char *)id << " の単価 : " << price.dblVal << '\n';
	}

	function.Release();
	worksheet.Release();							//COM オブジェクトを解放
	worksheets.Release();							//COM オブジェクトを解放

	//ワークブックを閉じる
	workbook->Close();
	workbook.Release();								//COM オブジェクトを解放
	workbooks.Release();							//COM オブジェクトを解放
```
エクセルの起動やワークブックの読込みは同じです。
`_Application::WorksheetFunction` を取得して関数を使用します。
各関数の引数はエクセルで使用するものと同じです。
ID は直セル文字列を渡せます。わざわざセルに入力する必要はないです。

# メソッドやプロパティの調べ方
これまで幾つかのメソッドやプロパティを紹介してきましたが、これらはごく一部です。とても紹介しきれないほどのメソッドやプロパティがあります。

基本的にユーザー (人間) が操作できことは、`C++` からも操作できます。

ただ、残念なことにこれらのドキュメントは整備されていません。
しかし、多くの場合 `VBA` のドキュメントが参考になります。メソッドの引数などは `VBA` のものと同じです。

メソッドの名前が分からないときは、エクセルでマクロの記録を開始して実際に操作すると、どのようなメソッドを使うべきかわかります。

また、`VBA` の解説をしたサイトは多くあるので、そこに載っているプログラムを参考にすることもできます。

# タイプライブラリから作られるヘッダーファイル
`#import "～"` でタイプライブラリを読込むと、`EXCEL.tlh` と `EXCEL.tli` と言うファイルが自動生成されるのですが、これらのファイルが、エクセルを操作する際に使用するクラスの定義と実装になっていますので、一度中身を見ておくといいです。

メソッド名やプロパティ名からおおよその機能が想像できます。引数などが分からなければ、`VBA` のドキュメントを参照すればいいです。

# 開発環境および実行環境
- Windows10
- Visual Studio 2019 Community
- Excel 2016