none
VC++/CLIからの動的なExcel読出し RRS feed

  • 質問

  • VisualStudio2008(Pro.) で、VC++/CLRアプリケーションとして作成されたツールからExcelの指定のセルの内容を読みだす部分を作成しています。

    他の方の質問を拝見およびネットで調べて下記のようにソースを記載し、読みだせるようにはなったのですが

    対象となるExcelファイルは別ツールによって内容が書き換えられるのですが、一度上書き保存されないと値が反映されません。

    保存前の値(セルに値を上書きして、リターンを押した状態)にアクセスする方法をどなたかご存じありませんでしょうか。

    以下、現在のサンプルコードです。

    仕様としては、特定のExcelファイル(既存。93-97形式)がすでに開かれている状態で、他ツールからA1セルの値があるタイミングで更新されます。

    このとき、そのツール側で上書き保存を行うかどうかはわかりません。

    そのファイルのA1セルを1秒ごとに読出したいです。

    ※前提として、プロジェクトの参照にMicrosoft::Office::Interop::Excelを追加しています。

    Form1.h

    namespace Exceltest {

     using namespace System;
     using namespace System::ComponentModel;
     using namespace System::Collections;
     using namespace System::Windows::Forms;
     using namespace System::Data;
     using namespace System::Drawing;

     public ref class Form1 : public System::Windows::Forms::Form
     {
     public:
      Form1(void)
      {
       InitializeComponent();
       //
       //TODO: ここにコンストラクタ コードを追加します
       //
      }

     protected:
      /// <summary>
      /// 使用中のリソースをすべてクリーンアップします。
      /// </summary>
      ~Form1()
      {
       if (components)
       {
        delete components;
       }
      }
     private: System::Windows::Forms::Button^  button1;
     protected:
     private: System::Windows::Forms::TextBox^  textBox1;
     private: System::Windows::Forms::TextBox^  textBox2;
     private: System::Windows::Forms::OpenFileDialog^  openFileDialog1;
     private: System::Windows::Forms::Timer^  timer1;
     private: System::Windows::Forms::Button^  button2;
     private: System::Windows::Forms::Button^  button3;
     private: System::ComponentModel::IContainer^  components;

     private: //Excel接続用
        Microsoft::Office::Interop::Excel::Application^ xlApp;
        Microsoft::Office::Interop::Excel::Workbook^ wb;
        Microsoft::Office::Interop::Excel::Worksheet^ ws;
        Microsoft::Office::Interop::Excel::Range^ rg;

     private:
      /// <summary>
      /// 必要なデザイナ変数です。
      /// </summary>

    #pragma region Windows Form Designer generated code
      /// <summary>
      /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
      /// コード エディタで変更しないでください。
      /// </summary>
      void InitializeComponent(void)
      {  /*  割愛します */  }
    #pragma endregion
     //開始ボタン
     private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {
         //Excelオブジェクト作成
         xlApp = gcnew Microsoft::Office::Interop::Excel::ApplicationClass();
         //読出しタイマー起動
         timer1->Start();

        }

    //終了ボタン
        private: System::Void button3_Click(System::Object^  sender, System::EventArgs^  e) {
        //Excelオブジェクト終了
         xlApp->Quit();
       }

    //Excel読出しタイマー(1秒おき)
        private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e) {
       int excelval;
        //指定のexcelファイルをオープン
         wb = xlApp->Workbooks->Open( "D:\\test.xls", //String^ Filename, 
                Type::Missing, //  Objcet^ UpdateLinks,
                Type::Missing, // Object^ ReadOnly,
                Type::Missing, // Object^ Format,
                Type::Missing, // Object^ Password,
                Type::Missing, // Object^ WriteResPassword,
                Type::Missing, // Object^ IgnoreReadOnlyRecommended,
                Type::Missing, // Object^ Origin,
                Type::Missing, // Object^ Delimiter,
                Type::Missing, // Object^ Editable,
                Type::Missing, // Object^ Notify,
                Type::Missing, // Object^ Converter,
                Type::Missing, // Object^ AddToMru,
                Type::Missing, // Object^ Local,
                Type::Missing );// Object^ CorruptLoad 
        //ActiveSheet
        ws = static_cast<Microsoft::Office::Interop::Excel::Worksheet^>(wb->ActiveSheet);
        //指定セル位置を取得("A1")
        rg = static_cast<Microsoft::Office::Interop::Excel::Range^>(ws->Cells[1,1]);
        excelval = Convert::ToInt32(rg->Text);
        //workbookをいったん閉じる
        wb->Close( Type::Missing,Type::Missing,Type::Missing);
        //画面表示
        this->textBox2->Text = excelval.ToString();
       }
    };

    こんな感じで、最初に開いたときに書かれている値を参照することはできるのですが、その後ExcelのA1の値を書き換えても1秒ごとに読みだしてはいるようですが、値は反映されません。

    Excelを上書き保存すると反映されます。

    他ツールの仕様で上書き保存してくれるかどうかわからないため、A1の値が書き換わったら(値が変化し、エンターが押されたら)、その値を参照したいのですが何か方法はございませんでしょうか。

    以上、拙い質問で申し訳ありませんがどなたかご教授お願いします。

    ちなみに、MFCを使用してExcelにAttachした場合においては上記問題は解決するようですが、ベースのツールがCLRで作成されており、MFCでイチから作りなおすのは自身のスキル的にとても大変なので避けたいです。。

    2015年2月9日 13:08

回答

  • 上書き保存していないと言うことは該当の Excel が未保存状態で生きているということですので、その Excel のオブジェクトを取得する必要があります。
    GetObject で既存オブジェクトを取得できるとは見かけますが、安定的に対象のオブジェクトをとれるかどうかは未確認です。
    (複数のプロセスで Excel ファイルを開いている場合など)

    参考
    http://qa.atmarkit.co.jp/q/4634

    // C++/CLI で書くのは大変かもですが。

    • 回答の候補に設定 星 睦美 2015年2月10日 0:37
    • 回答としてマーク 星 睦美 2015年2月17日 4:22
    2015年2月9日 14:29
    モデレータ

すべての返信

  • 上書き保存していないと言うことは該当の Excel が未保存状態で生きているということですので、その Excel のオブジェクトを取得する必要があります。
    GetObject で既存オブジェクトを取得できるとは見かけますが、安定的に対象のオブジェクトをとれるかどうかは未確認です。
    (複数のプロセスで Excel ファイルを開いている場合など)

    参考
    http://qa.atmarkit.co.jp/q/4634

    // C++/CLI で書くのは大変かもですが。

    • 回答の候補に設定 星 睦美 2015年2月10日 0:37
    • 回答としてマーク 星 睦美 2015年2月17日 4:22
    2015年2月9日 14:29
    モデレータ
  • Azulean様、アドバイスありがとうございます。

    早速、下記のように修正してみました。

    プロジェクトのプロパティ→共通プロパティの参照に、「Microsft.VisualBasic」を追加

    上記ソースのExcelアプリケーション取得部を下記に修正。

        

    //開始ボタン
     private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {
         //Excelオブジェクト作成
         // xlApp = gcnew Microsoft::Office::Interop::Excel::ApplicationClass();

            xlApp =  static_cast<Microsoft::Office::Interop::Excel::Application^>(Microsoft::VisualBasic::Interaction::GetObject("","Excel.Application"));

    //読出しタイマー起動
         timer1->Start();

        }

    と、してみました。コンパイルは通り、xlAppにアドレスは入るようですが、その後の動作に変わりはありませんでした。。

    (最初のデータは入りますが、その後の変更が反映されない)

    タスクマネージャを起動しながらデバッグしてみましたが、開始時に他のExcelファイルは起動されていない状態でした。

    開いているファイルオブジェクトそのものを取得するのであれば、その後にWorkbookをOpenするのがマズイのでしょうか。

    もう少し粘ってみて、どうしても無理そうならMFCで頑張ります。

    2015年2月9日 16:04
  • 自己レスです。

    すみません、さきほどせっかく貼っていただいたURLを最後まで読んでいませんでした。

    さらに言うと、最初の質問でCLI→CLRと誤記してました。

    下記で思った通りに動きました。

    ありがとうございました!!

        

     //開始ボタン
     private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {

       //Application取得不要

       //ExcelWorksheetオブジェクト取得(ファイル名指定、クラス指定なし)
         wb = static_cast<Microsoft::Office::Interop::Excel::Workbook^>(Microsoft::VisualBasic::Interaction::GetObject("D:\\test.xls",""));

        //ActiveSheet
        ws = static_cast<Microsoft::Office::Interop::Excel::Worksheet^>(wb->ActiveSheet);
        //指定セル位置を取得("A1")
        rg = static_cast<Microsoft::Office::Interop::Excel::Range^>(ws->Cells[1,1]);     

       //読出しタイマー起動
         timer1->Start();

        }

    //終了ボタン
        private: System::Void button3_Click(System::Object^  sender, System::EventArgs^  e) {

              //Excel終了不要(タイマーだけストップ)

         timer1->Stop();

        }

    //Excel読出しタイマー(1秒おき)
        private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e) {
       int excelval;
         excelval = Convert::ToInt32(rg->Text);
         //画面表示
        this->textBox2->Text = excelval.ToString();
       }
    };

    2015年2月9日 16:28
  • 既に解決済みっぽいので、

    > (複数のプロセスで Excel ファイルを開いている場合など)

    について Excel 2007 と 2013 で ROT を確認したので補足みたいなものです・・・。

    複数プロセスで同一ファイルを開いても、読み取り専用で開かれた場合には ROT へのファイルモニカ登録をしないようなので、ROT からの Object 取得は、常に編集中 Excel プロセスの Workbook 参照を返すはずです。

    定かではない記憶ですが VB の GetObject は開かれてない場合に Object 作成してた気もするので、そのあたりに注意が必要な気もします。

    自分は ROT 登録済みのオブジェクト参照取得に Marshal.BindToMoniker を使った事がありますが、たしかこちらは ROT 登録からの取得限定で、開いている Excel プロセスが生きている必要があったはず・・・。

    ----------------------------------------------------------------------------------------------

    自作コードを確認すると Marshal.BindToMoniker も登録がない場合に作成するような感じでした、はっきりしないので「自ら Excel プロセスを作成する状況は注意が必要」と考えてもらえると・・・。

    なんともあやふやな発言で申し訳ない・・・。

    • 編集済み kyano30 2015年2月12日 14:59 修正追記
    2015年2月12日 11:22