Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2009年05月26日] [更新 2020年10月23日] エクセル操作フォームEx
   
エクセル操作フォームEx   参考コード類
CSV データの読込み
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro + Excel 2010







CSV ファイルを開く


エクセル操作フォームEx では,通常のエクセルのブック同様,[開く] ファイルにファイル名を指定すれば,ファイル全体をエクセルのブック同様に表示することができます.ただし,TOpenDialog の Filter プロパティに CSV の設定がありません.必要であれば設定してください.

CSV フィルに限らず,エクセルでファイルを開いて表示した場合,ブックとして扱われます.CSV ファイルは単なるテキストファイルですからシートという概念はありません.シートが 1 つだけのブックとなります.そのシート名は,CSV ファイルのディレクトリと拡張子を除いたファイル名となります.

サンプルプログラム集の例にもあるように,CSV は ADO を使用して接続できます.つまり,ADO 接続でデータを取得して表示できますが,ここでは別の方法を試してみることにしました.





CSV ファイルから読込んで表示


カンマ区切りのテキストデータを収めたファイルから,セルにそれらの値をセットする例です.
TStringList の DelimitedText プロパティを使用して各要素に CSV データを代入し,それらの値をセルに代入しています.単純にファイルを開いてエクセルに読み込む場合と違い,読込んだデータを処理してからセルに代入することができます.
コード中の xlfExcelSheet は,CSV ファイルを読み込んで表示するシートオブジェクトです.


図1
実行結果
  • セル幅は手動調整したもの

リスト1 
procedure TplExcelFormEx1.Button1Click(Sender: TObject);
var
  LFileName : string;
  LReadList : TStringList;
  LColList  : TStringList;
  LRow      : Integer;
  LCol      : Integer;
  LStartCol : Integer;
  LStartRow : Integer;
begin
  // シートの内容 (書式や数式も) をクリア
  xlfExcelSheet.Cells.Clear;

  // CSV データのあるファイル名
  LFileName := ExtractFilePath(Application.ExeName) + 'AAA.txt';

  // そのファイルがあれば実行
  if FileExists(LFileName) then begin
    LReadList := TStringList.Create;
    LColList := TStringList.Create;

    // 2 行目の 2 列目から代入するものとする
    LStartCol := 2;
    LStartRow := 2;
    Screen.Cursor := crHourGlass;

    try
      LReadList.LoadFromFile(LFileName);

      // デリミタの設定 (空白はデリミタとしない)
      LColList.Delimiter       := ',';
      LColList.StrictDelimiter := True;

      // 1 行づつ読込んで LColList の各要素に代入
      for LRow := 0 to LReadList.Count - 1 do begin
        LColList.DelimitedText := LReadList[LRow];

        // 1 行文のデータをセルにセット
        for LCol := 0 to LColList.Count - 1 do begin
          xlfExcelSheet.Cells.Item[LStartRow + LRow, LStartCol + LCol] := LColList[LCol];
        end;
      end;
    finally
      Screen.Cursor := crDefault;
      LReadList.Free;
      LColList.Free;
    end;
  end;
end;


下のコードは,上のコードのテストで使用したテキストファイルの内容です.


リスト2 
"宮本 武蔵",45,20,80,20
"佐々木 小次郎",100,500,80,30
"赤垣 源蔵",30,98,90,20
"沓掛 時次郎",20,50,90,30
"清水 次郎長",15,45,90,30
"近藤 勇",60,75,90,40




CSV ファイルから読込んで表示 - バリアント配列使用


次のコードは,表示処理を高速にするために,バリアント配列を使用しています.非常に高速に読み込むことができます.
バリアント配列と,単純にセルに代入した場合の比較は,添付のテストプログラム [データベースから読込み] でも確認することができます.テストの結果では,上のセルへの単純代入に比べて 20 倍近く高速でした.


リスト3 
procedure TplExcelFormEx1.Button1Click(Sender: TObject);
var
  LFileName  : string;
  LReadList  : TStringList;
  LTempList  : TStringList;
  LColCount  : Integer;
  LStartCell : string;
  LEndCell   : string;
  LRow       : Integer;
  LCol       : Integer;
  LOleArray  : OleVariant;
begin
  // シートの内容 (書式や数式も) をクリア
  xlfExcelSheet.Cells.Clear;

  // CSV データのあるファイル名
  LFileName := ExtractFilePath(Application.ExeName) + 'AAA.txt';

  // そのファイルがあれば実行
  if FileExists(LFileName) then begin
    Screen.Cursor := crHourGlass;

    LReadList := TStringList.Create;
    LTempList := TStringList.Create;
    try
      // デリミタの設定 (空白はデリミタとしない)
      LTempList.Delimiter       := ',';
      LTempList.StrictDelimiter := True;

      LReadList.LoadFromFile(LFileName);

      // データをセットするセル範囲
      // 列数はあらかじめ分かっているものと仮定
      LColCount  := 5;
      LStartCell := 'B2';
      LEndCell   := 'F7';

      // Variant 配列を作成.高速化のために Lock
      LOleArray := VarArrayCreate([0, LReadList.Count - 1, 0, LColCount - 1],VarVariant);
      VarArrayLock(LOleArray);

      // 1 行づつ読込んで TempList の各要素に代入
      for LRow := 0 to LReadList.Count - 1 do begin
        LTempList.DelimitedText := LReadList[LRow];

        // 1 行分のデータをバリアント配列にセット
        for LCol := 0 to LTempList.Count - 1 do begin
          LOleArray[LRow, LCol] := LTempList[LCol];
        end;
      end;

      // Excel に配列データを送る
      xlfExcelSheet.Range[LStartCell, LEndCell].Value := LOleArray;
    finally
      // Variant 配列を UnLock して空にする
      VarArrayUnLock(LOleArray);
      VarClear(LOleArray);

      Screen.Cursor := crDefault;
      LReadList.Free;
      LTempList.Free;
    end;
  end;
end;


バリアント配列を使用する場合,配列の要素数をあらかじめ設定することになります.そのため,データを書き込むセルの範囲を設定しておくことになります.
以下のように,セルの位置を RiC1 形式の行番号と列番号で処理すれば,それらの値を自動的に設定することができます.


リスト4 
procedure TplExcelFormEx1.Button2Click(Sender: TObject);
var
  LFileName  : string;
  LReadList  : TStringList;
  LTempList  : TStringList;
  LColCount  : Integer;
  LStartPos  : TPoint;
  LStartCell : string;
  LEndCell   : string;
  LRow       : Integer;
  LCol       : Integer;
  LOleArray  : OleVariant;
begin
  // シートの内容 (書式や数式も) をクリア
  xlfExcelSheet.Cells.Clear;

  // CSV データのあるファイル名
  LFileName := ExtractFilePath(Application.ExeName) + 'AAA.txt';

  // そのファイルがあれば実行
  if FileExists(LFileName) then begin
    Screen.Cursor := crHourGlass;

    LReadList := TStringList.Create;
    LTempList := TStringList.Create;
    try
      // デリミタの設定 (空白はデリミタとしない)
      LTempList.Delimiter       := ',';
      LTempList.StrictDelimiter := True;

      LReadList.LoadFromFile(LFileName);

      // データをセットするセル範囲を計算するために最初の 1 行目を取得
      LTempList.DelimitedText   := LReadList[0];

      LColCount  := LTempList.Count;
      LStartCell := 'B2';
      LStartPos  := xlfToR1C1(LStartCell);
      LEndCell   := xlfToA1(LStartPos.Y + LReadList.Count - 1, LStartPos.X + LColCount - 1); // 'F7';

      // Variant 配列を作成.高速化のために Lock
      LOleArray := VarArrayCreate([0, LReadList.Count - 1, 0, LColCount - 1],VarVariant);
      VarArrayLock(LOleArray);

      // 1 行づつ読込んで TempList の各要素に代入
      for LRow := 0 to LReadList.Count - 1 do begin
        LTempList.DelimitedText := LReadList[LRow];

        // 1 行分のデータをバリアント配列にセット
        for LCol := 0 to LTempList.Count - 1 do begin
          LOleArray[LRow, LCol] := LTempList[LCol];
        end;
      end;

      // Excel に配列データを送る
      xlfExcelSheet.Range[LStartCell, LEndCell].Value := LOleArray;
    finally
      // Variant 配列を UnLock して空にする
      VarArrayUnLock(LOleArray);
      VarClear(LOleArray);

      Screen.Cursor := crDefault;
      LReadList.Free;
      LTempList.Free;
    end;
  end;
end;


上のコードでは,R1C1 形式への変換と A1 形式への変換にエクセル操作フォームEx に実装している関数を使用していますが,以下の記事に掲載している関数を作成して処理することもできます.





CSV ファイルから読込んで表示 - QueryTables 使用


QueryTables コレクションオブジェクトの Add メソッドを使用する例です.この方法も高速処理です.


リスト5 
procedure TplExcelFormEx1.Button1Click(Sender: TObject);
var
  LFileName    : string;
  LpConnectStr : OleVariant;
  LpStartCell  : Txlf2000Range;
  LQueryTbl    : OleVariant;
begin
  // シートの内容 (書式や数式も) をクリア
  xlfExcelSheet.Cells.Clear;

  Screen.Cursor:=crHourGlass;

  // 読込む CSV ファイルのフルパス名
  LFileName := ExtractFilePath(Application.ExeName) + 'AAA.txt';

  // 接続文字列
  LpConnectStr := 'TEXT;' + LFileName;

  // 出力先セル (左上) の指定
  LpStartCell := xlfExcelSheet.Range['B2', 'B2'];
  try
    LQueryTbl := xlfExcelSheet.QueryTables.Add(LpConnectStr, LpStartCell, EmptyParam);

    LQueryTbl.RefreshStyle           := xlOverwriteCells; // 上書き
    LQueryTbl.AdjustColumnWidth      := True;             // 列幅自動調整
    LQueryTbl.TextFileCommaDelimiter := True;             // カンマ区切り
    LQueryTbl.Refresh;                                    // 実行
  finally
    Screen.Cursor := crDefault;
  end;
end;




読み込む項目を選択する例 - QueryTables 使用


QueryTables.Add メソッドは,OLE DB または ODBC の接続文字列を使用してデータベースからデータを取得してエクセルのシートに読み込むことができます.
次の例は,CSV ファイルをデータベースのテーブル (表) として,ODBC 接続でエクセルのシートにデータを読み込む例です.この場合,SQL 文を以下のようにすると全てのデータの読み込みとなりますが,このサンプルでは読み込む項目を指定しています.

  LpSqlStr := 'select * from CSV_DB.csv ';

SELECT 文で読み込む項目を指定した場合,CSV ファイルの 1 行目にそれらの項目名としての文字列がないと OLE の例外が発生します.


リスト6 
procedure TplExcelFormEx1.Button1Click(Sender: TObject);
var
  LDir         : string;
  LpConnectStr : OleVariant;
  LpSqlStr     : OleVariant;
  LpStartCell  : Txlf2000Range;
  LQueryTbl    : Excel2000._QueryTable;
begin
  // シートの内容 (書式や数式も) をクリア
  xlfExcelSheet.Cells.Clear;

  Screen.Cursor := crHourGlass;

  // 読込む CSV ファイルがあるディレクトリ名
  LDir := ExtractFileDir(Application.ExeName);

  // 接続文字列 (ここでは ODBC ドライバを使用)
  // OLE DB の時は OLEDB;DRIVER=.... とする
  LpConnectStr := 'ODBC;DRIVER={Microsoft Text Driver (*.txt; *.csv)};DBQ=' + LDir;

  // 出力先セル (左上) の指定
  LpStartCell := xlfExcelSheet.Range['A1', 'A1'];

  // SQL 文 (選択クエリ)
  // CSV ファイルの最初の行が項目名として扱われる
  // CSV ファイルの先頭行にこれらの項目名がないと OLE 例外が発生する 
  LpSqlStr := 'select Company,Addr1 from CSV_DB.csv ';

  try
    LQueryTbl := xlfExcelSheet.QueryTables.Add(LpConnectStr, LpStartCell, LpSqlStr);

    LQueryTbl.RefreshStyle      := xlOverwriteCells; // 上書き
    LQueryTbl.AdjustColumnWidth := True;             // 列幅自動調整
    LQueryTbl.BackgroundQuery   := True;             // バックグラウンド実行
    LQueryTbl.Refresh(EmptyParam);                   // 実行
  finally
    Screen.Cursor := crDefault;
  end;
end;




ハイフン ( - ) を含む数字の文字列と先行する 0 の処理


エクセルのシートに次の内容の CSV データを読み込んで表示します.



リスト7 
01-01,0045,0055,東京都 文京区 
02-02,0060,0070,群馬県 水上町 


結果は下図のようになります.
エクセルでは,ハイフンを含む数字だけの文字列は日付として扱われることがあります. これはこれで便利な仕様とも言えますが,元のままで表示したい場合もあります.エクセルでは,数字だけの文字列は数値として扱われます.その結果,文字列の先行する 0 (ゼロ) は表示しません.



図2
上の CSV データを読み込んだ結果
  • ハイフンを含む数文字は日付形式
  • 先行する 0 は表示しない

CSV ファイルはテキストファイルです.元のテキストのままエクセルのシートに CSV のデータを表示するには,NumberFormatLocal  プロパティを使用して,明示的に文字列としての書式を読み込むセルに設定します.下に示すコードでは,読み込む全てのセルに対して書式を設定していますが,書式が必要なセルだけに対して設定すれば十分です.
セルの指定に Cells.Item プロパティを使用する場合は,下記のリンクの記事のように,OleVaiant 型の変数を使用して書式を設定します.Cells.Item プロパティは OleVariant 型です.



図3
CSV のテキストのまま表示

リスト8 
      // Excel に配列データを送る
      xlfExcelSheet.Range[LStartCell, LEndCell].NumberFormatLocal := '@';
      xlfExcelSheet.Range[LStartCell, LEndCell].Value := LOleArray;