続・MFCでアプリケーションを作る
by K.I
2009/09/04
Index
- MFCでアプリケーションを作ろうと思って、 スケルトンを作るところまでは何とかできた。
- しかし、いざ実際にアプリケーションを作ろうとすると、いろいろ引っかかってしまう。
- ファイルダイアログで、ファイルの種類を複数指定出来るようにしたい。
- MainFrame,Document,Viewの使い分けは、どうすれば良いんだろう。
- いろいろ試行錯誤したことをメモしておこう。
- これは行き当たりばったりのプログラムなので、あまり参考にならないかも。
[top]
- ファイルの拡張子を指定するのは、IDR_MAINFRAMEの4番目5番目のパラメータを書き換えれば良いというのは分かった。
DialogBarTest\n\nDialog\nテキスト(.txt)\n.txt\nDialogBarTest.Document\nDialog Document
- でも、これでは1種類のファイルしか扱えない。
- 自分が今まで作ったアプリケーションって、ほとんどが複数の拡張子を使うものだったので、これはちょっと許せない。
- どうやって、指定すれば良いんだろう? 常識なのかなぁ。
- このことについての説明が見つからない。ファイル選択はドロップダウンメニューになってるぐらいだから、出来ないわけは無いと思うんだけど。。
- いろいろ探し回りましたが、全然分かりませんでした。
- Documentクラスのシリアライズでは、既にファイルがOpenされている。
- ファイルオープンダイアログは、何時呼ばれるのかなぁ。
- こちらの情報から、以下のようになっていることが分かった。
ID_FILE_OPEN ( [開く...] )
↓
CWinApp:OnFileOpen()
↓
CWinApp:OpenDocumentFile()
↓
CDocManager::OpenDocumentFile()
↓
CDocument::OnOpenDocument()
↓
CDocument::Serialize()
↓
CDocument::SetPathName()
- OnFileOpenを弄れば良さそうだ。ということで検索してみると、
- 早速、自分のスケルトンアプリのCWinAppの派生クラスでOnFileOpenを定義する。(MyDbar.h)
//{{AFX_MSG(CMyDbarApp)
afx_msg void OnAppAbout();
CDialogBar m_wndDialogBar;
//}}AFX_MSG
afx_msg void OnFileOpen(); // この行を追加
DECLARE_MESSAGE_MAP()
- で、MESSAGE_MAPのCWinApp::OnFileOpen→CMyDbarApp::OnFileOpenに書き換える。(MyDbar.cpp)
BEGIN_MESSAGE_MAP(CMyDbarApp, CWinApp)
//{{AFX_MSG_MAP(CMyDbarApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
//}}AFX_MSG_MAP
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CMyDbarApp::OnFileOpen) // 変更
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
- それで、OnFileOpenのプログラム本体を追加すれば良いのか。
void CMyDbarApp::OnFileOpen()
{
CFileDialog fd(
TRUE, // TRUE:Open, FAlSE:Close
"", // デフォルトの拡張子
"", // デフォルトのファイル名
OFN_FILEMUSTEXIST| // 既存のファイルのみ
OFN_HIDEREADONLY| // ReadOnlyのCheckBoxを表示しない
OFN_LONGNAMES, // 長いファイル名を使う
"テキスト (*.txt)|*.txt|"
"全てのファイル(*.*)|*.*||", // ファイルフィルタ
NULL // 親ウィンドウ
);
fd.m_ofn.lpstrInitialDir=(LPCSTR)"C:\\temp"; //初期ディレクトリをtempに
if(fd.DoModal()==IDOK){
OpenDocumentFile(fd.GetPathName());
}
}
- これで、普通にファイルダイアログを呼び出す時に、ファイルフィルタを指定すれば良い。
- やっと複数の拡張子を指定する方法が分かった。嬉しい!
- 分かってみると簡単だけど、これも情報を公開してくれている方々のお陰です。感謝!!
- Serializeで、拡張子をチェックして処理する。
- いきなり小技だけど、VC5ではTokenize使えないみたいなので、こんな感じでどうだろうか。
- CStringは未だ使い慣れていないので、ちょっとイマイチか。。もっと良い方法ないかな?
CString line;
// 拡張子のチェック
line = ar.GetFile()->GetFileName();
if (line.Right(line.GetLength()-line.ReverseFind('.'))==".txt")
MessageBox(NULL,"text","xxx",MB_OK);
[top]
- MFCのDocument・Viewではシリアライズという方法で読書きをするらしいが、、
- 日本語にすると、直列化??エンコードみたいなことかな。
- 標準入出力みたいなイメージだが、もっと奥が深い概念のようだけど。。
- わかんなくても、とりあえずファイルが読書き出来れば良いんだよね。。。
MFCの関数、ちょっと復習しておく。
- CView::GetDocument
- ViewがDocumentにアクセスするためのポインタを得るために呼び出す
- CDocument::UpdataAllViews
- Documentの更新を行ったら、これを呼び出してViewを更新する。OnUpdateが呼び出される
- CView::OnDraw
- Documentの情報からViewに描画する。基本的にViewへの描画は全部ここで行う。
- Cview::OnUpdate
- Update領域を調べて、CWnd::InvalidateRectに渡す。デフォルトでは、OnInitialUpdateからも呼び出される。
- CView::OnInitialUpdate
- アプリケーションの起動、Documentの更新(ファイルの新規作成とか読込み)時に呼び出されるので、Viewの初期化を行う。
- CDocument::OnNewDocumentFunction
- ファイルの新規作成で呼び出されるので、Documentの初期化を行う。
public:
CString m_str;
if (ar.IsStoring())
{
ar << m_str; // 保存
}
else
{
ar >> m_str; // 読込み
}
MessageBox(NULL,m_str,"m_str",MB_OK);
これで適当なファイル1を開いてみると、どうも最初しか読み込まない。
それでテスト的に、簡単なtxtファイルを作って試してみると、今度は以下のエラー。
予期しないファイル形式です。
?なんで?ただのtxtファイルなのに。何を予期していたんだ。。
- 良く分からないが、arは、基本データ型?以外では、 << や >> の演算子は、そのままじゃ使えないらしい。
if (ar.IsStoring())
{
ar.WriteString(m_str); // 保存
}
else
{
ar.ReadString(m_str); // 読込み
}
MessageBox(NULL,m_str,"m_str",MB_OK);
- これだと、最初の1行だけ読み込まれた。
- ちゃんと実装すれば、<<や>>も使えるのかもしれないが、この方が自分には分かりやすい2ので、やっぱりRead,Writeでやろう。
結局、シリアライズは以下のような構成にしよう。
- 拡張子のチェック
- ar.IsStoring()をチェックして、保存なら、
- 読込みならば、
全体の構成としては、
- 拡張子に応じたファイル処理は、Documentクラスに書けば良いだろう。
- ViewはDocumentの内容を表示するだけにする。
- MainFrameに属する、DialogBarへの表示もここで行うべきかな。
- MainFrameは操作系ということで、全体をコントロールする部分を記述する。
ここまで来ると、あとはコーディングするだけかなぁ。
- 読み込むファイルの大きさが大きいので、最初はインデックスを作るだけにする。
- というか、複数のデータが含まれているようなファイルなので、1部だけ読み出したいのだ。
- ということで、シリアライズではデータの先頭部分を探して、インデックスを作るだけにしてみた。
- データの先頭を見つけたら、その次の行の先頭位置を記録しておくことにする。
- これは、ドキュメントビュー的にはどうなんだろうという感じだが、とりあえずやってみる。
- Viewは、この位置のデータを表示することになる。
- この場合、Viewで表示する時にDocumentは閉じているんだろうか?
- おそらく、Documentは閉じていると仮定して、フルパスのファイル名だけ保存3しておく。
- ViewクラスのOnDrawで、データを表示してみる。
- OnDrawは起動時にも呼ばれるみたいなので、ファイル名が空ならなにもしない。
- シリアライズでは、インデックスを作るだけなので実際のデータ読込みは、
- ReadStringを使いたいので、CFileではなくCStdioFileを使ってファイルを開く。
- とりあえず、一番最後のデータにSeekしてデータを読んでみる。
spara_data spx;
CStdioFile fp;
if (pDoc->m_path!="") {
fp.Open(pDoc->m_path,CFile::modeRead);
spx = pDoc->m_data.GetAt(pDoc->m_data.GetSize()-1);
fp.Seek(spx.pos,CFile::begin);
while(fp.ReadString(line,255)) {
TRACE(line); →とりあえずTRACEで表示
}
fp.Close();
}
- 読めることは読めたんだけど、Seekした位置は155byte後ろにずれている。。。
- CStdioFileは、CFileの派生クラスだから問題無いと思ったんだけど。。。
- 試しに、CFileに変えてみるか。。やっぱり同じ位置だ。。。どうして?
- 普通に考えると、シリアライズでGetPositionすると、本来の位置+155の値が戻ってくるということなのかなぁ。
- でやってみたら、場所によって違うようだ。どういうことなんだろう。
- 通常のファイルアクセスでは、ファイルポインタは開きなおしても同じ場所を指すんだけど、それは保障されないことなのかなぁ。
- それとも、シリアライズでは何か特殊な事をやっているんだろうか?
- ということは、OnOpenDocumentをオーバーライドしないとダメなんだろうか?
- プログラムの本質以外のところで悩むなぁ。ここまでして使う意味あるんだろうか?
- 仕方が無いので、OnOpenDocumentをクラスウィザードで追加する。
- デフォルトのOnOpenDocumentをコールするようになっているので、コメントアウトする。
- これで、シリアライズが呼ばれることは無くなった。
- ReaDataの部分は、元々ar(CArchive)用に書いてあったものを、CStdioFile用に書き換えたもの。
- 今度は、すんなり想定していた動作になった。CArchiveは普通のファイル処理と違うことをやっているんだろうか。
- 結局、シリアライズじゃ無くなっちゃったな〜。
1その適当なファイルも、拡張子はtxtじゃないけど、ただのテキストファイル。
2というか、CObjectとかCArchiveって言われても、よく分からん。
3既にDocumentViewの枠組みから完全に外れているんじゃないだろうか。。
[top]
- やっとデータ表示部分に取り掛かれるが、実際はファイルの一部だけ指定して表示するので、どこを表示するかのコントロールが必要。
- DialogBarは、MainFrameが持っているので、MainFrameから表示すべきだろう。
- ドキュメント読込み後に、UpdateAllViewsを呼び出すことで、OnDrawが実行されるはずだけど、
- MainFrameは、それが分からないので知らせてやる必要がある。
- どうしたら良いかな?MainFrameに表示更新用の関数を作ろう。
- これはスケルトンを作るときに考えた方法で、そのままやれば良いや。
- 結局、Documentの読込み完了で、CDocument::UpdateAllViewsが呼ばれ
- CView::OnUpdateから、CMainFrm::dialogbar_updateが呼ばれて、DialogBarが更新される
- コントロールを操作することで、Viewにデータを表示する。
- 表示する番号は、CDocumentのメンバ変数m_data_numberに保存することにして、
- viewを再表示させるために、UpdateAllviewを実行する。
- 。。。これだけではCview::OnDrawは実行されないみたいだ。
- CView::OnUpdateにInvalidateRect(NULL)を追加すると、再表示されるようになった。
[top]
- リソースエディタで、Spinコントロールを付けて、EditBoxの次のタブオーダに変更しておく。
- それでClassWizardで、CSpinButtonCtrlの変数を追加しようとしたんだけど出来ない。
- MainFrameが親ではなくて、DialogBarが親だからなのかなぁ。
- 面倒になってきたので、CMainFrame::OnCreateで、直接コントロールハンドルを取得した。
m_number_spin = (CSpinButtonCtrl*)m_wndDialogBar.GetDlgItem(IDC_SPIN1);
m_number_spin->SetBuddy(m_wndDialogBar.GetDlgItem(IDC_EDIT1));
でも、CMainFrame::dialogbar_updateで、Spinコントロールを弄ろうと思ったら、
- Debug Assertion Failed! のダイアログが出てしまう。
OnCreateの時とSpinコントロールのハンドルが違ってる4みたいだ。OnCreate時に取るべきじゃないのかも。
- 思い切り邪道な方法になってきてる気がするが、自分的にはこの方が気が楽だ。
- もう気分はSDKという感じで、MFCの正統な書き方には拘らないでやろう。
- CMainFrame::OnEnChangeが、最初に呼ばれてしまう。
- いろいろ調べてみると、Spinコントロールがあるとダメみたいだ。
- さらに確認すると、SpinコントロールのAutoBuddyがチェックされていると、こうなるみたいだ。
- 単純にAutoBuddyをOFFにしたら、ちゃんと動作するようになった。
- CMainFrame::OnCreateで、SetBuddyしてるので、AutoBuddyは必要ないらしい。
- MFCって、勝手にいろいろやってくれるので、悩ましいなぁ。
4これはハンドルじゃなくてポインタなのかも。
[top]
- CRadioButtonがあるのかと思ったけど、どうもCButtonを使うらしい。
- ClassWizardが使えないので、CMainFrm.cppに
- 直接、MESSAGE_MAPにON_BN_CLICKEDイベントに対する関数を追加
ON_BN_CLICKED(IDC_RADIO1,OnRadio1Clicked) // Radio1クリックイベント追加
ON_BN_CLICKED(IDC_RADIO2,OnRadio2Clicked) // Radio2クリックイベント追加
ON_BN_CLICKED(IDC_RADIO3,OnRadio3Clicked) // Radio3クリックイベント追加
ON_BN_CLICKED(IDC_RADIO4,OnRadio4Clicked) // Radio4クリックイベント追加
- ヘッダファイルCMainFrm.hのAFX_MSGに、関数宣言を追加
afx_msg void OnRadio1Clicked(); // Radio1クリック時に実行する関数を追加
afx_msg void OnRadio2Clicked(); // Radio2クリック時に実行する関数を追加
afx_msg void OnRadio3Clicked(); // Radio3クリック時に実行する関数を追加
afx_msg void OnRadio4Clicked(); // Radio4クリック時に実行する関数を追加
- あとは、実際の関数を、CMainFrm.cppに記述する。
void CMainFrame::OnRadio1Clicked() { ... }
void CMainFrame::OnRadio2Clicked() { ... }
void CMainFrame::OnRadio3Clicked() { ... }
void CMainFrame::OnRadio4Clicked() { ... }
- これは同じ関数にして、どのボタンが押されたかチェックして処理を変える方が良さそうだが、
- とりあえず、何も考えずにこうしたけど、本当はどうするのが良いんだろう?
- で、ボタンの初期化は、CMainFrame::OnCreateでボタンのハンドルを得てチェックするだけ。
- ラジオボタンの状態を読み込む。
- 押された時はイベント処理出来るけど、データ読込み時等の再表示では読み込む必要がある。
- もうMFCで書いてるというより、SDKみたいなコーディングだ。。
- コーディングが汚いのはSDKの所為ではなく、自分のテキトウなプログラムの所為。
- MFCのことを全然考えずに書いてるので、あまり悩むこともないのは良いけど。
[top]
- とりあえずは、
- CDocumentで、ファイル読書きとデータの保持
- CMainFrameで、CDialogBarを使って操作系を記述
- CViewで、CDocumentのデータを画面表示
- という感じで、それらしく作っては見たが、
- ファイルの拡張子で、自前で切替えてプログラムしたり、
- 結局、シリアライズを使わない形にしてしまったし、
- DialogBarのコントロールのハンドルを直接取得して操作しちゃったり、
- 記述がいい加減なので、ClassWizardが使えなかったり、
- 行き当たりばったりに、テキトウにコーディングしたので、悪いプログラムの見本のようになってしまった。
- しかし、基本はMFCのフレームワークを使っているので、印刷とか簡単に出来たりする。
- でも自分の場合、結局SDKで書いてるのと同じようなことをやってしまっているようだ。
[top]
[top]
[プログラムの部屋に戻る]
⇒ Disqusの広告がうるさすぎるので基本は非表示にしました