m_strokes;の下にCString m_CurLett;
といれてください。つまり、そのあたりは、
// アトリビュート
public:
CTypedPtrList<CObList, CNekoStroke*> m_strokes;
CString m_CurLett; //付け加えた1行
という感じになるでしょう。(Curはcurrentつまり「現在の」という意味です。)
このm_CurLettはどこかで初期化(初期の値を代入する)した方が良いようです。このようなメンバ変数の初期化は、MFCプログラミングでは普通OnNewDocumentという関数でやるようです。(これは、メニューのファイルで新規作成を選んだ時に呼び出される関数です。)ファイルビューなどを使ってCNekoDoc.cppのCNekoDoc::OnNewDocumentの定義の場所(CNekoDoc.cpp内にあります)を見つけ出し、
BOOL CNekoDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
m_CurLett="猫"; //付け加えた1行
return TRUE;
}
としてください。
さて、ここまでやってきたことは、まだ準備です。げげっ、、、。そろそろ疲れましたね。でも、あと少しです。
次に、NekoDoc.cppのvoid CNekoDoc::OnChangeLetter()のところを書き換えます。ClassWizardかファイルビューなどでこの場所を探してください。
この関数はメニューで「文字の変更」を選んだ時に、呼び出されるのでした。
void CNekoDoc::OnChangeLetter()
{
CLetterDlg dlg;
dlg.DoModal();
}
となっているでしょう。つまり、CLetterDlgのオブジェクト(インスタンス)dlgを生成し、そのメンバ関数DoModalを呼び出しているのです。この部分を
void CNekoDoc::OnChangeLetter()
{
CLetterDlg dlg;
if(dlg.DoModal()==IDOK){
m_CurLett=dlg.m_letter;
}
}
としてください。ifの中身ですが、想像がつく人はもうだいぶ馴れた人でしょう。実はDoModalはダイアログを表示するだけでなく、どのボタンが押されて終了したかを知らせてくれるのです。つまり、もし、OKボタンが押されるとそのIDのIDOK(これは決まっているのです)が戻され、もし、キャンセルボタンが押されればIDCANCELが戻されるのです。
それで上のコードは、もしOKボタンが押されればm_CurLettにdlgのm_letterの値(文字列)が代入されるという意味になるのです。
あとは、マウスのドラッグの際にこの文字列を使うように変更すれば良いだけですね。CNekoView.cppのvoid CNekoView::OnLButtonDown(UINT nFlags, CPoint point)を
void CNekoView::OnLButtonDown(UINT nFlags, CPoint point)
{
SetCapture(); //マウスをキャプチャ(補足)する
m_pCurStroke=new CNekoStroke;
CNekoDoc *pDoc=GetDocument();
m_pCurStroke->str=pDoc->m_CurLett; //ここだけ付け加えた
pDoc->m_strokes.AddTail(m_pCurStroke);
}
としてください。付け加えた行はドキュメントに保持されているユーザの選んだ文字列(m_CurLett)を現在の一筆(m_pCurStroke)の文字(str)にせよ、という意味ですね。
ちょっと、本論と関係無いのですが、変数の命名方についてコメントです。私は文字列を表す変数名に、普通str(stringの頭3文字)などを使うのですが、今回のプログラムでは文字を表すletterを途中から使い、なんかちぐはぐになってしまいました。すみませんでした。
さて、とりあえず、できたみたいなので、ちょっと実行してみてください。実行画面で、まず、普通にマウスをドラッグすると、猫という字が書けるでしょう。これは以前のままです。次に、メニューで文字の変更を選んでください。すると、文字変更のダイアログが開くので、適当な文字(列)を入力し、OKボタンを押してください。私は「犬」(私は犬も好きです。)と入力してみました。それからまたマウスのドラッグをやると、ちゃんと犬という文字が書けます!
しかし、実は、まだ未完成なのです。ここで、今、書いた画面を保存してください。つまり、メニューで保存を選び適当な名前をつけて保存するのです。そして、一度Nekoを終了し、もう一度実行してから、ファイルの「開く」で今保存したファイルを開いてください。すると、、、。全部「猫」になっているはずです。
これはどうしたことか! 落ち着いて考えてみましょう。(もうやんなりましたか?)今、Serializeかなと思った人はとても優秀です。CNekoStroke::Serializeを見てください。ここで、文字を書く場所を表すm_pointsはシリアライズ(つまりファイルへの書き込みとファイルからの書き出し)していますが、書き込む文字はシリアライズしていないのです。このため、前の例なら「犬」という文字が保存されていないのです。では、なぜ、同様に保存していない「猫」という字が書けたのでしょうか。それはCNekoStrokeのデフォルトのコンストラクタ(引数をとらないコンストラクタ)で「猫」という字を指定しているからです。
つまり、ファイルからの読み出しで、文字が読み出されず(はじめから保存していないのですから)、文字が書かれる場所だけが読み出されると、引数のないコンストラクタによって、自動的にstrが猫になってしまうのです。試しに、このコンストラクタを何もしないものに書き換えると、ファイルを読み込んでも、画面には何も表示されなくなります。それは、書くべき(人間に見える)文字が無いからです。
この問題をクリアするには、要するに、ちゃんと文字(str)そのものも保存するようにすれば良いのです。それには、CNekoStroke::Serializeを
void CNekoStroke::Serialize(CArchive& ar)
{
if(ar.IsStoring()){
ar<<str;
m_points.Serialize(ar);
}
else{
ar>>str;
m_points.Serialize(ar);
}
}
とすれば良いのです。Serializeは書き込みと読み出しの両方で使われる関数です。今までは、CNekoStrokeのSerializeは単にCArrayのSerializeを呼ぶだけだったので、気にしませんでしたが、今度は、新しいメンバstrのことを考えなければならないので、ちゃんと書き込みと読み出しを区別しなければならないのです。そのため、ifとelseがあるのです。
ここまで書けばわかるかもしれませんが、実は、ar.IsStoring()が書き込みと読み出しを区別してくれる関数なのです。この関数は、現在書き込み中なら「真」を戻すのです。簡単に言うと、ファイルへの書き込み中なら、はじめの中カッコ内が、そうでなければ(つまり読み込み中なら)あとの中カッコ内が実行されるのです。そして、ar<<str;はar(ファイルを表すオブジェクト)への書き込み、ar>>str;はarからの読み出しを意味します。
以上で、一応ちゃんと動くプログラムになりました。まだ、言い残しが少しあります。その言い残しは次回にまわしますが、ひとつだけ注意です。今のやり方で、ファイルへの書き込みと読み出しの仕方が変わったので、改造したプログラムでは以前に保存した猫文字ファイルは読めないようになってしまいました。残念ですがしかたありませんね。
次回は、メニューとダイアログボックスの補足です。(いつまでやってんだぁ。はい、次で終わりです。たぶん。)
目次のページ
前のページ
後のページ