- ベストアンサー
リストビュー ⇔ 別ファイル構造体 の実現方法
VC++6.0、Win32 APIを用いて作成しているダイアログボックスを表示するアプリについてです。 パターン(パターン1~3までの3種類)が格納される列と、文字列(最大256バイト)が 格納される列の2列からなるリストビューがあります。 そのリストビューに新しい情報(行)を追加するたびに、その該当行の情報を 別ファイル(設定ファイル)の構造体に格納し、リストビューの変更・削除があれば、 その都度構造体を更新し(?)反映させたいです。 メモリも無駄なく、効率的に使いたいです。 また次回以降そのアプリを起動させる際には、設定ファイルの内容を読み込んでリストビュー の表示に反映させたいです。 正直、構造体は苦手なため解らないことだらけなのですが、パターンを格納するint型の変数と 文字列を格納する固定長256byteのchar型配列の変数が必要なのではと思っています。 後は、レコード数を格納するヘッダーも必要なのでしょうか・・・ メモリの取得、開放、移動方法はいまいち分かりません。 現在は、ラジオボタン(パターン用)とエディットボックス(文字列用)を用いて、 リストビューに登録するところまでは出来ています。 変更や削除の機能も実装出来ています。 上記の処理を実現させるための詳細な流れを教えてください。 サンプルコードも載せて頂けると幸いです。 分かりにくい文章で申し訳ありませんが、よろしくお願い致します。
- みんなの回答 (16)
- 専門家の回答
質問者が選んだベストアンサー
★データ数が100個ですか。 ・それなら全データの書き出しで良いでしょうね。 どうせデータ入力を人間が行い『追加』、『更新』、『削除』ボタンを押すだけの 使い方なのでしょ。ボタンを離したら瞬時に書き出しも終了していますよ。 ・また、読み込みはプログラムの起動時に1回だけ行えば良いです。 あとはリストビューにデータがありますので操作のたびにファイルに書き出すだけで データを一致させられます。 ・よって作成する関数は2つです。 1つ目はファイルからリストビューに読み込む関数。 2つ目はリストビューからファイルに書き出す関数。 プロトタイプを載せると (1)BOOL readFile( HWND hDlg, INT nID, LPCTSTR lpFname[] ); (2)BOOL writeFile( HWND hDlg, INT nID, LPCTSTR lpFname[] ); とまります。引数は HWND hDlg…ダイアログのウインドウハンドル INT nID……リストビューのID LPCTSTR lpFname[]…ファイル名 で戻り値は正常なら TRUE、エラーなら FALSE を返す仕様です。 こんな感じどう。 ・ファイル形式は CSV にしましょう。 そうすれば Excel でやり取りが出来たり、データの確認用としても使えます。 CSV 形式は各データをカンマ文字(,)で区切っただけのテキストです。 今回は『パターン番号』、『文字列』の2つをカンマ文字で区切って行単位で読み書きします。 下にそのサンプルを載せておきます。 サンプル: // ファイルからリストビューに読み込む BOOL readFile( HWND hDlg, INT nID, LPCTSTR lpFname[] ) { char buff[ 256 ]; FILE *fp; int type; int no; if ( (hDlg = GetDlgItem(hDlg,nID)) != NULL ){ if ( (fp = fopen(lpFname,"r")) != NULL ){ for ( no = 0 ; fscanf(fp,"%d,%s\n",&type,buff) == 2 ; no++ ){ /* ★ここでリストビューに追加する処理★ no:リストビューの行位置を表す(0-99) type:パターン番号(1-3) buff:固定文字列 hDlg:リストビューのウインドウハンドル ※type のデータ値が 1~3 かチェックする処理を入れても良いかも。 */ } fclose( fp ); return TRUE; } } return FALSE; } // リストビューからファイルに書き出す BOOL writeFile( HWND hDlg, INT nID, LPCTSTR lpFname[] ) { char buff[ 256 ]; FILE *fp; int type; int max; int no; if ( (hDlg = GetDlgItem(hDlg,nID)) != NULL ){ if ( (fp = fopen(lpFname,"w")) != NULL ){ max = ListView_GetItemCount( hDlg ); for ( no = 0 ; no < max ; no++ ){ /* ★ここでリストビューから取得する処理★ no:リストビューの行位置を表す(0-99) type:パターン番号(1-3) buff:固定文字列 hDlg:リストビューのウインドウハンドル ※type のデータ値が 1~3 かチェックする処理を入れても良いかも。 */ fprintf( fp, "%d,%s\n", type, buff ); } fclose( fp ); return TRUE; } } return FALSE; } その他: ・上記のコメント部分にリストビューとのやり取りを記述します。 readFile() 関数では ListView_InsertItem()、ListView_SetItem() のマクロ関数を 使って追加していきます。 writeFile() 関数では ListView_GetItem() のマクロ関数を使って取得します。 ・リストビューの設定と取得は別のサブ関数として作っておくと見やすくなります。 関数のプロトタイプは次のような感じで。 (1)BOOL setListViewData( HWND hWnd, int no, int pattern, LPCTSTR lpString ); (2)BOOL getListViewData( HWND hWnd, int no, int *pattern, LPTSTR lpString ); ※引数の no=行位置、pattern=パターン番号、lpString=固定文字列となります。 ※戻り値は正常に設定や取得できれば TRUE、エラーなら FALSE を返す仕様です。 ・使い方は WM_INITDIALOG の場所で readFile() 関数を1回だけ使います。 その後は『追加』、『更新』、『削除』ボタンが押されたときの処理に1行だけ writeFile() 関数で書き出す行を追加すればよいだけです。 ソート: ・ソートは 100 個の構造体配列を malloc() で確保してその配列にリストビューの 内容をすべて取り込みます。あとは qsort() 関数で昇順、降順のソートを行ます。 ソート結果をリストビューのアイテムをすべてクリアしてから再登録するように すれば楽に出来ると思います。なので malloc() で確保するときに ListView_GetItemCount() でデータ数を取得しておきます。構造体配列のソートは qsrot() 関数と比較関数を 用意すれば出来上がりです。あとはヘッダの『列』をクリックすればソートできるように 機能追加すれば良いです。 ・以上。
その他の回答 (15)
失礼しました、訂正です。 (誤) また、文字列メモリは保持のため 必ずアプリケーション終了時に行います。 (正) また、文字列メモリの解放は保持のため 必ずアプリケーション終了時に行います。 あとジャーナルデータは例えば個数を決めておきます (個数=ロールバック回数+ロールフォワード回数) N=記録可能最大個数(回数) M=現在回数 &Data[i]=該当回数目ローデータとした時、 回数 M が N を超えた時に、 for (i=0;i<N-1;i++) Data[i]←Data[i+1]の処理; Data[N-1]←最新データ とすればいくはずです。 あくまで考え方なので…(大汗)
>ところで、構造体を使わないことによるデメリットは、ソートを行え >ない以外に何かありますでしょうか。 リストビュー上で昇順・降順ソートやドラッグソート (クリックで1項目を移動させるもの) を行ってどんな順列になろうとも 書き込み前に先頭から書き出せば関係ありません。 しかし“ソート履歴”や“履歴リコール機能” を実装する際は、単純にLV_ITEM 構造体を個数分用意して インデックスの状態をジャーナルデータとして記録すれば あの関数も、この構造体も…の必要ありません。 ジャーナルデータの構造は、シンプルです。 アプリケーション起動後から終了までの間... (変更回数 / アクションフラグ=0:追加 1:入れ替え 2:変更 3:削除 / 対照インデックス / パターン / 文字列) ※パターン、文字列は変更の時だけ記録、以外は空記録します。 また、文字列メモリは保持のため 必ずアプリケーション終了時に行います。 1/0/0 … インデックス0を追加 2/0/1 … インデックス1を追加 3/1/1,0 … インデックス1と0入れ替え) 4/3/1 … インデックス1を削除 ・・・ ※ソートは1項目ずつ変更を記録します。 無駄に多くなるように思えますが、どっちみち最大数と同じ個数 ですから、支障はないです。 現在N回目とした場合、“M回前の状態に戻す”(M>0)なら A/B/C ・N ポイントから N-M+1 ポイントまで以下を繰り返す ・もし B が... 0:C のインデックスを削除(切り詰め) 1:C の2つのインデックスを入れ替え 2:C のインデックスのパターンと文字列をコピー 3:C のインデックスを再追加 ・・・ と言った感じでロールバック/ロールフォワード 処理をします。 不具合や抜け目があるかもしれませんが参考として。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★時間差ですね。驚き! >ところで、用意する構造体は「ANo.7」と同じものでよろしいのでしょうか? ↑ 回答 No.13 の紹介ページを読んで見て下さい。 ソートは比較関数を作成すれば簡単に機能追加できます。 よってソート用に構造体を用意する必要はありません。 >それと、for文の中の処理はそれぞれ何行くらいになりますでしょうか? ↑ 20行~30行程度だと思うよ。 だから別にサブ関数にしなくても良いが、分かりやすく関数にした方が良いです。 また、サブ関数を作ればいろいろとお応用も利きますから。 下にちょっとしたサンプルを載せておきます。 サンプル: // リストビューに列データを挿入/追加 VOID subListViewItem( HWND hWnd, long pos, LPCTSTR lpString ) { static HWND hCtrl; static int subPos; LV_ITEM item; item.mask = LVIF_TEXT; item.iItem = pos; item.iSubItem = subPos; item.pszText = (LPTSTR)lpString; if ( hWnd != NULL ){ item.iSubItem = subPos = 0; hCtrl = hWnd; ListView_InsertItem( hCtrl, &item ); } else{ ListView_SetItem( hCtrl, &item ); } subPos++; } // 使い方 BOOL setListViewData( HWND hWnd, INT nPos, INT nPattern, LPCTSTR lpString ) { TCHAR szPattern[ 16 ]; wsprintf( szPattern, TEXT("%d"), nPattern ); subListViewItem( hWnd, nPos, szPattern ); // 最初の列 subListViewItem( NULL, nPos, lpString ); // 続きの列 return TRUE; } その他: ・上記のサンプルを参考に getListViewData() の関数も作ってみて下さい。 そして for() 文の中に setListViewData() や getListViewData() を記述すれば 良いだけです。 readFile() 関数には setListViewData() 関数を for() 文の中で記述。 writeFile() 関数には getListViewData() 関数を for() 文の中で記述。 という事です。 ・以上。あとは自分でお作り!
お礼
<続き> ただ、今回は特に構造体を使うことなくできてしまいました。 せっかく構造体を教えていただいたのに使わなくてすみません。 とにかく、まずはある程度形にしたいと思いまして・・・ 動いたときは本当に嬉しかったです。 もちろん、この後構造体を作成し、ソートに挑戦したいと思います。 教えていただいたサイトを参考に、なんとか完成させたいと思います。 ところで、構造体を使わないことによるデメリットは、ソートを行えない以外に何かありますでしょうか。 ご教授の程、よろしくお願いいたします。
補足
多少変数名など変更させていただきましたが、教えていただいた通りにやりましたら、うまくいきました。 本当にどうもありがとうございました。 以下が、ソースの一部です。 //********************************************************************** // // リストビューからファイルに書き出す関数 // //********************************************************************** BOOL WriteFile(HWND hList, const char *lpFname) { char szPattern[2]; char szBuff[256]; FILE *fp; int max; int no; int nPattern; if((fp = fopen(lpFname, "w")) != NULL){ max = ListView_GetItemCount(hList); for(no = 0; no < max; no++){ ListView_GetItemText(hList, no, 0, szPattern, sizeof(szPattern)); ListView_GetItemText(hList, no, 1, szBuff, sizeof(szBuff)); if(!strcmp(szPattern, "1")){ nPattern = 1; }else if(!strcmp(szPattern, "2")){ nPattern = 2; }else if(!strcmp(szPattern, "3")){ nPattern = 3; } fprintf(fp, "%d,%s\n", nPattern, szBuff); } fclose(fp); return TRUE; } return FALSE; } //********************************************************************** // // リストビューに列データを挿入/追加 // //********************************************************************** void subListViewItem(HWND hWnd, int pos, const char *szBuff) { static HWND hCtrl; static int subPos; LV_ITEM item; item.mask = LVIF_TEXT; item.iItem = pos; item.iSubItem = subPos; item.pszText = (LPTSTR)szBuff; if(hWnd != NULL){ item.iSubItem = subPos = 0; hCtrl = hWnd; ListView_InsertItem(hCtrl, &item); }else{ ListView_SetItem(hCtrl, &item); } subPos++; } // 上記関数を使う関数 BOOL setListViewData(HWND hWnd, INT nPos, int nPattern, const char *szBuff) { char szPattern[16]; wsprintf(szPattern, "%d", nPattern); subListViewItem(hWnd, nPos, szPattern); // 一列目 subListViewItem(NULL, nPos, szBuff); // 二列目 return TRUE; }
- Oh-Orange
- ベストアンサー率63% (854/1345)
★追記。 ・ソートは ListView_SortItems() マクロ関数で出来ました。 次のページにソートの方法が載っています。 比較関数さえ作れば簡単に出来ます。 http://www.kumei.ne.jp/c_lang/sdk2/sdk_110.htm→『第110章 リストビューのソート』 ・以上。参考にどうぞ。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★もっと簡単に考えてみますか。 ・単純に追加、挿入、削除の操作を行ったらリストビューより全データを 順番にファイルに書き出した方が楽かもしれないね。そしてプログラムの 起動時にだけファイルから読み込む処理を1回行う。 ・この考えなら簡単に実装できると思います。 データの数があまり多くないのならばこれが一番お勧めですね。 メモリも無駄なく、少量なら効率的かな。 ・上記の方法ならファイルの読み込み関数、ファイルの書き込み関数の2つを 用意すればもう出来ますね。リストビューの追加、削除などは出来ている ようなので。 補足要求: ・kenkenpo さんはファイル構造をどのように考えているのでしょうか? 上記の簡単は方法ならテキストにした方が良いかもしれませんね。 ファイル構造的には1行が (1)パターン番号 (2)固定文字列 の2つがあれば良いので CSV 形式と同じカンマ区切りにするのはどうでしょうか。 CSV なら Excel でも読み込めたりします。 ・データの数が 1000 個ぐらいなら全データの読み書きでもいいと思います。 とにかくファイル構造はテキスト形式、バイナリ形式、データ量はどれぐらい。 追加、削除のときに全データをファイルに書き出して反映させるタイプなのか、 追加、削除のときに1データをファイルに書き出して反映させるタイプなのか どちらを考えていますか?→実装が簡単な方は全データの書き出しですけど。 ・以上。補足要求します。はっきりしないと回答が混乱しちゃいます。決めて。
補足
お世話になっております。 こんな時間にお時間を取らせてしまってすみません。書き込みありがとうございました。 正直な所、あんなに関数が必要になるとは思っていませんでしたので、 プロトタイプ宣言を見て圧倒されております。 補足をさせていただきます。 ・リストビューの指定行への挿入は考えておりません。追加と変更と削除のみです。 (ただ、できれば列をクリックすることで、パターンごとのソートができたらとは思っています。 少し調べてみましたところ、MFCを使わないと何だか難しそうな感じですが・・・) ・ファイル形式にこだわりはありません。と言いますか、どちらにどの様な メリット・デメリットがあるのかもよく分かっていません。なるべく簡単な方法を希望します。 CSV形式で問題ありません。 (ただ、VTClientさんへの最初のお礼でも書かせていただいたのですが、 リストビューの内容を登録したファイルは、別のアプリからも使いたいと思っています。 具体的には、パターンごとに用意された3つの関数にそれぞれの文字列を引数として 渡すというものです。その処理を実装しやすいほうがいいです。) ・データ数は、たぶん多くても100個程度かと思われます。 ・あまりプログラムのことが分からないなりに、毎回全データを読み書きしていたら、 処理速度が落ちるのかなと漠然と思ってはいたのですが、それがあまり気にならない程度でしたら 、実装が簡単な「全データをファイルに書き出して反映させるタイプ」の方が良いかもしれません。 何度もお手数をお掛けしてしまい申し訳ありませんが、よろしくお願い致します。
No.9 です。 以下にリストを載せました。 一応これでエラーはなくなるはずです。 変数名をかえて、質問者さん用の処理を(ボタン無効など)登録、変更、削除の処理に 追加してください。 尚、対応変数は、No.4 を参照して下さい。 >例えば、fopenしてfread、fwriteするようなことはできますでしょうか。 for (i=0;i<=N;i++) fwrite(…"%d %d %d %d %d %d",FList[i].index,FList[i].cur,FList[i].type,FList[i].back.index,FList[i].next.index,FList[i].size); 配列位置、対応位置、パターン、1つ前の配列位置、1つ先の配列位置、2桁目の文字データサイズ (1桁目文字列は、パターンの値で判断) という要領です。 同様に読み込みをしたら、 FileList *p; int cur; SetFocus(list); ListView_DeleteAllItems(list); MessageBox(hDlg,"確認します","列挙",0); for (cur=0;FList[cur].cur;cur++) if (cur>=N) return 1; for (p=&FList[cur];p->cur!=-1;p=p->next) { LVITEM cell; char txBuf[20]; cell.mask=LVIF_TEXT; cell.iItem=p->cur; cell.iSubItem=0; cell.pszText=txBuf; switch (p->type) { case 1: strcpy(txBuf,"パターンA"); break; case 2: strcpy(txBuf,"パターンB"); break; case 3: strcpy(txBuf,"パターンC"); } ListView_InsertItem(list,&cell); cell.iSubItem=1; cell.pszText=p->txBuf; ListView_SetItem(list,&cell); } でリストビューに表示反映できます。 >VTClient さんの4つの回答(アドバイス)をよく読むとリストビューと同じデータを… Oh-Orange さん、ご指摘ありがとうございます! 間違いなくおっしゃるとおりですね。 普通に考えれば、Oh-Orange さんがご提示されましたNo5の内容が 私自身も最妥当と思います。 しかしよく考えてみれば、質問者さんの内容ですと、 「構造体を考慮したものとして…」 と示唆しています。 リストビューよりファイル書き出し前に最新状態を拾うのであれば 単に、リストビューアイテム構造体を先頭からシーケンシャルに周して書き出し (読み込みはその逆) すれば良いだけで、そもそもユーザー定義の構造体の必要がありませんよね? その上で構造体視点で今回、考えたものです。 ただもし目的の反映さえできればあとはどーでも…と質問者さんお考えでしたら (私が)でしゃばったものとしてすみません!
お礼
>しかしよく考えてみれば、質問者さんの内容ですと、 >「構造体を考慮したものとして…」 >と示唆しています。 >リストビューよりファイル書き出し前に最新状態を拾うのであれば >単に、リストビューアイテム構造体を先頭からシーケンシャルに周し>て書き出し >(読み込みはその逆) >すれば良いだけで、そもそもユーザー定義の構造体の必要がありませ>んよね? >その上で構造体視点で今回、考えたものです。 >ただもし目的の反映さえできればあとはどーでも…と質問者さんお考>えでしたら >(私が)でしゃばったものとしてすみません! VTClientさんがでしゃばったなんてとんでもございません。 ただ自分が、勝手に構造体を使えばいいんじゃないかなと思い込んでいただけでして、 現に構造体を使わずに目的の機能を実現できました。 VTClientさんがおっしゃるとおり、目的の反映さいできれば方法はどんなものでも良かったのです。 大変ご迷惑お掛けしました。失礼いたしました。 しかしながら、自分は構造体やポインタの理解がまだまだ足りていないので、 VTClientさんが提示してくださったソースコードは大変参考になります。 まだ完全には理解できておりませんが、隅々まで理解できるように頑張ります。 この度は、本当にありがとうございました。
No.1,2,3,4 です。 冒頭で追加 #define N 100 // 最大項目数 struct FileList{ int index;// 配列位置 int cur;// 対応位置 char* txBuf; int size; int type;// パターン FileList *back; FileList *next; } FList[N+1]; case WM_INITDIALOG: for (int i=0;i<=N;i++) { FList[i].index=i; FList[i].size=0; FList[i].cur=-1; FList[i].back=i?&FList[i-1]:&FList[i]; FList[i].next=i<N?&FList[i+1]:&FList[i]; }・・・ case IDC_BUTTON1://登録 { LVITEM cell; FileList *p,*p2,*p3; int cur,cur2; char txBuf[15]; sum=ListView_GetItemCount(list); // index=ListView_GetSelectionMark(list)+1;// これで任意位置追加 index=sum;//これで常時最後位置追加 for (cur2=0;FList[cur2].cur!=-1;cur2++) if (cur2>N-2) return 1; for (cur=cur2+1;FList[cur].cur!=-1;cur++) if (cur>N-1) return 1; cell.mask=LVIF_TEXT; cell.iItem=index; cell.iSubItem=0; cell.pszText=txBuf; switch (state) { case 1: strcpy(txBuf,"パターンA"); break; case 2: strcpy(txBuf,"パターンB"); break; case 3: strcpy(txBuf,"パターンC"); } ListView_InsertItem(list,&cell); cell.iSubItem=1; GetDlgItemText(hDlg,IDC_EDIT1,txBuf,sizeof(txBuf));// txBuf=登録文字列 p3=&FList[cur]; p2=&FList[cur2]; if (p2->cur==-1) p2->next=p3; if (sum) for (cur=0;FList[cur].cur!=(index?index-1:0);cur++);else cur=0;// cur=最後の行 p=&FList[cur]; p2->type=state; p2->txBuf=(char*)malloc(p2->size=strlen(txBuf)+1); strcpy(p2->txBuf,txBuf); if (index) { p->next->back=p2; p->next->next=p2->next; p2->back=p; p2->next=p->next->cur==-1?p3:p->next; p->next=p2; p2->cur=index; for (p=p2->next;p->cur!=-1;p=p->next) p->cur++; } else { FileList *p4; p->back=p2; p2->next=sum?p:p3; p2->back=p2; for (p4=p;p4->next->cur!=-1;p4=p4->next); p4->next=p3,p3->next=p3,p3->back=p4; for (;p->cur!=-1;p->cur++,p=p->next); p2->cur=index; } ListView_SetItem(list,&cell); SetFocus(list); return 1; } case IDC_BUTTON2://変更 { LVITEM cell; FileList *p; int cur; char txBuf[15]; cell.mask=LVIF_TEXT; index=ListView_GetSelectionMark(list); if (index<0) return 1; cell.iItem=index; cell.iSubItem=0; cell.pszText=txBuf; switch (state) { case 1: strcpy(txBuf,"パターンA"); break; case 2: strcpy(txBuf,"パターンB"); break; case 3: strcpy(txBuf,"パターンC"); } ListView_SetItem(list,&cell); cell.iSubItem=1; GetDlgItemText(hDlg,IDC_EDIT1,txBuf,sizeof(txBuf)); for (cur=0;FList[cur].cur!=index;cur++); p=&FList[cur]; p->type=state; if (p->size) free(p->txBuf); p->txBuf=(char*)malloc(p->size=strlen(txBuf)+1); strcpy(p->txBuf,txBuf); ListView_SetItem(list,&cell); SetFocus(list); return 1; } case IDC_BUTTON3://削除 { FileList *pf,*p; int cur,sum; index=ListView_GetSelectionMark(list); if (index<0) return 1; sum=ListView_GetItemCount(list); ListView_DeleteItem(list,index); for (cur=0;FList[cur].cur==-1;cur++); for (;FList[cur].cur!=index;cur++) if (cur>N-1) return 1; if ((pf=&FList[cur])!=pf->next) for (p=pf->next;p->cur!=-1;p=p->next) p->cur--; pf->next->back=pf->back; pf->back->next=pf->next; if (pf->size) free(pf->txBuf); pf->size=0,pf->cur=-1; SetFocus(list); return 1; }
- Oh-Orange
- ベストアンサー率63% (854/1345)
続き。 ★エラーコード関数 dataGetError :エラーコードの取得 dataSetError :エラーコードの設定 ★データ管理関数 dataOpen :ファイル構造の管理データを開く dataClose :ファイル構造の管理データを閉じる dataAttach :リストビューのウインドウハンドルをアタッチする ★基本操作関数 dataAppend :ファイル構造とリストビューに追加 dataInsert :ファイル構造とリストビューに挿入 dataChange :ファイル構造とリストビューに更新(上書き) dataDelete :ファイル構造とリストビューに削除 ★サブ関数セット1 subIDCheck :ID文字列のチェック subAlloc :ファイル名,データ並び,データ有無を確保 subReadOpen :ファイルからヘッダ構造をオープン subCreateOpen :新規ファイルからヘッダ構造をオープン ★サブ関数セット2 subListViewItem :リストビューに列データを挿入/追加 subInsertData :リストビューに1つの構造体データを挿入 subDataExpand :ファイル構造のデータブロックを拡張 subEmptySearch :ファイル構造から空きデータを検索 subRead :ファイル構造からデータを読み込む subWrite :ファイル構造にデータを書き込む 解説 ・最低でも上記の関数群を用意する必要があります。 この関数群はファイル構造のバイナリをオープンして管理ヘッダを作成してから プログラムが終了するまでファイルをオープンした状態で使います。 そしてこの関数群で1つの構造体データをファイル構造とリストビューの同時に 追加、挿入、削除、更新(上書き)する操作になります。 ・上記の関数群をしっかりと用意さえすれば GUI のボタンから追加、挿入、削除 などの操作が楽になります。またバグを発生させないですみます。おまけにメモリも リストビューにデータの実体を管理させるので節約できます。 ・重要なのはリストビューに1構造体のデータを管理させて、そのデータの順番と ファイル構造の書き込み位置だけを管理するシステム作りが必要なのです。 C++ 言語のクラスとして設計するともっと汎用性が良くなります。 その他: ・1つ1つ作成していけば、最後の GUI 操作が楽になりますよ。 とにかくリストビューとファイル構造を関連付けて管理する必要があります。 今回、紹介した構造体と関数群はディスク・ファイルの管理法である FAT などを 参考にしてみました。FAT=『File Allocation Table』です。 ・紹介した構造体(head_t,data_t)と回答 No.6 で図解したファイル構造と関数群の プロトタイプ宣言からリストビューとファイル構造の操作システムを構築するための 整理と理解をして下さい。多分、初めて実装するので時間がかかると思いますが 1個ずつ時間をかけ作っていけばいいと思います。 私もちょっとだけ作成してみよう。試しに。 ・最後にデータ量はどれぐらいですか? それにより簡略化できるかもしれない。 紹介したものは大量のデータでも扱えるように考えています。 大まかなフローチャート: (1)ファイルをオープンして管理ヘッダを作成 (2)管理ヘッダのデータ並び情報を読み込む (3)管理ヘッダのデータ有無情報を読み込む (4)GUI ボタンなどで追加、挿入、削除、更新の操作を行う (5)管理ヘッダをファイルに保存してクローズ 以上。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★ファイルはバイナリです。 と回答 No.6 で書いているのですけど。 ・idString はファイル形式を識別するために用意したものです。 例えば BMP ファイルは先頭に "BM" という識別文字が必ずあります。 プログラムではこの識別文字やヘッダ情報から本当に BMP ファイル形式かどうかを 判別します。これと同じような考えでリストビュー構造のファイル形式に識別文字列の idString を用意したのです。だから無くても良いがあった方が便利かもという事だ。 >「データの構造体」の文字列は2つではなく1つです。 パターン番号、固定文字列の2列と解釈すべきでしたか。 失礼しました。国語苦手! >主要な部分のソースコードだけでも提示していただけると幸いです。 主要なソースコードだけでは多分理解できないでしょう、すべてのソースを サンプルで載せることが出来ません。 かなり関数が多くなるので。 その代わりに関数のプロトタイプ宣言を載せます。 あと新しい構造体も。修正版。 // ヘッダ管理の構造体を宣言 typedef struct head_t { // サイズ 60 バイト char idString[ 16 ]; // ファイル形式のID文字列 long headSize; // ヘッダのサイズ long allSize; // 全データのサイズ long oneSize; // 1データのサイズ long nowData; // 登録データの個数 long maxData; // 最大データの個数 HWND hWnd; // リストビューのハンドル FILE *fp; // ファイル・ポインタ char *fname; // データのファイル名 long *order; // データの並び順配列 char *exist; // データの有無用配列 char *alloc; // 可変データ領域全体 } head_t; // データ管理の構造体を宣言 typedef struct data_t { // サイズ 260 バイト int pattern; // パターン番号 char str[ 256 ]; // 固定長文字列 } data_t; // エラーコード関数 extern int dataGetError( void ); extern int dataSetError( int error ); // データ管理関数 extern head_t *dataOpen( const char dataFile[] ); extern head_t *dataClose( head_t *hp ); extern head_t *dataAttach( head_t *hp, HWND hWnd ); // 基本操作関数 extern int dataAppend( head_t *hp, data_t *dp ); extern int dataInsert( head_t *hp, long pos, data_t *dp ); extern int dataChange( head_t *hp, long pos, data_t *dp ); extern int dataDelete( head_t *hp, long pos ); extern int dataDefrag( head_t *hp ); // サブ関数セット1 static int subIDCheck( const char string[] ); static int subAlloc( head_t *hp, const char dataFile[] ); static head_t *subReadOpen( head_t *hp, const char dataFile[] ); static head_t *subCreateOpen( head_t *hp, const char dataFile[] ); // サブ関数セット2 static void subListViewItem( head_t *hp, long pos, const char string[] ); static void subInsertData( head_t *hp, long pos, data_t *dp ); static int subExpand( head_t *hp ); static long subEmptySearch( head_t *hp ); static void subRead( head_t *hp, long pos, data_t *dp ); static void subWrite( head_t *hp, long pos, data_t *dp ); まだ続く。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★追記。 ・もう少しファイル構造を分かりやすく書いて見ました。 なお、ファイルタイプは独自のバイナリ・データとします。 +------------+ |管理ヘッダ情報|←固定長ヘッダ +------------+ |データ並び情報|←可変長データ +------------+ |データ有無情報|←可変長データ +------------+ |データNo.0000 |←固定長データ(先頭) +------------+ |データNo.0001 | +------------+ : +------------+ |データNo.1023 |←固定長データ(最後) +------------+ ※No.0000~No.1023 が実際の構造体データのブロックです。可変ブロックです。 解説: ・データファイルの構造は上から順に (1)管理ヘッダ情報(固定長ヘッダ) (2)データ並び情報(インデックス配列) (3)データ有無情報(有無フラグ配列) (4)データブロック(可変長データ) となります。 ・1つのファイルにパックする場合はこんな感じですが (2)、(3)を別のファイルに 分離する方法もあります。こちらはデータを拡張するときに別ファイルが増加する だけなのでデータ件数が多い場合は拡張しやすくなると思います。 ・上記の『データ並び情報』がデータ(No.0000~No.1023)のインデックスになります。 このインデックスからデータが実際のファイル内の何処に位置するのかが分かります。 ディスク・システムで言うところの FAT にあたります。 ・あと『データ有無情報』はデータ(No.0000~No.1023)が空きデータか、有効データかの フラグを管理しているテーブルです。このテーブルを用意したのは削除と登録(追加) するときに効率よく(高速に)空きデータを探せるためです。安全のためにデータブロック にも空きマーク(削除フラグ)をセットしておきます。これによりデータブロックより 『データ有無情報』を正しく初期化できるようになります。 その他: ・ファイルのデータブロックがいったん拡張すると収縮することは出来ません。 収縮する場合はデータブロックのデフラグを行ってファイル構造を再構築する必要が あります。これは別の処理やプログラムなどで組み込めば良いでしょう。 ・以上。
お礼
この度は突然の図々しいお願いにも関わらず、詳細に渡る解説・アドバイスを、 ご丁寧に分かりやすくしていただき誠にありがとうございました。 しかしながら、今の自分では理解できない部分が多すぎるため、これだけのヒントを提示して頂きながら、 このままでは先に進めない状況です。大変情けなく感じております。 例えば、「idString…データのファイル形式を識別するための文字列」がどういうものなのか分かりません。 ファイル形式とは、「テキスト」とか「バイナリ」とかいうことでしょうか?(多分違うと思いますが・・・) 何から何まで教えていただくことは、丸投げしていることと同じですし大変心苦しいのですが、 主要な部分のソースコードだけでも提示していただけると幸いです。 もしそれが無理なようでしたら、この課題をやるにあたって参考になるサイトなどを紹介していただければと思います。 何から何までお世話になりっぱなしで本当に申し訳ないですが、どうぞよろしくお願いいたします。 (ちなみに、大したことではないのですが「データの構造体」の文字列は2つではなく1つです。 紛らわしい書き方ですみませんでした。)
- 1
- 2
お礼
何度もご丁寧にアドバイス頂き、本当にありがとうございます。 ところで、用意する構造体は「ANo.7」と同じものでよろしいのでしょうか? それと、for文の中の処理はそれぞれ何行くらいになりますでしょうか? 毎度お手数お掛けしますが、よろしくお願いいたします。