VCLからExcelにアクセス(C++Builder版)
第9回デベロッパーキャンプ セッションA2でネタになった、VCLを使ってExcelにアクセスするサンプルのC++Builder版を作ってみた。
オリジナルと違うのは読み込み元のデータベースをdbExpressでBlackfish SQLに接続したこと。
それに伴って、ソースコードに一部変更あり。
読み込み元のデータベースの作成から。
- データエクスプローラで[BLACKFISHSQL]を選択して、[新規接続の追加]を選択。接続名を"BSQL_EMP"にして保存。
- [BSQL_EMP]上で右クリックして[接続の変更]を選択。ダイアログが表示されるので、以下の項目を入力。
- [テスト接続]をクリックして接続出来ているかどうか確認して、[OK]をクリック。
基本的にはDelphiとあまり変わりがないのだけど、以下に注意。
- Delphiでの"ExcelRange"といった型名は、C++Builder版では"ExcelRangePtr"という感じで"Ptr"が付く。もちろん、メソッドやプロパティへのアクセスはアロー演算子で。
- プロパティへのアクセスは出来ない場合が多いので、その場合はアクセサ経由でアクセスする。(NumberFormat→get_NumberFormat/set_NumberFormat)
- メソッドに値を渡す場合は原則としてVariant型。TVariantやOleVariantでもOK。当然、文字列等渡す場合はキャストしてやること。
- xlHAlignCenterといった定数はクラス内のenumとなっているので、XlHAlign::xlHAlignCenterのようにクラス名を付ける。定数の定義はExcel_2K.hに記述されている。
procedure TForm1.SetHeaderFormatClick(Sender: TObject); var col_count: Integer; rs: string; er: ExcelRange; begin col_count := Table1.FieldDefs.Count; rs := Format('%s%d:%s%d', [GetColTitle(1), header_count, GetColTitle (col_count), header_count]); er := ExcelWorksheet1.Range[rs, EmptyParam]; er.NumberFormat := 'G/標準'; // 標準 er.HorizontalAlignment := xlHAlignCenter; // 中央 er.Font.Bold := True; // 太字 er.Font.Italic := True; // 斜体 end;
C++Builderではこんな感じになる。
void __fastcall TForm1::SetHeaderFormatClick(TObject *Sender) { int col_count = Table1->FieldDefs->Count; Variant rs1 = Address(header_count, 1); Variant rs2 = Address(header_count, col_count); ExcelRangePtr er = ExcelWorksheet1->Range[rs1][rs2]; er->set_NumberFormat(Variant("G/標準")); // 標準 er->set_HorizontalAlignment(Variant(XlHAlign::xlHAlignCenter)); // 中央 FontPtr f = er->get_Font(); f->set_Bold(Variant(true)); // 太字 f->set_Italic(Variant(true)); // 斜体 }
オリジナルはセルのアドレスは直接生成しているけど、C++Builder版ではAddressというユーティリティー関数で行・列からセルのアドレスを生成している。
String __fastcall TForm1::GetColTitle(int idx) { String t; TCHAR ch; if (idx <= 26) { ch = 'A' + idx - 1; t = ch; } else { ch = 'A' + (idx - 1) / 26 - 1; t = ch; ch = 'A' + (idx - 1) % 26; t += ch; } return t; } String __fastcall TForm1::Address(int Row, int Col) { String a = Format("%s%d", OPENARRAY(TVarRec, (GetColTitle(Col), Row))); return a; }
もう一つの違いはデータのアクセス。一つ一つセルに埋めていく「低速版」。
void __fastcall TForm1::SetBodyValuesSlow() { int row_count = 0; ExcelRangePtr er; Variant rs; Variant v; TField* Field; Table1->First(); while (Table1->Eof == false) { ++row_count; for (int i = 0; i < Table1->FieldDefs->Count; ++i) { rs = Address(header_count+row_count, i + 1); er = ExcelWorksheet1->Range[rs]; Field = Table1->FieldByName(Table1->FieldDefs->Items[i]->Name); if (i == 4) { v = Field->AsDateTime; } else if (i == 9) { v = Field->AsCurrency; } else { v = Field->Value; } er->set_Value(v); } Table1->Next(); } }
TIMESTAMP型とDECIMAL型がVariantにうまく変換できないので、AsDateTimeとAsCurrencyで取得している。
続いて、二次元Variant配列を使った「高速版」。
void __fastcall TForm1::SetBodyValuesFast() { // 2次元のVariant配列を作成 int b[] = {0, Table1->RecordCount - 1, 0, Table1->FieldDefs->Count - 1}; Variant ar = VarArrayCreate(b, 3, varVariant); Table1->First(); int row_count = 0; int idx[2]; Variant fv; TField* Field; while (Table1->Eof == false) { for (int i = 0; i < Table1->FieldDefs->Count; ++i) { Field = Table1->FieldByName(Table1->FieldDefs->Items[i]->Name); if (i == 4) { fv = Field->AsDateTime; } else if (i == 9) { fv = Field->AsCurrency; } else { fv = Field->Value; } // Variant配列にフィールドの値をセット ar.PutElement(fv, row_count, i); } Table1->Next(); ++row_count; } // エクセルの広い範囲に一気にVariant配列を書き込む!! Variant rs = Address(header_count+1, 1, header_count+row_count, Table1->FieldDefs->Count); ExcelRangePtr er = ExcelWorksheet1->Range[rs]; er->set_Value2(ar); }
二次元Variant配列の生成とアクセスが大きく変わっている。あと、なぜかSALARYフィールドの書式が反映されない。
さすがに全ソースコードは晒せないので、プロジェクト一式はここから。
プロジェクトを作り直して、Unit1.cpp/Unit1.hpp/Unit1.dfmを再指定すればC++Builder 2009でもいけるかも。