複数のファイルを開き反復処理を行うには

解決


hoge  2006-05-02 09:43:43  No: 61599  IP: 192.*.*.*

よろしくお願いします、先日unsigned char型のデータをテキストボックスに
表示する方法でお世話になったものです、その際はお世話になりました
Visual Studio 2005 VC8という環境です、
本日御教授お願いしたいことですが、複数のファイルを開く操作を行う場合、
openFileDialogメッソドのMultiselectプロパティを有効にすれば、実現できますが、
複数ファイルの選択までは行えるのですが、選択したファイルの書出し処理を
行いたいのですが巧くいきません。

次のような処理を行う場合

1.  ファイルを複数選択
2.  ファイルを開く
3.  関数を呼出し文字整形処理を行う
4.  textbox1にファイルの内容を書出す
5.  ファイル閉じる
6.  次のファイルを開く
7.  2から6までの処理を最後のファイルまで繰り返す

下記のコードですと
最後に読込んだファイルだけしか処理が行われず、期待している振舞い(反復処理)を
しません、どこが問題なのか御教授お願いいたします。

  private: System::Void toolStripButton1_Click(System::Object^  sender, System::EventArgs^  e) {
    using ::System::Windows::Forms::DialogResult; 
    //函数fooの引数
    unsigned char arg_1, arg_2;

    //函数fooの定義
    void command_word(unsigned char arg_1, unsigned char arg_2);

    Stream^ fs;
    OpenFileDialog^ openFileDialog1 = gcnew OpenFileDialog;
    openFileDialog1->InitialDirectory = "D:\\hoge";
    openFileDialog1->Filter = "All files (*.*)|*.*";
    openFileDialog1->FilterIndex = 2;
    openFileDialog1->RestoreDirectory = true;

    //複数のファイルを選択できるようにする
    openFileDialog1->Multiselect = true;

    if ( openFileDialog1->ShowDialog() == ::DialogResult::OK )
      {

        if ( (fs = openFileDialog1->OpenFile()) != nullptr )
          { 
          System::IO::BinaryReader ^ binReader = gcnew BinaryReader(fs);
          //文字変数を24バイト確保
          unsigned char c[24];
          int i = 0;
          while(i < 24)
          {

            //配列に1バイト読込む
            c[i] = binReader->ReadByte();

            // ポインタインクリメント
            i++;
          }

        //arg_1, aarg_2初期化
        arg_1 = c[0];
        arg_2 = c[1];

        //函数fooの呼出し
        foo(arg_1, arg_2);

        //  ファイル名の取得
        String^ s1 = openFileDialog1->FileName;

        //変数foo2を初期化
        String^ s2 = gcnew String( foo2 );

        //変数foo3を初期化
        String^ s3 = gcnew String( foo3 );

        //foo4の参照している変数を初期化
        String^ s4 = gcnew String( foo4 );

        //foo5の参照している変数を初期化
        String^ s5 = gcnew String( foo5 );
                
        //表示  
        textBox1->Text = "foo1" + "foo2" + "foo3" + "foo4" + "foo5" + "\r\n" +
          s1 + s2 + s3 + s4 + s5 + "\r\n" ;

        //binReaderクローズ
        binReader->Close();
        } 
      }
    }

編集 削除
じゃんぬねっと  URL  2006-05-02 09:57:46  No: 61600  IP: 192.*.*.*

> 複数のファイルを開く操作を行う場合、
> openFileDialogメッソドのMultiselectプロパティを有効にすれば、実現できますが、

選択できるだけで「開く」のは実現できないでしょう。

> 複数ファイルの選択までは行えるのですが、選択したファイルの書出し処理を
> 行いたいのですが巧くいきません。

FileName ではなく FileNames メンバから列挙しないとダメですよね?

編集 削除
Blue  2006-05-02 10:29:57  No: 61601  IP: 192.*.*.*

>if ( (fs = openFileDialog1->OpenFile()) != nullptr )
のブロックの処理をファイル分繰り返すということでしょうか?

じゃんぬさんからも指摘がありますが、
openFileDialog1->OpenFile()
では先頭のファイルからしかStreamオブジェクトが取れないと思います。
openFileDialog1->FileNames
から選択数分ぐるぐるまわって処理しなければいけないでしょう。
(そうすると、textBox1->Text が全部上書きだから最後のファイルの情報しか出ないけど。)

それと、
>    //文字変数を24バイト確保
>    unsigned char c[24];
>    int i = 0;
>    while(i < 24)
>    {
>
>        //配列に1バイト読込む
>        c[i] = binReader->ReadByte();
>
>        // ポインタインクリメント
>        i++;
>    }
ここは

// 24バイト分読み込む
array< unsigned char >^ c = binReader->ReadBytes( 24 );

とした方がすっきりします。
また、ファイルは読み込まなくなったら早めに閉じておいたほうがよいかも。

> 函数
これってわざとでしょうか?

編集 削除
hoge  2006-05-02 10:58:41  No: 61602  IP: 192.*.*.*

じゃんぬねっとさん、blueさんどうもお世話になります

> 選択できるだけで「開く」のは実現できないでしょう。
そうでしたか、開けると思い、海外のサイトも含め昨日一日調べまくっていました

> openFileDialog1->FileNames
> から選択数分ぐるぐるまわって処理しなければいけないでしょう。
> (そうすると、textBox1->Text が全部上書きだから最後のファイルの情報しか出ないけど。)

確かにご指摘通りで上書きになってしまいますね、連結して出力させる仕組みを考えないと、期待している処理は行えませんね。

それからFileNamesから、取得できるのは、すべてのファイル名の他に取得したファイル数も得られるのでしょうか?

> のブロックの処理をファイル分繰り返すということでしょうか?
それで複数選択した最後のファイルまで読込めると思っていました、でもよく考えると
fsにファイルの終端まで読込めという意味ですよね、勘違いしてました

>array< unsigned char >^ c = binReader->ReadBytes( 24 );
御教授ありがとうございます、すっきりしてます

> 函数
> これってわざとでしょうか?
理由はありませんがなんとなく、使ってました

編集 削除
Blue  2006-05-02 11:12:02  No: 61603  IP: 192.*.*.*

for each を使いましょう。
> それからFileNamesから、取得できるのは、すべてのファイル名の他に取得したファイル数も得られるのでしょうか?
for each ( String^ s in openFileDialog1->FileNames )

ファイル数をとりたい場合は
openFileDailog1->FileNames->Length
で取れます。

> 海外のサイトも含め
ですよね。日本語のC++/CLI(Managed C++)のサイトがどうも少ないようです。
でも、結局のところ .Net Framework を使っているので C#(VB.2005)でも十分参考になると思います。
(あとは、C++/CLIの文法にするだけです)

編集 削除
Blue  2006-05-02 11:37:13  No: 61604  IP: 192.*.*.*

追記
> //表示    
> textBox1->Text = "foo1" + "foo2" + "foo3" + "foo4" + "foo5" + "\r\n" +
> s1 + s2 + s3 + s4 + s5 + "\r\n" ;
という表示であれば、ListViewコントロールとか使ってみるとか。

編集 削除
hoge  2006-05-02 12:48:50  No: 61605  IP: 192.*.*.*

blueさんどうもありがとうございます
> openFileDailog1->FileNames->Length
Lengthプロパティで取得できたんですね、ありがとうございます
それから、
> for each ( String^ s in openFileDialog1->FileNames )
を参考に、向学のため各ファイル名を配列に取得することを考え
MSDNを探していましたら
http://msdn2.microsoft.com/ja-JP/library/system.windows.forms.filedialog.filenames.aspx
ここでそれらしき記述を見つけたのですがfileというString型の配列にファイル名を
取得して言っている様に見えるのですが、私の見方間違っていますか?
各ファイルを個別の配列に切出す場合はどんな感じになるのでしょうか。

array<String^>^ files = openFileDialog1->FileNames;
for each ( String^ file in files ) {
  MessageBox::Show( file );//これは成功
}

MessageBox::Show( file[1] );//失敗

編集 削除
Blue  2006-05-02 12:59:17  No: 61606  IP: 192.*.*.*

MessageBox::Show( files[0] );
なのでは?
配列にする必要がないようですけど。
(openFileDialog1->FileNames自身が配列)

編集 削除
Blue  2006-05-02 13:10:47  No: 61607  IP: 192.*.*.*

for each ( String^ file in openFileDialog1->FileNames )
{
     // ファイルオープン
     IO::BinaryReader^ binReader = gcnew IO::BinaryReader( IO::File::OpenRead( file ) );
     // 24バイト分読み込む
     array< unsigned char >^ c = binReader->ReadBytes( 24 );
     //arg_1, aarg_2初期化
     arg_1 = c[0];
     arg_2 = c[1];
// 省略
}

としてけばよさそうですがどうでしょうか?

編集 削除
hoge  2006-05-02 18:38:42  No: 61608  IP: 192.*.*.*

Blueさん、どうもですわざわざソースまで書いていただいてありがとうございます
コードも非常にすっきりしました、いよいよ表示部分に取掛かっているのですが
先に書かれていた、ListViewを検索すると確かに見栄えもよく是非使いたくなりました
しかし、いかんせん参考となるサイトが見当たりません
ListViewのプロパティを変えてグリッドを表示したり、ヘッダー部分の設定は
簡単にできたのですが、アイテムの追加が出来なくて悪戦苦闘しています
http://www.atmarkit.co.jp/fdotnet/dotnettips/258listviewadd/listviewadd.html
ここのサイトを参考C#のコードを置き換えてやっているのですが
結果がでません、MSDNも私の理解力が乏しいのは否めませんが、
肝心な部分は簡潔な説明で終始し不必要な部分には冗長な解説が多く、
いまひとつ的を得ません。

// リストビューの初期化
ListView^ listView1 = gcnew ListView;
// ListViewコントロールのデータをすべて消去します。
listView1->Items->Clear();
// アイテム追加
array<String^>^ item1 = { s1, s2, s3, s4, s5};

listView1->Items->Add(gcnew ListViewItem(item1));//これでは表示できないのですが?

編集 削除
Blue  2006-05-02 19:11:46  No: 61609  IP: 192.*.*.*

> // リストビューの初期化
> ListView^ listView1 = gcnew ListView;
ここはいらないのでは?
フォーム上にコントロールとして配置すれば勝手にnewされます。
(扱いはtextBox1と同じ)

編集 削除
hoge  2006-05-02 20:46:00  No: 61610  IP: 192.*.*.*

blueさんどうもです
初期化は必要なかったんですね、ところで
listView1 -> Items->Add( s1);
としたら、ファイルから読込んだs1の内容が表示できたので
これで出来るかと思い
listView1 -> Items->Add( s1, s2, s3, s4, s5 );
としたら
'System::Windows::Forms::ListView::ListViewItemCollection::Add' : 5 個の引数を伴うオーバーロードされた関数はありません。
とエラーメッセージが出ました
引数を減らしていったらs3で、表示できましたが内容が違うものでした
恐らく
listView1 -> Items->Add( arg_1, arg_2, arg_3 );
3個のパラメータを持つメソッドのような感じですが、どこを調べていいものやら
listView1 -> Text = s1, s2, s3, s4, s5;
とかもコンパイルは通りましたが、何も表示できません
VC++でリストビューの使い方を分かり易く解説している、参考になるサイトとかご存知ありませんでしょうか?

編集 削除
Blue  2006-05-02 21:03:30  No: 61611  IP: 192.*.*.*

>// アイテム追加
>array<String^>^ item1 = { s1, s2, s3, s4, s5};
>
>listView1->Items->Add(gcnew ListViewItem(item1));//これでは表示できないのですが?
これであっているはずですよ。

編集 削除
Blue  2006-05-02 21:25:24  No: 61612  IP: 192.*.*.*

ちなみに
> 初期化は必要なかったんですね
というわけではなく、変数宣言自体がいらないです。

C言語でもそうですが

int main()
{
    int n = 0;
    {
         int n;
         n = 10;
    }
    printf( "%d\n", n );
    return 0;
}

が 10 にならないのと理由は同じです。

編集 削除
hoge  2006-05-02 22:02:48  No: 61613  IP: 192.*.*.*

Blueさん、初期化の解説理解できました本当にお世話になっています
それから
確かに、表示できました・・・・・さっきまでは出来なかったのに?

array<String^>^ item1 = { s1, s2, s3, s4, s5};
listView1->Items->Add(gcnew ListViewItem(item1));
としたらs1〜s5のデータが一行目に表示できました
続いて
array<String^>^ item1 = { s1, s2, s3, s4, s5};
listView1->Items->Add(gcnew ListViewItem(item1));
array<String^>^ item2 = { s1, s2, s3, s4, s5};
listView1->Items->Add(gcnew ListViewItem(item2));
としたら、二行表示できたので

for eachの前に
int i = 0;
とiを0で初期化して

array<String^>^ item[i] = { s1, s2, s3, s4, s5};
listView1->Items->Add(gcnew ListViewItem(item[i]));

i++;
iをインクリメントしたら、最終行まで表示できると思ったのですが
: error C2057: 定数式が必要です。
: error C2466: サイズが 0 の配列を割り当てまたは宣言しようとしました。
: error C2728: 'cli::array<Type,dimension> ^' : ネイティブ配列はこのマネージ型を含むことはできません
こういう場合どの様にしたら宜しいでしょうか?

編集 削除
Blue  2006-05-02 22:26:05  No: 61614  IP: 192.*.*.*


listView1->Items->Clear();
for each ( String^ file in openFileDialog1->FileNames )
{
     // ファイルオープン
     IO::BinaryReader^ binReader = gcnew IO::BinaryReader( IO::File::OpenRead( file ) );
     // 24バイト分読み込む
     array< unsigned char >^ c = binReader->ReadBytes( 24 );
     //arg_1, aarg_2初期化
     arg_1 = c[0];
     arg_2 = c[1];

// 省略

    // アイテム追加
    array< String^ >^ item = { s1, s2, s3, s4, s5 };
    listView1->Items->Add( gcnew ListViewItem( item ) );
}

でことではないんでしょうか?

編集 削除
hoge  2006-05-02 22:44:37  No: 61615  IP: 192.*.*.*

Blueさんどうもです
リストビューにオープンファイルダイアログで選択した数は表示できますが
みな同じデータ(最後に選択したものです)なんですが・・・

編集 削除
Blue  2006-05-02 22:59:27  No: 61616  IP: 192.*.*.*

> みな同じデータ(最後に選択したものです)なんですが・・・
そんなはずないはずですが。
ためしに

ファイル名, s1, s2, s3, 34, s5

でリストビューに表示してみては?
ファイル名だけ違っているの場合、s1〜s5の設定の仕方に問題があると思います。

編集 削除
Blue  2006-05-02 23:01:25  No: 61617  IP: 192.*.*.*

あ、s1はファイル名でしたね。

> //    ファイル名の取得
> String^ s1 = openFileDialog1->FileName;
これ違いますよ。

String^ s1 = file

です。openFileDialog1->FileNameは1つのファイルしか指しません。

編集 削除
hoge  2006-05-03 00:40:14  No: 61618  IP: 192.*.*.*

どうも、お世話になりっぱなしですみません
//    ファイル名の取得

変更前  String^ s1 = openFileDialog1->FileName;
変更後  String^ s1 = file

これでやっと、複数のファイルが逐次読込まれたと思ったのですが、ところが
リストビューにはs1(フィル名)の内容は変化しているのですが
s2, s3, s4, s5の値は変化していませんでした
単一のファイルを選択して、表示させるとs1, s2, s3, s4, s5のデータ
が正しく表示されます

s2からs5のデータというのは、実は
//関数fooの呼出しで
foo(arg_1, arg_2);

で、グローバル変数に設定したデータを書換えます、いわゆる
戻値のない参照渡しを行っています。

char foo2[255];
char foo3[255];
char foo4[255];
char foo5[255];

分かりにくい説明で申し訳ございませんが、要約すると
関数fooを呼び出すと
下記のfoo2〜foo5の値が変化している仕組みです

//変数foo2の参照している変数を初期化
String^ s2 = gcnew String( foo2 );

//変数foo3の参照している変数を初期化
String^ s3 = gcnew String( foo3 );

//変数foo4の参照している変数を初期化
String^ s4 = gcnew String( foo4 );

//変数foo5の参照している変数を初期化
String^ s5 = gcnew String( foo5 );

編集 削除
Blue  2006-05-03 01:05:25  No: 61619  IP: 192.*.*.*

arg_1,arg_2の値が同じであれば、結果も同じになるんですよね?
そのファイルの中身の1バイト目と2バイト目に相違はちゃんとありますか?


それと、そろそろ デバッグ してみてはいかがでしょうか?
関数fooを呼び出すところあたりに ブレイクポイント を仕掛けて、
各変数の値をチェックしてみたほうが良いです。

編集 削除
hoge  2006-05-03 02:31:20  No: 61620  IP: 192.*.*.*

Blueさんいつも遅くまですみません
>arg_1,arg_2の値が同じであれば、結果も同じになるんですよね?
はいその通りです

そのファイルの中身の1バイト目と2バイト目に相違はちゃんとありますか?
>バイナリエディタ(Strings)で相違は確認しています

>それと、そろそろ デバッグ してみてはいかがでしょうか?
>関数fooを呼び出すところあたりに ブレイクポイント を仕掛けて、
>各変数の値をチェックしてみたほうが良いです。
ありがとうございます、頭が少し冷めてきました
デバッグしてみて問題点が少しづつですが浮上がってきました

ファイル1、ファイル2の二つのファイルを開くようにデバッグして検証してみましたところ

1    for each文は正常に働いています
2    関数の呼出し、変数への書込み(参照渡し)も正常です

問題は、最初にファイルを開いてarg_1、arg_2(c[0]、c[1])にデータをセットする時
本来ファイル1のデータがセットされなければならないのですが、
なぜか、ファイル2の値が設定されていました、多分ファイルを開くタイミングまたは
位置に問題がありそうな気がします、もう少し探って見ます。

編集 削除
hoge  2006-05-03 11:30:18  No: 61621  IP: 192.*.*.*

あらためてデバッグしていても
現在オープンしているファイルの動きに不自然な挙動は
見当たりませんでした、for〜eachのブロックに突入すると、正しく
アクティブになるファイル(sの値)も変化しています、しかし
変数cの値が(c[0]〜c[24])全然変化していません、
当然arg_1、arg_2にも変化はないので結果s1以外のs2〜s5は変化する
すべもありません、そこで一度cを初期化してみようと

array< unsigned char >^ c = nullptr;

としてfor〜eachのブロック直下に置いたら
: error C2065: 's' : 定義されていない識別子です。
となり

関数を呼出した後のに置きますと
: error C2374: 'c' : 再定義されています。2 回以上初期化されています。
となりました

配列cを初期化する良い方法はございませんか?

編集 削除
hoge  2006-05-03 14:08:01  No: 61622  IP: 192.*.*.*

配列の初期化は無理なようなので、起点をかえて考えていましたら、うっかり
見過ごしていました

//ファイルストリームの設定
Stream^ file;

for each ( String^ file in openFileDialog1->FileNames )

このようにファイルストリーム名とfor eachブロック内で使う変数名が一緒だと下記のようなエラーが出ていたので
current_fileという変数を使用していたのですが、これですと読込まれるデータは
いつも一緒という事にはならないのでしょうか?
分かる方が居られたら御教授お願いいたします。


以下、ファイルストリーム名とfor eachブロック内の変数が同じ場合のエラーです

error C2664: 'System::IO::BinaryReader::BinaryReader(System::IO::Stream ^)' : 1 番目の引数を 'System::String ^' から 'System::IO::Stream ^' に変換できません。(新しい機能 ; ヘルプを参照)

: error C2440: '=' : 'System::IO::Stream ^' から 'System::String ^' に変換できません。

: error C2039: 'Close' : 'System::String' のメンバではありません。

: error C2065: 'current_file' : 定義されていない識別子です。

編集 削除
Blue  2006-05-03 22:28:54  No: 61623  IP: 192.*.*.*

> 以下、ファイルストリーム名とfor eachブロック内の変数が同じ場合のエラーです
for each のブロックの中では、fileという変数は String^型 として認識されます。ブロックの外側で宣言した Stream^型 の file という変数とはまったく別物ということになります。
ためしに、for each のブロックが終わったあとに、fileという変数を使うと
Stream^型として扱われるのがわかると思います。
(というか、これは本当にC言語の基本中の基本です。)


それと、ブロック内で変数宣言した場合、そのブロックが終われば、変数宣言された変数はソコで終わりです。それはループでも同じです。
(これも、C言語の基本)

Testプログラム)
#include <iostream>
struct test{ 
    test() { std::cout << "test コンストラクタ" << std::endl; }
    ~test() { std::cout << "test デストラクタ\n" << std::endl; }
};
int main(){
     for ( int i = 0; i < 10; i++ ){
          test t;
     }
     return 0;
}

ReadByteが結局一番怪しいそうです。きちんと読んでいるファイルが該当のものなのかを確認する必要がありそうです。
(24バイト読み込んでいるところにブレイクポイントを張って、そのファイルを実際バイナリエディタで開いて先頭24バイトが同じか確認してみる)

> バイナリエディタ(Strings)で相違は確認しています
本当にありますか?
テキストで比較すると、例えば UTF-8 コードのテキストファイルの場合
全てのファイルの1,2バイト目は BOM に当たるのですべて同じになります。
そういう恐れはないでしょうか?

それと、先頭2バイトしか使わないのであれば、24バイト読み込む必要性がわかりません。

listView1->Items->Clear();
for each ( String^ file in openFileDialog1->FileNames )
{
     // ファイルオープン
     IO::BinaryReader^ binReader = gcnew IO::BinaryReader( IO::File::OpenRead( file ) );
     //arg_1, aarg_2初期化
     arg_1 = binReader->ReadByte();
     arg_2 = binReader->ReadByte();

// 省略

    // アイテム追加
    array< String^ >^ item = { file, s2, s3, s4, s5 };
    listView1->Items->Add( gcnew ListViewItem( item ) );
}

でいいような。

編集 削除
hoge  2006-05-04 08:21:45  No: 61624  IP: 192.*.*.*

Blueさんどうも、解決していただきありがとうございました

> Stream^型として扱われるのがわかると思います。
Stream^型データの概念がいまひとつ理解できていませんでした、

> 本当にありますか?
ターゲットのバイナリファイルはSHIFT-JISで書かれていることはあらかじめ
確認していましたので、間違ってはいませんでした

> それと、先頭2バイトしか使わないのであれば、24バイト読み込む必要性がわかりません。
実際には24Byteのデータなのですが、アルゴリズムさえ分かれば同じと考え省略していまいした、一言添えるべきでしたすみません。

大変世話になりました。

編集 削除