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



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




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


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

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


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




01_操作サンプル


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


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


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


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



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


リスト1
プロジェクトのソースコード
program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  //Window XPの場合TrueにするとfsStayOnTopが無効になってしまう
  //Windows 7でコンパイルした場合はTrueでOK
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.


以下がサンプルの実行例と実際の操作コード例です.
このような,他のアプリケーションを起動して,それを操作するアプリケーション (プログラム) の動作仕様を決定するのは簡単ではありません.
  • 操作対象のアプリが起動していなければ起動するのか,起動中のアプリだけを対象とするのか
  • 操作対象のアプリがすでに起動していた場合,別に新規に起動するのか,起動中のアプリを操作対象にするのか
  • 終了時に対象のアプリを起動したままにするのか,対象のアプリも終了させるのか
  • 対象アプリも終了させる場合,複数起動していた場合には,自アプリから起動したアプリだけを終了させるのか,それとも全てを終了させるのか
  • 対象のアプリが,操作側の自アプリ以外で終了した場合はどうするのか,自アプリ以外では終了させないようにするのか
と,実にいろいろな場面が考えられるからです.


図1
設計時画面
  • 以下のボタン類を配置
  • [エクセルを新規起動する]
  • [既存のブックを開く]
  • [表示中のエクセルへの操作]
  • [エクセルを開いたまま終了]
  • [エクセルも一緒に閉じて終了]
  • [指定セルに文字列]
  • [行の挿入]
  • [シートの追加]
  • [文字列の検索]
  • [日付のセル値を取得]
  • [整数値の取得]
  • [枠線と枠内罫線]
  • [上下左右個別枠線]
図2
実行開始画面
  • 作成した EXE を実行
    管理者権限で実行すると操作に失敗するので注意
  • プライマリモニタの画面右上に表示
図3
フォームは最前面
  • エクセルにフォーカス移動しても最前面
  • すぐにボタンクリック (次の操作) が可能

リスト2
CreateOleObject を使用したエクセルのオブジェクトの生成と操作例
CreateOleObject の使用には uses に ComObj が必要
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 宣言 }
     ExcelObj   : OleVariant;
     WorkBook   : OleVariant;
     WorkSheet  : 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(ExcelObj) then begin
    WorkBook  := unAssigned;
    WorkSheet := unAssigned;
    ExcelObj  := unAssigned;
  end;
end;

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

    //ブックを作成
    WorkBook := ExcelObj.Workbooks.Add;

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

    //表示
    ExcelObj.Visible := True;

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

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

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

    Ver := ExcelObj.Version;
    if StrToFloatDef(Ver, 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(WorkBook) then begin
      ACaption := ExcelObj.Caption;
      AWnd     := FindWindow(nil, PChar(ACaption));
      SetWindowPos(AWnd,
                   HWND_TOP,
                   0,
                   0,
                   0,
                   0,
                   SWP_NOMOVE or SWP_NOSIZE);
      WorkBook.Close;
    end;

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

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

    //表示位置を調整
    WorkRect  := Screen.PrimaryMonitor.WorkareaRect;
    xlsWidth  := WorkRect.Right - Self.Width;
    xlsHeight := WorkRect.Bottom - WorkRect.Top;
    SetWindowPos(xlsHandle,
                 HWND_TOP,
                 WorkRect.Left,
                 WorkRect.Top,
                 xlsWidth,
                 xlsHeight,
                 SWP_SHOWWINDOW);

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

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

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

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

    //表示}
    ExcelObj.Visible := True;
  end;
end;

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

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

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

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

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

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

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

  WorkSheet.Cells.Clear;
end;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  ARange := WorkSheet.Range['F24', 'G25'];

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

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

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

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

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

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

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

end.




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


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

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

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

procedure TForm1.Button4Click(Sender: TObject);
var
  ARange : OleVariant;
begin
  ARange := WorkSheet.Range['A1', 'C10'];

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


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

リスト3
OleVariant で扱う際の注意
セルの書式を「文字列」にする場合に,失敗するコードの例
//=============================================================================
//  OLEObjectで変数等を扱う際の注意
//  セルの書式を文字列に設定する例
//=============================================================================
procedure TForm1.Button6Click(Sender: TObject);
var
  ARange : OleVariant;
begin
  ARange := WorkSheet.Range['A1', 'C10'];
  ARange.NumberFormatLocal := '@';

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


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


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

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

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

procedure TForm1.Button8Click(Sender: TObject);
var
  ARange : OleVariant;
begin
  ARange := WorkSheet.Range['A1', 'C10'];
  ARange.NumberFormatLocal := OleVariant('@');

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

procedure TForm1.Button9Click(Sender: TObject);
var
  ARange : OleVariant;
  AType  : OleVariant;
begin
  ARange := WorkSheet.Range['A1', 'C10'];
  AType  := '@';
  ARange.NumberFormatLocal := AType;

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


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

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



//-----------------------------------------------------------------------------
//  OleVariant型の変数に値を設定する際の最も正統的な方法
//  TVariantArg, VT_XXX等の定数名の使用には,usesにActiveXが必要
//  TVariantArg型やTVarDataを使用する場合は,WideString型を使用する
//-----------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
  ARange  : OleVariant;
  AValue  : OleVariant;
  CellVal : WideString;
begin
  ARange := WorkSheet.Range['A1', 'C10'];

  CellVal := '010';
  TVariantArg(AValue).vt      := VT_BSTR;
  TVariantArg(AValue).bstrVal := PWideChar(CellVal);

  ARange.Cells[1,1].Value := AValue;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  ARange  : OleVariant;
  AValue  : OleVariant;
  CellVal : WideString;
begin
  ARange := WorkSheet.Range['A1', 'C10'];

  CellVal := '010';
  TVarData(AValue).VType   := varOleStr;
  TVarData(AValue).VOleStr := PWideChar(CellVal);

  ARange.Cells[1,1].Value := AValue;
end;




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


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

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


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

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

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォームを閉じる時の処理
//  このコードでは,フォームを閉じる前にエクセルを終了してしまうとエラー
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  WorkBook  := unAssigned;
  WorkSheet := unAssigned;
  ExcelObj.Quit;
  ExcelObj  := unAssigned;
end;

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

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

end.


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


  ARange := WorkSheet.Range[WorkSheet.Cells[2, 1], WorkSheet.Cells[ 2, 10]];
  ARange.HorizontalAlignment := OleVariant(xlHAlignCenter);




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


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

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


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


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

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

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




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


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


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

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

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

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

  • 図20
  • 上のコンボボックスで[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 の値を取得して表示します.


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

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

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



リスト6
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
  GUID : TGUID;
  Str  : string;
begin
  Str  := 'Excel.Application';
  GUID := ProgIDToClassID(Str);

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

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

  //登録されていればProgIDが表示される
  ShowMessage(ClassIDToProgID(GUID));
end;

end.




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


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


リスト7
エクセル操作のテスト用基本コード
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) ブックを表示
//=============================================================================
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
  //ExcelObjオブジェクトが生成されているかをチェック
  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;

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

      //WorkSheetオブジェクトが生成されているかをチェック
      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_エクセル操作のテスト用基本コード ( 起動時に指定ブックを開く )


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


リスト8
エクセル操作のテスト用基本コード (起動時に指定ブックを開く)
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,WorkSheetは全て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
  //ExcelObjオブジェクトが生成されているかをチェック
  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;

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

      //WorkSheetオブジェクトが生成されているかをチェック
      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 では,他のアプリへのメッセージ送信,他のアプリからのメッセージの受信等,他のアプリの操作関係の制限が強くなっています.



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




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


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

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

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