Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年02月01日] [更新 2020年07月28日] Delphi サンプルプログラム集
400_CreateOleObject によるエクセル操作の基本例
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro + Excel 2010
400_Excel_CreateObject.zip [2,246 KB] 2020年07月28日版 (EXE 同梱)



  • 2010年06月13日
  • CreateOLEObject 関係の記事追加と動作確認環境の変更、コード修正
  • 2014年01月12日
  • プライマリモニタに表示するようにした
  • 2014年01月22日
  • エクセル操作のテスト用基本コードのサンプルを追加
  • 履歴の一部を削除
  • 2014年06月13日
  • 終了時の処理コードを変更 (エクセル側でブックを閉じてしまった時への対応策の追加)
  • 他のアプリの操作に関する注意の記事を追加




CreaateOleObject で生成したエクセルのオブジェクトの操作


CreateOleObject で生成したエクセルのオブジェクトを、OleVariant 型で操作する例です。
OleVariant 型でオブジェクトを扱う場合、コード補完機能は使用できません。

単にエクセルというアプリケーションを起動するだけであれば、本ページのようなコードは必要ありません。[485_アプリの起動とファイルを開く関数] の記事あるいは、その記事中のリンクを参考にしてください。


[備考 1]
エクセルに限らず、CreatOleObject で生成した COM オブジェクトのインスタンスは、 1 つしか存在できません。複数のエクセルのスンスタンスが必要な場合は、クラスのインスタンスを生成して利用します。エクセルの場合は、TExcelApplication を使用します。TButton 等と同じように、クラスのインスタンスは複数生成することができます。 複数のブックを扱う場合、複数のエクセルのインスタンスを生成しなくても、1 つのエクセルのインスタンスで、複数のブックのオブジェクトが扱えます。

[備考 2]
ネット上に、タイプライブラリをインポート (タイプライブラリの取り込み) して作成したユニットを使用するサンプルがありますが、Delphi の professional エディション以上であれば、その操作は必要ありません。Excel2000, ExcelXP, Excel2010 等のユニットには、タイプライブラリの取り込みで作成したユニットと同じ Excel 操作用のコードが含まれています。どのようなユニットがあるかは以下の記事で確認してください。
それらのユニットが実装されていない Delphi ではタイプライブラリの取り込みでユニットファイルを作成する必要があります。Delphi 6 等の Personal エディションには、それらのユニットが実装されていません。




01_操作サンプル


Delphi の IDE で本サンプルプログラムをコンパイルして実行すると、GetActiveOleObject 関数は例外を発生して失敗します。作成した EXE を直接実行してください。
Delphi の IDE でコンパイルして実行した場合、通常は、管理者権限での実行となります。本サンプルを管理者権限で実行すると、GetActiveOleObject が例外を発生します。


[備考 1]
GetActiveOleObject 関数が例外を発生するのは、Windows Vista 以降のユーザーインタフェイス特権の分離 (UIPI) 機能によるものです。本サンプルを管理者権限で実行した場合、エクセルも管理者権限で実行すれば、エクセルからのメッセージを受信できるようになり、GetActiveOleObject 関数は例外を発生しません。起動中のエクセルの存在を検出できます。


[備考 2]
エクセルを管理者権限で実行するには、エクセルの [プロパティ] ダイアログで、[互換性] タブの [特権レベル 管理者としてこのプログラムを実行する] をチェックします。
ただし、テストだけにしておいた方が無難です。管理者権限での実行のままにしておくと、エクセルの機能の一部が正常に動作しなる場合があります。


[備考 3]
エクセルが起動しているか、エクセルのブックが開いているかは、以下の記事のような方法でも確認することができます。



このサンプルのプロジェクトソースは次のようになっています。フォームが最前面に表示されない場合は、MainFormOnTaskbar の値を False にしてみてください。筆者の Windows XP の環境でコンパイルした場合、True のままだと fsStayOnTop が無効になってしまいました。


図1
設計時の画面

リスト1
プロジェクトのソースコード
unit Unit1;

interface

uses
  //テストで定数定義変数を使用する場合は、Office2000を追加する
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Excel2000, OleServer;

type
  TForm1 = class(TForm)
    GroupBox1: TGroupBox;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    Button9: TButton;
    Button10: TButton;
    Button11: TButton;
    OpenDialog1: TOpenDialog;
    Button12: TButton;
    Button13: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure Button8Click(Sender: TObject);
    procedure Button9Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button10Click(Sender: TObject);
    procedure Button11Click(Sender: TObject);
    procedure Button12Click(Sender: TObject);
    procedure Button13Click(Sender: TObject);
  private
    { Private 宣言 }
     FExcelObj  : OleVariant;
     FWorkBook  : OleVariant;
     FWorkSheet : OleVariant;
     procedure ClearActiveSheetCells;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  エクセルに隠れないようにこのFormを常に最前面の右上に表示するようにする
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FormStyle := fsStayOnTop;
  Self.Left := Screen.PrimaryMonitor.Width - Self.Width;
  Self.Top  := Screen.PrimaryMonitor.WorkareaRect.Top;
end;

//=============================================================================
//  フォームを閉じる時に念のため unAssignedしておく
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if not VarIsEmpty(FExcelObj) then begin
    FWorkBook  := unAssigned;
    FWorkSheet := unAssigned;
    FExcelObj  := unAssigned;
  end;
end;

//=============================================================================
//  新規にブックを作成し表示する
//  表示した Exel のセルに値をセット
//  もしこのプログラムで制御しているエクセルがあれば何もしない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  if VarIsEmpty(FExcelObj) then begin
    // Excel のオブジェクトを作成(特定の警告やメッセージを非表示)
    FExcelObj := CreateOleObject('Excel.Application');
    FExcelObj.DisplayAlerts := False;

    // ブックを作成
    FWorkBook := FExcelObj.Workbooks.Add;

    // シートオブジェクトを定義
    FWorkSheet := FWorkBook.WorkSheets[1];

    // 表示
    FExcelObj.Visible := True;

    // 最大化で表示
    // 後でエクセルを単独起動した時最大化表示となってしまうので注意
    FExcelObj.WindowState := xlMaximized;
  end;
end;

//=============================================================================
//  [ファイルを開く]ダイアログを表示して既存のブックを開く
//  もしこのプログラムで操作しているブックがあれば閉じる
//  また DisplayAlerts =T rue にしている時は、保存の確認ダイアログが表示されるので
//  SetWindowPos でエクセルを前面に表示してダイアログが隠れない様にする
//  表示したら Excel のセルに値をセット
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LVer      : string;
  LCaption : string;
  LWnd     : HWND;
begin
  if VarIsEmpty(FExcelObj) then begin
    FExcelObj := CreateOleObject('Excel.Application');
  end;

  with OpenDialog1 do begin
    //初期フォルダ
    InitialDir := ExtractFileDir(Application.ExeName);

    LVer := FExcelObj.Version;
    if StrToFloatDef(LVer, 0.0) > 11.9 then begin
      Filter := 'エクセルブック (*.xls;*.xlsx)|*.xls;*.xlsx';
    end else begin
      Filter := 'エクセルブック (*.xls)|*.xls';
    end;
  end;

  if OpenDialog1.Execute then begin
    if not VarIsEmpty(FWorkBook) then begin
      LCaption := FExcelObj.Caption;
      LWnd     := FindWindow(nil, PChar(LCaption));
      SetWindowPos(LWnd,
                   HWND_TOP,
                   0,
                   0,
                   0,
                   0,
                   SWP_NOMOVE or SWP_NOSIZE);
      FWorkBook.Close;
    end;

    // ブックを開く
    FWorkBook := FExcelObj.Workbooks.Open(OpenDialog1.FileName);
    // シートオブジェクトを定義
    FWorkSheet := FWorkBook.WorkSheets[1];
    // 表示
    FExcelObj.Visible                  := True;
    FExcelObj.ActiveWindow.WindowState := xlMaximized;
    FWorkSheet.Cells[2,2].Value        := 'ABC';
  end;
end;

//=============================================================================
//  既に誰かが表示したエクセルを操作する
//  表示したらこのフォームの左側いっぱいに表示
//  WorkRect はタスクバーを除いたモニタのワークエリア
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LxlsHandle : HWND;
  LxlsWidth  : Integer;
  LxlsHeight : Integer;
  LWorkRect  : TRect;
begin
  // エクセルが起動中かをハンドルの有無で検査
  LxlsHandle := FindWindow('XLMAIN', nil);
  if LxlsHandle = 0 then begin
    MessageBox(Handle, 'エクセルは起動していません', '情報', MB_ICONINFORMATION);
  end else begin

    // 表示位置を調整
    LWorkRect  := Screen.PrimaryMonitor.WorkareaRect;
    LxlsWidth  := LWorkRect.Right - Self.Width;
    LxlsHeight := LWorkRect.Bottom - LWorkRect.Top;
    SetWindowPos(LxlsHandle,
                 HWND_TOP,
                 LWorkRect.Left,
                 LWorkRect.Top,
                 LxlsWidth,
                 LxlsHeight,
                 SWP_SHOWWINDOW);

    // 表示中のExcelのオブジェクトを取得
    // 管理者権限で実行すると失敗する
    FExcelObj := GetActiveOleObject('Excel.Application');

    // 表示中のブックに接続
    FWorkBook := FExcelObj.ActiveWorkbook;

    // シートオブジェクトを定義(ここではSheet3)
    FWorkSheet := FWorkBook.WorkSheets[3];

    // そのシートをアクティブにする
    FWorkSheet.Activate;

    // 表示
    FExcelObj.Visible := True;
  end;
end;

//=============================================================================
//  エクセルは開いたまま終了
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
begin
  Close;
end;

//=============================================================================
//  エクセルも一緒に閉じる
//  このプログラム起動前に表示しているエルクセルは閉じない
//
//  初期化の unAssigned は終了時には不要だが、習慣的に実行した方がよい
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
var
  LBookObj : OleVariant;
  LBookNo  : Integer;
begin
  // エクセルのオブジェクトが生成されているかをチェック
  if not VarIsEmpty(FExcelObj) then begin

    // テスト用なので警告ダイアログを非表示にする
    FExcelObj.Application.DisplayAlerts := False;

    // 開いているブックを保存しないで閉じる
    for LBookNo := FExcelObj.Workbooks.count downto 1 do begin
      LBookObj := FExcelObj.Workbooks[LBookNo];
      LBookObj.Saved := True;
      LBookObj.Close;
    end;

    // FWorkBook オブジェクトが生成されているかをチェック
    if not VarIsEmpty(FWorkBook) then begin
      FWorkBook := unAssigned;

      // FWorkSheet オブジェクトが生成されているかをチェック
      if not VarIsEmpty(FWorkSheet) then begin
        FWorkSheet := unAssigned;
      end;
    end;
    FExcelObj.Quit;
    FExcelObj := unAssigned;
  end;
  Close;
end;

//=============================================================================
//  テスト用に現在アクティブなシートを全てクリアする関数
//  ClearContent を使用すると、書式を残したままセルの値をクリアできる
//=============================================================================
procedure TForm1.ClearActiveSheetCells;
begin
  if VarIsEmpty(FExcelObj) then exit;

  FWorkSheet.Cells.Clear;
end;

//
//    ------  この上のコードはエクセルの起動から終了の基本コード ------
//
//=============================================================================
//  セル A1 に文字列 ABC をセット
//
//  [備考]
//  TExcelApplication, TExcelSheet等のコンポーネントを使用時は、
//  FWorkSheet.Cells.Items[1,1].Value := 'ABC';
//  のように Items が必要
//=============================================================================
procedure TForm1.Button6Click(Sender: TObject);
begin
  if VarIsEmpty(FExcelObj) then exit;

  ClearActiveSheetCells;
  FWorkSheet.Cells[1,1].Value := 'ABC';
  FWorkSheet.Cells[1,1].Interior.ColorIndex := 4;
end;

//=============================================================================
//  行の挿入
//  どこに挿入したかわかるように文字列をセット
//  xl.....で定義されている定数を使用するには Excel2000.pas や Office 000.pas 等が
//  uses に必要(あるいは自分で定義する)
//=============================================================================
procedure TForm1.Button7Click(Sender: TObject);
begin
  if VarIsEmpty(FExcelObj) then exit;

  // シートの内容をクリアしてから
  ClearActiveSheetCells;
  FWorkSheet.Range['A1:A7'].Value := '123';
  FWorkSheet.Range['A7:A7'].Insert(xlShiftDown);
end;

//=============================================================================
//  シートの追加
//  最後尾に追加
//=============================================================================
procedure TForm1.Button8Click(Sender: TObject);
begin
  if VarIsEmpty(FExcelObj) then exit;

  FWorkBook.WorkSheets.Add(EmptyParam,
                          FWorkBook.WorkSheets[FWorkBook.WorkSheets.Count]);
end;

//=============================================================================
//  文字列の検索
//  現在アクティブなシート内の 'AB' を最初だけ検索
//=============================================================================
procedure TForm1.Button9Click(Sender: TObject);
var
  LobjRow  : Variant;
  LStrText : String;
begin
  if VarIsEmpty(FExcelObj) then exit;

  // テスト用の値をセット
  FWorkSheet := FWorkBook.WorkSheets[1];
  FWorkSheet.Activate;
  ClearActiveSheetCells;
  FWorkSheet.Cells[2,3].Value := 'ABC';
  FWorkSheet.Cells[5,3].Value := 'DAB';
  FWorkSheet.Cells[7,3].Value := 'ABC';
  try
    // FWorkSheet.Cells.Find('AB').Row;が通常のコードであるが
    // このような書き方も可能という例
    LobjRow  := FWorkSheet.Cells.Find(what := 'AB', LookIn := xlValues).Row;
    LStrText := VarToStr(LobjRow) + '行目にあります';
    MessageBox(Handle, PChar(LStrText), '情報', MB_ICONINFORMATION);
  except
    MessageBox(Handle, '見つかりません', '情報', MB_ICONINFORMATION);
  end;
end;

//=============================================================================
//  日付型を文字列に変換
//=============================================================================
procedure TForm1.Button10Click(Sender: TObject);
var
  LDateTime : TDateTime;
  LFormat   : String;
begin
  if VarIsEmpty(FExcelObj) then exit;

  // テスト用の値をセット
  FWorkSheet.Cells[1,2].NumberFormatLocal := 'ggee年mm月dd日';
  FWorkSheet.Cells[1,2].Value             := '2005/3/6';

  // セルの値を TDataTime 型の値として取り出す
  LDateTime := VarToDatetime(FWorkSheet.Cells[1, 2]);

  // 表示形式を設定して表示
  LFormat          := 'ggee"年"mm"月"dd"日';
  Button10.Caption := FormatDateTime (LFormat, LDateTime);
end;

//=============================================================================
//  整数値を文字列に変換して取得
//=============================================================================
procedure TForm1.Button11Click(Sender: TObject);
var
  LVar: Variant;
begin
  if VarIsEmpty(FExcelObj) then exit;

  LVar := FWorkSheet.Cells[1, 3];
  Button11.Caption := VarToStr(LVar);
end;

//=============================================================================
//  枠線と枠内罫線
//  uses に Excel2000 が必要(各定数値使用のため)
//  CreateOleObject の場合はwith ... do begin 文は使用不可
//  [Servers] タブの TExcel.... の場合はエクセル操作フォームの例参照
//=============================================================================
procedure TForm1.Button12Click(Sender: TObject);
var
   LRange: Variant;
begin
  if VarIsEmpty(FExcelObj) then exit;

  // 対象範囲
  LRange := FWorkSheet.Range['B5', 'G22'];

  // 枠罫線
  LRange.BorderAround(xlContinuous,xlMedium, 16, EmptyParam);

  // インサイド罫線.縦
  LRange.Borders[xlInsideVertical].LineStyle  := xlContinuous;
  LRange.Borders[xlInsideVertical].Weight     := xlHairline;
  LRange.Borders[xlInsideVertical].ColorIndex := 12;

  // インサイド罫線.横
  LRange.Borders[xlInsideHorizontal].LineStyle  := xlContinuous;
  LRange.Borders[xlInsideHorizontal].Weight     := xlHairline;
  LRange.Borders[xlInsideHorizontal].ColorIndex := 12;
end;

//=============================================================================
//  上下左右個別罫線
//  uses に Excel2000 が必要(各定数値使用のため)
//  CreateOleObject の場合はwith ... do begin 文は使用不可
//  [Servers] タブの TExcel.... の場合はエクセル操作フォームの例参照
//=============================================================================
procedure TForm1.Button13Click(Sender: TObject);
var
  LRange: Variant;
begin
  if VarIsEmpty(FExcelObj) then exit;

  LRange := FWorkSheet.Range['F24', 'G25'];

  //上罫線
  LRange.Borders[xlEdgeTop].LineStyle  := xlContinuous;
  LRange.Borders[xlEdgeTop].Weight     := xlThick;
  LRange.Borders[xlEdgeTop].ColorIndex := 15;

  // 下罫線
  LRange.Borders[xlEdgeBottom].LineStyle  := xlContinuous;
  LRange.Borders[xlEdgeBottom].Weight     := xlThick;
  LRange.Borders[xlEdgeBottom].ColorIndex := 15;

  // 左罫線
  LRange.Borders[xlEdgeLeft].LineStyle  := xlContinuous;
  LRange.Borders[xlEdgeLeft].Weight     := xlThick;
  LRange.Borders[xlEdgeLeft].ColorIndex := 15;

  // 右罫線
  LRange.Borders[xlEdgeRight].LineStyle  := xlContinuous;
  LRange.Borders[xlEdgeRight].Weight     := xlThick;
  LRange.Borders[xlEdgeRight].ColorIndex := 15;

  // セルの結合
  LRange.MergeCells         := True;
  LRange.HorizontalAlignment:= xlCenter;
  LRange.VerticalAlignment  := xlCenter;
  LRange.WrapText           := True;

  // 右下がり斜め罫線
  LRange.Borders[xlDiagonalDown].LineStyle := xlDot;
  LRange.Borders[xlDiagonalDown].Weight    := xlHairline;
  LRange.Borders[xlDiagonalDown].Color     := clGray;

  // 右上がり斜め罫線
  LRange.Borders[xlDiagonalUp].LineStyle := xlDot;
  LRange.Borders[xlDiagonalUp].Weight    := xlHairline;
  LRange.Borders[xlDiagonalUp].Color     := clGray;
end;

end.




02_OleVariant で扱う際の注意 - 文字列としての数字


Delphi は変数の型に厳しい言語です。それが便利でもあり、また面倒と感じることもあるかも知れません。型が明らかな場合には、Delphi は、多くの場合、変数の型変換を自動で行ってくれます。
OleVariant 型の場合も自動で行ってくることもありますが、変数の種類と値によっては、型を明示的にキャスト等しないと、期待通りの結果が得られないことがあります。
以下は、その 1 つの例です。

次のコードは、セルの書式を「文字列」にする例ですが、このコードは '@' という文字のコードである 64 という値が「数値」としてセルに表示されてしまいます。


図2
'@' の文字コードが表示される
  • エクセルのオブジェクトは、基本的に全てバリアント型である
  • Delphi の TExcelApplication 等は、扱いやすいように型を定義している
  • エクセルの場合、書式設定の '@' は、特殊な意味を持つ

リスト2
OleVariant で扱う際の注意
セルの書式を「文字列」にする場合に、失敗するコードの例
unit Unit1;

interface

uses
  // テストで定数定義変数を使用する場合は、Office2000 を追加する
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Excel2000;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
     FExcelObj  : OleVariant;
     FWorkBook  : OleVariant;
     FWorkSheet : OleVariant;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ComObj,
  ActiveX;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FormStyle := fsStayOnTop;

  // エクセルのオブジェクトを作成 (特定の警告やメッセージを非表示)
  FExcelObj := CreateOleObject('Excel.Application');
  FExcelObj.DisplayAlerts := False;

    // ブックを作成
    FWorkBook := FExcelObj.Workbooks.Add;

    // シートオブジェクトを定義
    FWorkSheet := FWorkBook.WorkSheets[1];

  // 表示
  FExcelObj.Visible := True;
end;

//=============================================================================
//  フォームを閉じる時にエクセルも一緒に閉じる
//
//  フォームを閉じる時に念のため unAssigned しておく
//  初期化の unAssigned は終了時には不要だが、習慣的に実行した方がよい
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  // エクセルのオブジェクトが生成されているかをチェック
  if VarIsEmpty(FExcelObj) then Exit;

  // 開いているブックを保存しないで閉じる
  FWorkBook.Saved := True;
  FWorkBook.Close;
  FExcelObj.Quit;

  FWorkSheet := unAssigned;
  FWorkBook  := unAssigned;
  FExcelObj  := unAssigned;
end;

//=============================================================================
//  数値を文字列 (数字) にする例
//  @ 記号の ASCII コードの数値 64 が表示される
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRange : OleVariant;
begin
  LRange := FWorkSheet.Range['A1', 'C10'];
  LRange.NumberFormatLocal := '@';

  // Range ではなく、Cells を使ってみる
  LRange.Cells[1,1].Value := '010';
end;

end.


数字を常に指定桁数で表示したい場合、以下のような書式を使用する方法も考えられます。ただし、この場合、セルの値は文字列ではなく、数値となります。数値なので、デフォルトでの表示は右端寄せとなります。


//=============================================================================
//  OLEObjectで変数等を扱う際の注意
//  NumberFormatLocal でセルの書式を文字列に設定する例
//  NumberFormatLocal に直接、文字列の書式である @ を指定
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRange : OleVariant;
begin
  LRange := FWorkSheet.Range['A1', 'C10'];

  // 表示する桁数分の 0 を指定する (右寄せ表示となる)
  LRange.NumberFormatLocal := '0000';
  // 左寄せで表示したい場合
  // LRange.HorizontalAlignment := xlLeft;
  LRange.Cells[1,1].Value := 10;
end;


セルの書式を「文字列」にするには、例えば以下のコードのように、文字に変換して、書式文字列の文字であることを明示的に認識させるか、OleVariant にキャストしてから代入します。


図3
数字が文字列として扱われる

リスト3
OleVariantで扱う際の注意
セルの書式を「文字列」にする例。変数の型を明示的に指定する
//=============================================================================
//  OLEObjectで変数等を扱う際の注意
//  NumberFormatLocal でセルの書式を文字列に設定する例
//  NumberFormatLocal に VarToStr で文字列の書式である @ を指定
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRange : OleVariant;
begin
  LRange := FWorkSheet.Range['A1', 'C10'];
  LRange.NumberFormatLocal := VarToStr('@');

  //Rangeではなく、Cellsを使ってみる
  LRange.Cells[1,1].Value := '010';
end;

//=============================================================================
//  OLEObjectで変数等を扱う際の注意
//  セルの書式を文字列に設定する例
//  NumberFormatLocal に OleVariant で文字列の書式である @ を指定
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LRange : OleVariant;
begin
  LRange := FWorkSheet.Range['A1', 'C10'];
  LRange.NumberFormatLocal := OleVariant('@');

  // Rangeではなく、Cells を使ってみる
  LRange.Cells[1,1].Value := '010';
end;

//=============================================================================
//  OLEObjectで変数等を扱う際の注意
//  セルの書式を文字列に設定する例
//  NumberFormatLocal に OleVariant 型の変数の文字 @ を指定
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LRange : OleVariant;
  LType  : OleVariant;
begin
  LRange := FWorkSheet.Range['A1', 'C10'];
  LType  := '@';
  LRange.NumberFormatLocal := LType;

  // Rangeではなく、Cells を使ってみる
  LRange.Cells[1,1].Value := '010';
end;


OleVariant 型の変数は、次のように TVariantArg や TVarData で、変数の型を指定して扱うのが正統法と言えます。
ただし、このコードを実行しても、セルに '010' という文字列は表示できません。これはエクセルのセル側の書式の問題だからです。先行する 0 を表示するような書式を指定するには、上のサンプルのように NumberFormatLocal を使用します。
Delphi では、下のコードのようなバリアント型を指定しなくても自動認識してくれますが、特殊な型の変数を扱う場合は、TVariantArg や TVarData 型を使用した処理が必要な場合があります。

COM インターフェイスでは、文字列として WideString や PWideString 型を使用します。
このコードで String 型を使用すると、例外が発生します。



//=============================================================================
//  OleVariant 型の変数に値を設定する際の最も正統的と思われる方法
//  TrVaiantArg を使用する例
//  TVariantArg 型や TVarData を使用する場合は、WideString 型を使用する

//  TVariantArg, VT_XXX 等の定数名の使用には、uses に ActiveX が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRange   : OleVariant;
  LValue   : OleVariant;
  LCellVal : WideString;
begin
  LRange := FWorkSheet.Range['A1', 'C10'];

  LCellVal := '010';
  TVariantArg(LValue).vt      := VT_BSTR;
  TVariantArg(LValue).bstrVal := PWideChar(LCellVal);

  LRange.Cells[1,1].Value := LValue;
end;

//=============================================================================
//  OleVariant 型の変数に値を設定する際の最も正統的と思われる方法
//  TVarData を使用する例
//  TVariantArg 型や TVarData を使用する場合は、WideString 型を使用する

//  TVariantArg, VT_XXX 等の定数名の使用には、uses に ActiveX が必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LRange   : OleVariant;
  LValue   : OleVariant;
  LCellVal : WideString;
begin
  LRange := FWorkSheet.Range['A1', 'C10'];

  LCellVal := '010';
  TVarData(LValue).VType   := varOleStr;
  TVarData(LValue).VOleStr := PWideChar(LCellVal);

  LRange.Cells[1,1].Value := LValue;
end;




03_OleVariant で扱う際の注意 - 整数型の定数値


上の場合と同じように、型に関する例です。
Excel を操作するコードでは、ExcelXXXX.pas 内で定義している定数名をよく使用します。多くの場合は、これらの定数は 16 進数で定義されていますから、Cardinal 型となります。一方、エクセル等のプロパティの値には負の値を持つものがあります。

例えば、下のコードはセルの書式設定の例ですが、定数 xlHAlignCenter の値は -4108 です。この定数名で実行すると、下図のように、「Range クラスの XXXXXXXX プロパティを設定できません」というエラーが発生する場合があります。本来ならば、プロパティ HorizontalAlignment の設定用関数で符号付きの整数値に変換するか、エクセルの方で解釈するはずですが、そうしてくれない場合があるようです。
そこで、下のコードでは、このプロパティに定数値を代入する際に、明示的に符号付きの整数型にキャストして実行しています。
この現象は、TExcelApplication コンポーネントを使用する際には、少なくても筆者の現在の環境では発生していません。


図4
プロパティが設定できない

リスト4
OleVariant で扱う際の注意
整数型のプロパティの値を、定数名で設定する場合
unit Unit1;

interface

uses
  //テストで定数定義変数を使用する場合は、Office2000を追加する
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComObj, Excel2000;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
     FExcelObj  : OleVariant;
     FWorkBook  : OleVariant;
     FWorkSheet : OleVariant;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
//エクセルのオブジェクトが生成されているかをチェック
  if VarIsEmpty(FExcelObj) then Exit;

  FWorkBook  := unAssigned;
  FWorkSheet := unAssigned;
  FExcelObj.Quit;
  FExcelObj  := unAssigned;
end;

//=============================================================================
//  新規にブックを作成し表示する
//  HorizontalAlignment プロパティの設定例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRange : OleVariant;
begin
  // Excel のオブジェクトを作成 (特定の警告やメッセージを非表示)
  FExcelObj := CreateOleObject('Excel.Application');
  FExcelObj.DisplayAlerts := False;
  // ブックを作成
  FWorkBook := FExcelObj.Workbooks.Add;
  // シートオブジェクトを定義
  FWorkSheet := FWorkBook.WorkSheets[1];
  //表示
  FExcelObj.Visible := True;

  // 1 行目から 100 行目までの 2 列目のアライメントを設定
  // 行と例の番号は 1 ベースの値
  LRange := FWorkSheet.Range[FWorkSheet.Cells[1, 2], FWorkSheet.Cells[100, 2]];
  LRange.HorizontalAlignment := Integer(xlHAlignCenter);
end;

end.


あるいは、上の数字の時と同じように OleVariant でキャストし、OleVariant 型であることを明確にしても動作するようです。元々 OleVariant で扱うのですから、こちらの方がいいかも知れません。


  LRange := FWorkSheet.Range[FWorkSheet.Cells[1, 2], FWorkSheet.Cells[100, 2]];
  LRange.HorizontalAlignment := OleVariant(xlHAlignCenter);




04_VBA ( マクロ ) のコードを参考にする


このサンプルプログラムは操作例が少ないので、必要であれば、ML や掲示板などで調べて下さい。掲示板などには結構操作例のコードがあります。あるいは、 で説明している Excel VBA のヘルプや VBA のコードを参考にして作成します。VBA のコードは、エクセル上で該当する操作をマクロに記録して、それを Delphi のコードに書換えます。また、記録したマクロをそのまま実行する方法もあります。

マクロの機能を利用するには、[開発] タブを表示するようにしておくと便利です。以下の説明図は、Excel 2010 の場合です。2013年05月19日までは Excel 2007 を使用した説明でしたが、変更しました。設定項目の表示方法が違っていますので注意してください。


図5
[開発] タブを表示
  • [Excel のオプション] の [リボンのユーザ設定] で [開発] にチェック
図6
マクロの記録開始
図7
マクロの設定ダイアログ
  • 名前を付けられる
図8
記録終了
図9
記録終了したらマクロを表示
図10
マクロの編集画面
  • コードを見ながらまたはコードをコピーして Delphi で編集




05_CreateOLEObject 関数で生成可能なオブジェクト


このページのサンプルでは、エクセルのオブジェクトを CreateOLEObject で生成しています。これはマイクロソフトの他の言語では CreateObject に相当しますが、この引数に 'Excel.Application' を指定しています。

この CreateOLEObject の引数は、Delphi のヘルプでは [ClassName は、クラス識別子 (CLSID) の文字列表現を指定します]となっています。この CLSID の文字列表現が [ProgID] で、プログラム ID と呼ばれている文字列です。本サンプルでは 'Excel.Application' です。他のサンプルやネット上のサンプルコードを見ると分かるように、この ProgID はアプリケーションそのものだけではなく、例えばエクセルのシート等も含まれます。

ProgID に指定可能な値は、OLE/COM Object Viewer で確認することができます。これは [タイプライブラリ取り込みの他の方法 ] で説明している Microsoft Windows SDKs をインストールすると使用できるようになります。

VersionIndependentProgID の値が定義されているオブジェクトが、CreateOLEObect, CreateCOMObject 関数で生成可能なオブジェクトです。



オブジェクトビューアは次の場所にあります。

//Windows SDK 7.1 + 32 ビット版 Windows
C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\OleView.exe

//Windows SDK 8.1 + 64 ビット版 Windows (32 ビット版 OleView) 
C:\Program Files (x86)\Windows Kits\8.1\bin\x86\oleview.exe

//Windows SDK 8.1 + 64 ビット版 Windows (64 ビット版 OleView) 
C:\Program Files (x86)\Windows Kits\8.1\bin\x64\oleview.exe


図11
OLE/COM Object Viewer
  • Microsoft SDKs 7.1 の場合
  • Windows SDK 8.1 は [スタート] メニューに該当する項目はない

  • 図12
  • [Object Classes] [All Ojbects] の [Microsoft Office Excel Application]

  • 図13
  • [Type Libraries] の Excel の [PictureFormat] 項目
  • どんなプロパティやメソッド類があるかを確認できる




06_Visual Basic Editor のオブジェクトブラウザ


上の図のような内容は、以下の図の説明のように、VB の Editor でも見ることができます。ここではエクセル 2010 での説明ですが、ワードでも同じです。
Visual Basic あるいは VBA では、この [参照設定] で、操作したいオブジェクトを追加すると、そのオブジェクトがコード内で使用可能となります。


  • 図14
  • [開発] タブから [Visual Bascic] をクリックして [Visual Basic Editor] を起動

  • 図15
  • [表示] から [オブジェクトブラウザ] を起動

  • 図16
  • 右クリックのメニューから [参照設定] を選択

  • 図17
  • 例えば、本サイトのサンプルプログラム集でも使用している ADOX をリストから選択
  • チェックして [OK] をクリック
  • ダイアログの下には、このライブラリが使用する DLL の名前と場所が表示されている

  • 図18
  • 上のコンボボックスで[ADOX]を選択
  • 左ペインに ADOX のオブジェクト (クラス) リスト。右ペインにメソッドやプロパティ
  • Create を選択すると下にそのメソッドの引数と簡単な説明




07_COM オブジェクトと他のアプリの操作


前項で述べた VBA の [参照設定] は、Delphi で言えばタイプライブラリの取り込みや ActiveX コントロールの取り込みに相当します。
Delphi の [コンポーネントのインポート] で表示されるリストは、前項の VBA では [参照可能なライブラリ ファイル] です、これらのファイルは DLL または TLB (タイプライブラリ) ファイルです。

一般に、他のアプリケーションのプロパティや関数類は、別のアプリからは操作できません。COM はこれらを他のプログラム言語から操作可能にするための仕様です。アプリケーションを外部のアプリケーションから操作可能にする仕組みを一般に COM (コム) と言います。
COM はアプリケーション間通信の手段を提供しているとも言えます。

COM を実装したアプリケーションは、CreateOleObject, CreateComObject やタイプライブラリの取り込みでインポートしたファイルを使用して、そのアプリケーションを操作できるようになります。
COM を実装していても、どのような操作が可能かは、そのアプリケーションがどのような COM オブジェクトを、どのようなプロパティやメソッド類を公開しているかによります。
エクセルのブック、シートは COM オブジェクトであり、エクセル自体も COM オブジェクトということになります。また、エクセルは COM を実装した COM アプリということにもなります。

Delphi で作成するアプリケーションにも COM は実装できます。その際、どのような機能、プロパティやメソッドを公開するかは、そのアプリケーションの開発者によります。

COM を実装していないアプリケーションを操作するには、ウィンドウハンドルを取得して、ある程度は操作することが可能です。また、IAccessible インターフェイスや UI Automation 関係インターフェイスを使用する方法もあります。
文字列 (テキスト) だけは特別で、専用のメッセージ類があります。



[備考]
COM オブジェクト (OLE オートメーション、ActiveX コントロール) をオブジェクトのクラス名あるいは、クラス名を識別する CLSID 文字列から生成するには CreateOleObject を使用します。
一方、オブジェクトの GUID 値から COM オブジェクトを生成する時は CreateComObject 関数を使用します。全ての COM オブジェクトが CreateOleObject と CreateComObject のどちらでも生成できるわけではありません。それは、COM オブジェクトを提供する開発者がどのインターフェイスから生成するようにしているかによります。





08_CLSID と ProgID


CLSID の値と ProgID は ComObj.pas 内の関数類を使用して相互変換できます。そのテスト用プログラムです。CLSID または ProgID の値を取得して表示します。


図19
設計時画面
図20
Button1 クリック
図21
Button2 クリック
  • これが CLSID の文字列表現で、ProgID と呼ばれる値
  • 末尾の 14 は、エクセルのバージョン

この上の図の結果の末尾の 14 は、エクセルのバージョンです。14 は Excel 2010 です。
CreateOleObject の引数として使用する場合、システムにインストールされていないバージョンの値を指定するとエラーとなり、エクセルは起動できません。通常は、このバージョン番号をしないで使用します。指定しても、指定しなくても、現在システムで有効なバージョンが起動します。

システムに複数のバージョンのエクセルがインストールされていても、CLSID の値は全て同じです。つまり、バージョンを指定して、エクセルのオブジェクトを生成することはできません。
本ページの最初のサンプルのように、起動中のエクセルのバージョンとは関係なく、起動中のエクセルに接続することはできます。



リスト5
CLSID と ProgID の相互変換
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj;

{$R *.dfm}

//=============================================================================
//  プログラムIDからCLSIDを取得
//  ProgIDToClassID, GUIDToString の使用には uses にComObj が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LGUID : TGUID;
  LStr  : string;
begin
  LStr  := 'Excel.Application';
  LGUID := ProgIDToClassID(LStr);

  // 登録されていれば CLSID が表示される
  ShowMessage(GUIDToString(LGUID));
end;

//=============================================================================
//  CLSID からプログラム ID を取得
//  ClassIDid-dategID の使用には usesに ComObj が必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LGUID  : TGUID;
  LStr   : string;
begin
  LStr   := '{00024500-0000-0000-C000-000000000046}';
  LGUID  := StringToGUID(LStr);

  // 登録されていれば ProgID が表示される
  ShowMessage(ClassIDid-dategID(LGUID));
end;

end.




09_エクセル操作のテスト用基本コード


最初のサンプルは、いろいろなテスト用のコードがあり長くなっています。次のコードは、ほとんどエクセルの起動と終了だけにしたサンプルです。
起動時にエクセルのオブジェクトを生成し、ブックのオブジェクトも生成します。各種テスト用プロジェクトとして利用できます。


リスト6
エクセル操作のテスト用基本コード
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
     FExcelObj   : OleVariant;
     FWorkBook   : OleVariant;
     FWorkSheet  : OleVariant;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ComObj,
  Excel2000,
  Office2000;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//
//  (1) エクセルのオブジェクトを生成
//  (2) ブックオブジェクトを作成
//  (3) シートオブジェクトを取得
//  (4) ブックを表示
//
//  FExcelObj, FWorkBook, FWorkSheet は全て OleVariant 型の変数
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  FExcelObj  := CreateOleObject('Excel.Application');
  FWorkBook  := FExcelObj.Workbooks.Add;
  FWorkSheet := FWorkBook.WorkSheets[1];

  FExcelObj.Visible := True;
end;

//=============================================================================
//  フォームを閉じる時の処理
//  エクセルも閉じる。保存はしない
//
//  初期化の unAssigned は終了時には不要だが、習慣的に実行した方がよい
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  LBookObj : OleVariant;
  LBookNo  : Integer;
begin
  // エクセルのオブジェクトが生成されているかをチェック
  if not VarIsEmpty(FExcelObj) then begin

    // テスト用なので警告ダイアログを非表示にする
    FExcelObj.Application.DisplayAlerts := False;

    // 開いているブックを保存しないで閉じる
    for LBookNo := FExcelObj.Workbooks.count downto 1 do begin
      LBookObj := FExcelObj.Workbooks[LBookNo];
      LBookObj.Saved := True;
      LBookObj.Close;
    end;

    // ワークブックオブジェクトが生成されているかをチェック
    if not VarIsEmpty(FWorkBook) then begin
      FWorkBook := unAssigned;

      // ワークシートオブジェクトが生成されているかをチェック
      if not VarIsEmpty(FWorkSheet) then begin
        FWorkSheet := unAssigned;
      end;
    end;
    FExcelObj.Quit;
    FExcelObj := unAssigned;
  end;
end;

//=============================================================================
//  エクセルの操作例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  FWorkSheet.Cells[1,1].Value := 'ABC';
  FWorkSheet.Cells[1,1].Interior.ColorIndex := 4;
end;

end.




10_エクセル操作のテスト用基本コード ( 起動時に指定ブックを開く )


最初のサンプルは、いろいろなテスト用のコードがあり長くなっています。次のコードは、ほとんどエクセルの起動と終了だけにしたサンプルです。
起動時にエクセルのオブジェクトを生成し、指定したブックを開くようにしています。各種テスト用プロジェクトとして利用できます。


リスト7
エクセル操作のテスト用基本コード (起動時に指定ブックを開く)
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormShow(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
     FExcelObj   : OleVariant;
     FWorkBook   : OleVariant;
     FWorkSheet  : OleVariant;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ComObj,
  Excel2000,
  Office2000;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//
//  (1) エクセルのオブジェクトを生成
//  (2) 指定のブックを開いてブックオブジェクトを取得
//  (3) シートオブジェクトを取得
//  (4) ブックを表示
//
//  FExcelObj, FWorkBook, FWorkSheet は全て OleVariant 型の変数
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
var
  LBookFilePath : String;
begin
  LBookFilePath := ExtractFilePath(Application.ExeName) + 'Test.xls';

  FExcelObj  := CreateOleObject('Excel.Application');
  FWorkBook  := FExcelObj.Workbooks.Open(LBookFilePath);
  FWorkSheet := FWorkBook.WorkSheets[1];

  FExcelObj.Visible := True;
end;

//=============================================================================
//  フォームを閉じる時の処理
//  エクセルも閉じる。保存はしない
//
//  初期化の unAssigned は終了時には不要だが、習慣的に実行した方がよい
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  LBookObj : OleVariant;
  LBookNo  : Integer;
begin
  // エクセルのオブジェクトが生成されているかをチェック
  if not VarIsEmpty(FExcelObj) then begin

    // テスト用なので警告ダイアログを非表示にする
    FExcelObj.Application.DisplayAlerts := False;

    // 開いているブックを保存しないで閉じる
    for LBookNo := FExcelObj.Workbooks.count downto 1 do begin
      LBookObj := FExcelObj.Workbooks[LBookNo];
      LBookObj.Saved := True;
      LBookObj.Close;
    end;

    // ワークブックオブジェクトが生成されているかをチェック
    if not VarIsEmpty(FWorkBook) then begin
      FWorkBook := unAssigned;

      // ワークシートオブジェクトが生成されているかをチェック
      if not VarIsEmpty(FWorkSheet) then begin
        FWorkSheet := unAssigned;
      end;
    end;
    FExcelObj.Quit;
    FExcelObj := unAssigned;
  end;
end;

//=============================================================================
//  エクセルの操作例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  FWorkSheet.Cells[1,1].Value := 'ABC';
  FWorkSheet.Cells[1,1].Interior.ColorIndex := 4;
end;

end.




11_エクセル等、他のアプリの操作に関する注意


一般的に、他のアプリケーションの操作は簡単ではありません。
エクセルの場合、Delphi 側で起動したブックを、エクセル側の操作で閉じてしまったり、操作対象のシートを削除してしまうと、そのブックやシートのオブジェクトに対しての操作は例外が発生します。下図はその例です。

エクセル側の操作を検出できれば、対策処理が可能ですが、一般的に、他のアプリの操作を検出するのは困難です。IAccessible インターフェイスや UI Automation 関係インターフェイスを使用すればある程度の検出が可能ですが、かなり面倒です。try - except の例外で処理してしまった方が簡単かも知れません。

対象の他のアプリを必要な時に起動し、操作を終了したらできるだけ早い段階で閉じてしまうのが確実な方法です。その時、各オブジェクトに unAssigned を代入しておけば、VarIsEmpty 関数で、そのオブジェクトが生成されているかを判定できます。
VarIsEmpty 関数は、プログラム内で生成したインスタンスを判定するものです。外部からブック等を閉じると、ブックの実体がなくなり、インスタンスが無効となってしまいます。したがって、それらのオブジェクトに対する操作を行うと、例外が発生することになります。

Windows Vista では、他のアプリへのメッセージ送信、他のアプリからのメッセージの受信等、他のアプリの操作関係の制限が強くなっています。



図22
バリアント演算のエラー
  • オブジェクトが生成されていない状態で、そのオブジェクトの操作コードを実行した場合等に発生する
図23
OLE エラー
  • 操作対象のオブジェクトが失われてしまった場合等に発生する




12_エクセルのバージョンを指定してブックを開く


エクセルのバージョンを指定してブックを開くことはできません。
これは、CreateOleObject でも、TExcelApplication でエクセルを操作する場合でも同じです。起動できるのは、カレントバージョンのエクセルです。

システムに複数のバージョンのエクセルがインストールされている場合、カレントバージョンを変更すれば、ブックを開く時にそのバージョンのエクセルで開くことができます。しかし、カレントバージョンの変更は、インストール作業が行われます。そのため、操作には時間がかかります。

見かけ上、指定したバージョンでブックを開くことは可能です。レジストリから該当するバージョンの実行ファイルのパス名を取得します。そして、ShellExecute 関数等で起動します。起動したら、起動中のエクセルに接続すれば、そのブックの操作が可能になります。
実際のディスク上のバス名は、環境によって違いがある可能性があります。実行ファイルのパス名は、レジストリから取得するのが確実です。