VC++(MFC)でExcelを操作する方法(1)

仕事で、久しぶりVC++をやることに。どうしても出力はExcelにして欲しいという要望があってかつての記憶を手繰りながら忘れないうちにメモ。長くなりそうなので、分割してアップします。

クラスの追加で「タイプライブラリからMFCクラスを作成」ExcelObjectライブラリを選択し、次のインタフェースをからクラスを生成します。(注意:Rangeクラスを生成しコンパイルするとVARIANT DialogBox()の箇所でエラーとなります。この部分はVARIANT CDialogBox()として対処しています。)

  • _Application
  • _Workbook
  • _Worksheet
  • Range
  • Workbooks
  • Worksheet
  • それぞれのクラスは名前のままエクセルのWorkbook(s)/Worksheet(s)/Rangeのラッパクラスとなります。また、各種関数を呼び出す際に使用するオプションパラメタは下記のように定義しています。

    COleVariant covTrue((short)TRUE),
        covFalse((short)FALSE),
        covOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);
    

    まず、最初にアプリケーション(エクセル)にアタッチします。

    CApplication excel;
    bool bCreate;
    if (CLSIDFromProgID(OLESTR("Excel.Application"), &clsid) != NOERROR){
      AfxMessageBox("Excel not installed or version not compatible");
      return ;
    }
    
    LPUNKNOWN lpUnk;
    LPDISPATCH lpDispatch;
    if (GetActiveObject(clsid, NULL, &lpUnk) == NOERROR){
      HRESULT hr = lpUnk->QueryInterface(IID_IDispatch,(LPVOID*)&lpDispatch);
      lpUnk->Release();
      if (hr == NOERROR){
        //すでにExcelが起動されている状態であればAttachDispatch
        excel.AttachDispatch(lpDispatch, TRUE);
        bCreate = false;
      }
    }
    //Excelが起動されていなければCreateDispatch()
    if (excel.m_lpDispatch == NULL){
      if (!excel.CreateDispatch(clsid, &e)){
        AfxMessageBox("can't AttachDispatch Excel Application!!");
        return 0;
      }
      bCreate = false;
    }
    

    エクセルがもともと起動しているか否かでAttachDispatch/CreateDispatchかの処理に分かれます。後はそれぞれWorkbooks/Workbook/Worksheets/Worksheetオブジェクトにアタッチして作業をおこないます。

    CWorkbooks wbooks;
    CWorkbook wbook;
    CWorksheets wsheets;
    CWorksheet wsheet;
    //ユーザ操作禁止
    excel.put_UserControl(FALSE);
    //workbooksオブジェクトにアタッチ
    wbooks.AttachDispatch(excel.get_Workbooks());
    //workbooksに新規Workbookを作成しwookbookオブジェクトにアタッチ
    wbook.AttachDispatch(wbooks.Add(covOptional));
    //作成したworkbookのworksheetsオブジェクトにアタッチ
    wsheets.AttachDispatch(wbook.get_Worksheets());
    

    実際の作業はworksheetオブジェクトに対しておこなうので最後はworksheetオブジェクトを生成します。下の例では、get_Count()メソッドでWorksheetsオブジェクトの現在のシート数を求め、最後のシートにアタッチしそのシートの後ろに新規シートを追加します。

    VARIANT varLast;
    varLast.vt = VT_DISPATCH;
    varLast.pdispVal = wsheets.get_Item(COleVariant((long)wsheets.get_Count()));
    wsheet.AttachDispatch(wsheets.Add(covOptional,varLast,COleVariant((long)1),covOptional));
    wsheet.put_Name("サンプルシート");
    

    ここまでで、ようやくセルにアクセス可能な状態になります。あとはそれぞれのセルに対して処理をおこないます。最初ハマったのですが、セルの一つずつに対して処理を行うととっても遅いのでCOleSafeArrayクラスを使用して多次元配列で扱うのがコツです。下の例では1行3カラム(A1..C1)のセルのデータをまず作成しその後cell(A1..C1)オブジェクトにアタッチして一度にデータをセットしています。

    CString cs;
    COleSafeArray sa;
    DWORD elements[2];
    long  index[2];
    BSTR  bstr
    elements[0] = 1; //1行
    elements[1] = 3; //3カラム
    sa.Create(VT_BSTR,2,elements);
    index[0] = 0; index[1] = 0;
    for(int i=0;i<elements[1];i++){
      cs.Format("ここはセル%c1",(0x41+i));
      index[0] = 0; index[1] = i;
      bstr = cs.AllocSysString();
      sa.PutElement(index,bstr);SysFreeString(bstr);
    }
    CString fr;
    CString to;
    CRange cell;
    fr.Format("A1");
    to.Format("C1");
    cell.AttachDispatch(wsheet.get_Range(COleVariant(fr),COleVariant(to)));
    cell.put_Value(COleVariant(sa));
    cell.ReleaseDispatch();
    sa.Clear();
    sa.Detach();
    

    基本的にこのようにしてセルの値を設定していきます。最後は保存して終了。bCreateフラグは最初に設定したフラグ。元々エクセルが起動していた場合はQuit()するとそのブックも閉じてしまいますのでCreateDispatch()したときのみQuitします。

    //保存
    wbook.SaveAs(COleVariant("c:\\sample.xls"),covOptional,covOptional,covOptional,
            covOptional,covOptional,0,covOptional,covOptional,covOptional,covOptional);
    //操作禁止解除
    excel.put_UserControl(TRUE);
    //クリーンアップ
    wsheet.ReleaseDispatch();
    wsheets.ReleaseDispatch();
    wbook.ReleaseDispatch();
    wbooks.ReleaseDispatch();
    excel.ReleaseDispatch();
    if (bCreate) excel.Quit();
    VariantClear(&varLast);
    

    とりあえずはこれでなんとかできるはずです。もう少し細かいところは次回書きます。

    この記事のトラックバックURL:

    http://hippos-lab.com/blog/trackback/103

返信