調子に乗って第2弾。
【課題2】ヒストグラムプログラムの作成
ファイルを1バイトずつ読み出し、ファイルの中で0〜255の値が使われている頻度を棒グラフで表示する。
また個々の数値が使われている頻度をローカルにテキストとして保存もできるようにする。
学習内容としては「ドキュメント・ビューアーキテクチャー」のさわりと、「オブジェクトのシリアル化」あたり。
(手順1)
MFC AppWizard(exe)でプロジェクト名を「MyHist」とする。
ステップ1/6でSDIを選択。あとはデフォルトのままでOK。プロジェクトファイル一式を生成する。
(手順2)
ドキュメントを操作するクラス(CMyHistDocクラス)にメンバ変数を追加する。
ClassViewのペインの中のDocに該当するクラス(ここではCMyHistDoc)上で右クリックし、
「メンバ変数の追加」をクリック。下記の2つの変数を追加する。
・int Counter[256] (データの出現頻度を記録する配列変数)
・CString FileName (ファイル名を保存する変数)
※ここでは話を簡単にするためPublicとしている。
(手順3)
上記2つの変数をCMyHistDocのコンストラクタ内で初期化しておく。
(手順4)
次にCMyHistDocのメンバ関数Serializeを編集する。
これはもともとCObjectの仮想関数として定義されているもので、MFCプログラミングでオブジェクトの内容をファイルに読み書きする時に使う「お約束」らしい。SerializeはArchiveクラスのオブジェクトを参照として渡され、これを使って色々なファイル処理を実装する。
またArchiveオブジェクトは内部でCFileクラスのオブジェクトと連携しているらしく、CFileクラスのメンバ関数を使うと更に細かく色々なファイル処理ができるとのこと。
上記のように単純なif-elseで書き込み用と読み込みように分かれている。
CArchiveクラスの代表的な関数は以下の通り。
またCFileクラスの主要メンバ関数は以下の通り
(手順5)
CMyHistクラスのOnDrawを編集して描画部分を作る。
(手順6)
ビルドして実行。
こんな感じになればOK。
※保存する際のファイル名はデフォルトで開いたファイル名なので、うっかりすると元のファイルの上に保存してしまうので、注意すること!(例題としてちょっと変だな、これ。)

参考書物:
山本信雄 著「プログラマ養成入門講座 VisualC++ 3 はじめてのMFCプログラミング」(翔泳社)
【課題2】ヒストグラムプログラムの作成
ファイルを1バイトずつ読み出し、ファイルの中で0〜255の値が使われている頻度を棒グラフで表示する。
また個々の数値が使われている頻度をローカルにテキストとして保存もできるようにする。
学習内容としては「ドキュメント・ビューアーキテクチャー」のさわりと、「オブジェクトのシリアル化」あたり。
(手順1)
MFC AppWizard(exe)でプロジェクト名を「MyHist」とする。
ステップ1/6でSDIを選択。あとはデフォルトのままでOK。プロジェクトファイル一式を生成する。
(手順2)
ドキュメントを操作するクラス(CMyHistDocクラス)にメンバ変数を追加する。
ClassViewのペインの中のDocに該当するクラス(ここではCMyHistDoc)上で右クリックし、
「メンバ変数の追加」をクリック。下記の2つの変数を追加する。
・int Counter[256] (データの出現頻度を記録する配列変数)
・CString FileName (ファイル名を保存する変数)
※ここでは話を簡単にするためPublicとしている。
(手順3)
上記2つの変数をCMyHistDocのコンストラクタ内で初期化しておく。
///////////////////////////////////////////////////////////////////////////// // CMyHistDoc クラスの構築/消滅 CMyHistDoc::CMyHistDoc() { // TODO: この位置に1度だけ呼ばれる構築用のコードを追加してください。 FileName = ""; int i; for(i=0;i<256;i++){ Counter[i]=0; } } /////////////////////////////////////////////////////////////////////////////
(手順4)
次にCMyHistDocのメンバ関数Serializeを編集する。
これはもともとCObjectの仮想関数として定義されているもので、MFCプログラミングでオブジェクトの内容をファイルに読み書きする時に使う「お約束」らしい。SerializeはArchiveクラスのオブジェクトを参照として渡され、これを使って色々なファイル処理を実装する。
またArchiveオブジェクトは内部でCFileクラスのオブジェクトと連携しているらしく、CFileクラスのメンバ関数を使うと更に細かく色々なファイル処理ができるとのこと。
///////////////////////////////////////////////////////////////////////////// // CMyHistDoc シリアライゼーション void CMyHistDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: この位置に保存用のコードを追加してください。 int i; char s[20]; // ファイルにデータを書き出す ar.WriteString(FileName); for(i=0;i<256;i++) { wsprintf(s,",%02X=%d回n",i,Counter[i]); ar.WriteString(s); } } else { // TODO: この位置に読み込み用のコードを追加してください。 CFile *cf; int i,FileLen; unsigned char BinData; // カウンタの値を初期化 for(i=0;i<256;i++) { Counter[i]=0; } // ファイル名と大きさを取得 cf=ar.GetFile(); FileName=cf->GetFileName(); FileLen=cf->GetLength(); // ファイルのデータを読み出し出現頻度をカウント for(i=0;i<FileLen;i++) { ar >> BinData;// 1バイトずつ読み出し Counter[BinData]++; } // ビューにドキュメント更新を伝える UpdateAllViews(NULL); } } /////////////////////////////////////////////////////////////////////////////
上記のように単純なif-elseで書き込み用と読み込みように分かれている。
CArchiveクラスの代表的な関数は以下の通り。
| Close | 書き込まれていないデータをフラッシュし、CFileから切り離す |
| Flush | 書き込まれていないデータをフラッシュする |
| operator << | ファイルからデータを読み込む |
| operator >> | ファイルにデータを書き込む |
| Read | ファイルから未加工のバイト列を読み込む |
| Write | ファイルに未加工のバイト列を書き込む |
| WriteString | ファイルに1行の文字列を書き込む |
| ReadString | ファイルから1行の文字列を読み込む |
| GetFile | ファイルに結び付けられたCFileオブジェクトのポインタを取得する |
| IsLoading | ファイルが読み出し用ならTRUE |
| IsStoring | ファイルが書き込み用ならTRUE |
またCFileクラスの主要メンバ関数は以下の通り
| GetLength | ファイルの長さ(バイト単位)を取得する |
| GetStatus | ファイルのステータス(作成日時や属性など)を取得する |
| GetFileName | ファイルのファイル名を取得する |
| GetFileTitle | ファイルのタイトル(ファイル名から拡張子を除いた部分)を取得する |
| GetFilePath | ファイルのフルパスを取得する |
(手順5)
CMyHistクラスのOnDrawを編集して描画部分を作る。
///////////////////////////////////////////////////////////////////////////// // CMyHistView クラスの描画 void CMyHistView::OnDraw(CDC* pDC) { CMyHistDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: この場所にネイティブ データ用の描画コードを追加します。 if(pDoc->FileName != "") { int i; CPoint pt; // ファイル名を表示 pDC->TextOut(0,0,pDoc->FileName); // ヒストグラム(棒グラフ)を表示 for(i=0;i<256;i++) { pt.x=0; pt.y=i+18; pDC->MoveTo(pt); pt.x=pDoc->Counter[i]; pDC->LineTo(pt); } } } /////////////////////////////////////////////////////////////////////////////
(手順6)
ビルドして実行。
こんな感じになればOK。
※保存する際のファイル名はデフォルトで開いたファイル名なので、うっかりすると元のファイルの上に保存してしまうので、注意すること!(例題としてちょっと変だな、これ。)

参考書物:
山本信雄 著「プログラマ養成入門講座 VisualC++ 3 はじめてのMFCプログラミング」(翔泳社)
コメント (17) |
トラックバック (3) |










