| ■ プロジェクトの作成 |
| この章では、エディターに興味がある人を対象に、プログラミングのアプローチをしたいと思います。 エディターといっても入門用なので、ここでは基本的な処理だけ追加することにします。 後は皆さん自身で、お好みに変更してみてください。 今回は、タイトルの通りにMDIアプリケーションを作成します。プロジェクト名は"MDIEditor"にします。 MDIとは、マルチ・ドキュメント・インターフェースの略で、複数のViewを持つアプリケーションを意味します。具体的には、Visual StudioやワードがMDIアプリケーションです。 作成方法は、ダイアログの編集と同様です。MFC AppWizardのステップ1の[作成するアプリケーションの種類]で[MDI]を選択します。ダイアログの時と同様に、他の設定はそのままですが、最後のビューの指定のみ変更します。ビューの種類はデフォルトではCViewになっているので、CEditViewに変更します。 ![]() |
| ■ とりあえず実行してみましょう |
| 作成したばかりの状態で、とりあえずビルドして実行してみましょう。 実行後は、下の画面のようになります。MFCのCEditViewは、この状態でエディタとしての基本は出来上がっています。 この状態では、文字の入力、作成したテキストの保存、そしてASCIIファイルの読込みが可能です。 また、MDIのメリットとして、複数のテキストファイルを開くことが出来ます。 ![]() |
| ■ まずフォントを変更しましょう |
| デフォルトの状態だと、エディタのフォントがプロポーショナルフォントになっており、各行の幅が揃っていません。 エディタは普通固定フォントで各行の幅が揃った状態のほうが使いやすい場合が多いので、まずフォントを変更してみましょう。 @ フォント変数の追加 通常は、ビュー内でフォントを作成し、変更するのですが、今回はMDIなのでCMainFrameにフォントを持たせ、ビューでそれを使うことにします。 まず、CMainFrameに、CFontのメンバ変数m_fontを追加します。この変数は必ずメンバ変数にしてください。 今回は、外部から参照するので当然ですが、フォント設定時にローカル変数だと、処理終了後に消滅してしまい変更できません。 CFont m_EditorFont; // エディター用のフォント
A 処理の追加 CMainFrameのOnCreate内の最後に 以下のように、処理を追加します。 ここではフォントは「MS ゴシック」にしました。サイズは14ですが、マイナス指定することにより、font mapperが調整してくれます。 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
一般的な他のコードは省略します。
// エディター用のフォント作成.
LOGFONT lf;
memset( &lf, 0, sizeof(lf));
lf.lfHeight = -14;
lf.lfWidth = 0;
lf.lfCharSet = SHIFTJIS_CHARSET;
strcpy( lf.lfFaceName, "MS ゴシック"); // 固定フォント.
if ( !m_EditorFont.CreateFontIndirect( &lf))
{
TRACE0("Could Not create font for editor\n");
return -1;
}
return 0;
}
B OnCreate関数の追加 CMDIEditorViewに初期化処理を追加します。クラスウィザードでWM_CREATEを選択しOnCreateを追加します。 C フォントの設定処理の追加 前項で作成した、OnCreate内に以下のように、フォントの設定処理を追加します。 ここで、CMDIEditorViewからCMainFrameを参照するために、CMDIEditorAppの標準変数m_pMainWndを参照します。 そのためには、CMDIEditorAppを参照しなければなりませんが、これは標準のグローバル変数であるtheAppが設定されているので、CMDIEdirorAppのヘッダーファイル内に以下の行を追加します。 extern CMDIEditorApp theApp; // 外部からCWinAppを参照可能にする.
これで、参照可能になり以下のソースのようにCMainFrameを参照することができるようになります。int CMDIEditorView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CEditView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: この位置に固有の作成用コードを追加してください
CMainFrame* pMainFrame = ( CMainFrame*)theApp.m_pMainWnd;
SetFont( &pMainFrame->m_EditorFont);
return 0;
}
ここで、ソースコードの先頭に、MainFrm.hをインクルードしてください。 |
| ■ 定番機能の検索・置換処理の追加 |
| エディタには、定番処理の検索・置換処理があります。ここでは、これらの処理を追加してみましょう。 @メニューの追加 まずは、メニューを追加しましょう。 [ResourceView]の[Menu]の中のIDR_MDIEDITYPEを選択します。 ここで気づかれた人もいると思いますが、MDIは作成時に2つのメニューを持ちます。 これは、MDIの場合、SDIと違いチャイルドウィンドウを全て閉じた状態が存在します。 この状態のとき使われるのが、IDR_MAINFRAMEの方です。もう一方はIDR_xxxxxxTYPEになりxxxxxxは作成したプロジェクトの名前が入ります。 ここで追加するメニューはドキュメントが開かれたときのためのものなのでIDR_MDIEDITYPEを編集します。 追加するメニュー項目は[編集]の下に、[検索](ID_FIND)、[下方検索](ID_FIND_NEXT)、[上方検索](ID_FIND_PREV)、[置換](ID_REPLACE)の4つです。 これらの機能は、比較的良く使う機能なので、コントロールキーとファンクションキーを使いコピー・貼り付けと同様にいつでも使えるようにキーを割り当てます。 ![]() A各処理ルーチンの追加 クラスウィザードを使い、前項で作成したメニュー項目に対する処理をCMDIEditorViewに追加します。 次に、各コードを記述しますがその前に、共通変数を準備します。 CFindReplaceDialog *m_pFindDlg; // 検索・置換ダイアログのポインタ CString m_FindStr; // 検索用文字列 CString m_ReplaceStr; // 置換用文字列 BOOL m_bSearchDown; // 下方向検索フラグ BOOL m_bMachCase; // 大文字小文字判別フラグ検索・置換に関しても、MFCではおおむねの関数等が提供されています。検索・置換の設定に関してはCFindReplaceDialogを使います。 これは、よく使うCFileDialogのように、システムが提供する共通ダイアログの内の一つです。 次に、コンストラクタ内で各メンバ変数を初期化します。 CMDIEditorView::CMDIEditorView()
{
// TODO: この場所に構築用のコードを追加してください。
m_pFindDlg = NULL;
m_FindStr = _T("");
m_ReplaceStr = _T("");
m_bSearchDown = TRUE; // 下方検索.
m_bMachCase = FALSE; // 大文字小文字判定無し.
}
また、CFindReplaceDialogは、このダイアログ自体は各種設定を行うだけで、実際の処理は親ウィンドウ(呼び出し側)が行わなければなりません。その場合、RegisterWindowMessage関数い親ウィンドウに検索・置換のメッセージを送ります。親ウィンドウは、ON_REGISTERED_MESSAGE マクロを使いメッセージを受信します。実際のソースコードは以下のようになります。 static const UINT nMsgFindReplace = ::RegisterWindowMessage(FINDMSGSTRING);これにより、検索・置換のための新しいメッセージが作成されます。 メッセージ受信マクロは、以下のようになります。 BEGIN_MESSAGE_MAP(CMDIEditorView, CEditView)
//{{AFX_MSG_MAP(CMDIEditorView)
ON_COMMAND(ID_FIND, OnFind)
ON_COMMAND(ID_FIND_NEXT, OnFindNext)
ON_COMMAND(ID_FIND_PREV, OnFindPrev)
ON_COMMAND(ID_REPLACE, OnReplace)
//}}AFX_MSG_MAP
// 標準印刷コマンド
ON_COMMAND(ID_FILE_PRINT, CEditView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CEditView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CEditView::OnFilePrintPreview)
ON_REGISTERED_MESSAGE(nMsgFindReplace, OnFindReplaceCmd)
END_MESSAGE_MAP()
次に[検索]メニューの処理は、以下のようになります。void CMDIEditorView::OnFind()
{
if ( m_pFindDlg)
return;
m_pFindDlg = new CFindReplaceDialog;
ASSERT( m_pFindDlg != NULL);
DWORD dwFlags = FR_HIDEWHOLEWORD;
// 検索方向の設定
if ( m_bSearchDown)
dwFlags |= FR_DOWN;
// 大文字小文字の判別設定
if ( m_bMachCase)
dwFlags |= FR_MATCHCASE;
if ( !m_pFindDlg->Create( TRUE, m_FindStr, m_ReplaceStr, dwFlags, this))
{
m_pFindDlg = NULL;
ASSERT_VALID(this);
return;
}
ASSERT( m_pFindDlg != NULL);
ASSERT_VALID(this);
}
ここでは、CFindReplaceDialog作成します。各種設定は前回の設定をキープしておき、それを使います。また、置換との違いはCreate関数の最初のパラメータが、TRUEのときは検索、FALSEのときは置換になるだけで後は同じなので、[置換]メニューの処理は省きます。 次に、実際の処理ルーチンは、以下のようになります。 LRESULT CMDIEditorView::OnFindReplaceCmd(WPARAM, LPARAM lParam)
{
ASSERT_VALID(this);
CFindReplaceDialog* pDialog = CFindReplaceDialog::GetNotifier(lParam);
ASSERT(pDialog != NULL);
ASSERT(pDialog == m_pFindDlg);
if (pDialog->IsTerminating()) // 検索・置換終了
{
m_pFindDlg = NULL;
ShowWindow(SW_SHOW);
}
else if ( pDialog->FindNext()) // 次を検索
{
m_bSearchDown = pDialog->SearchDown();
m_bMachCase = pDialog->MatchCase();
m_FindStr = pDialog->GetFindString();
if ( !FindText( m_FindStr, m_bSearchDown, m_bMachCase))
AfxMessageBox( IDS_FIND_END);
}
else if ( pDialog->ReplaceCurrent()) // 現在示す箇所を置換
{
CString SelCs;
GetSelectedText( SelCs);
m_bSearchDown = pDialog->SearchDown();
m_bMachCase = pDialog->MatchCase();
m_FindStr = pDialog->GetFindString();
m_ReplaceStr = pDialog->GetReplaceString();
if ( !SelCs.GetLength())
{
if ( !FindText( m_FindStr, m_bSearchDown, m_bMachCase))
{
AfxMessageBox( IDS_REPLACE_END);
return 0;
}
}
GetEditCtrl().ReplaceSel( m_ReplaceStr, TRUE);
if ( !FindText( m_FindStr, m_bSearchDown, m_bMachCase))
{
AfxMessageBox( IDS_REPLACE_END);
return 0;
}
}
else if ( pDialog->ReplaceAll()) // 全て置換
{
CString SelCs;
GetSelectedText( SelCs);
m_bSearchDown = pDialog->SearchDown();
m_bMachCase = pDialog->MatchCase();
m_FindStr = pDialog->GetFindString();
m_ReplaceStr = pDialog->GetReplaceString();
if ( !SelCs.GetLength())
{
if ( !FindText( m_FindStr, m_bSearchDown, m_bMachCase))
{
AfxMessageBox( IDS_REPLACE_END);
return 0;
}
}
while( 1)
{
GetEditCtrl().ReplaceSel( m_ReplaceStr, TRUE);
if ( !FindText( m_FindStr, m_bSearchDown, m_bMachCase))
break;
}
}
ASSERT_VALID(this);
return 0;
}
このルーチン内では、現在開かれているCFindReplaceDialogの状態を知る関数を使い、処理を振り分けています。実際の検索・置換処理も全て、CEditViewの関数および、CEditViewが使っているCEditCtrlの関数を使うので、処理といってもほとんどそれらの関数のパラメータをセットするだけです。詳細はヘルプを見てください。Bストリング・テーブルに文字列データを追加。 前項のOnFindReplaceCmd関数内でメッセージを表示するのにAfxMessageBoxを使用しています。 この関数では、次のように文字列を直接指定できますが AfxMessageBox( "検索が終了しました");ここでは、 AfxMessageBox( IDS_FIND_END);のように、文字列のIDを呼び出しています。 これは、ストリング・テーブルにID番号と該当する文字列を設定し、ソース内では直接文字列を扱わず、そのIDを呼び出すやり方です。 このやり方のメリットとしては、同じような文字列を複数使う場合、ストリング・テーブルを修正することによりまとめて変更が可能なことと、英語などマルチランゲージのアプリケーションを作成するときに有効です。 文字列データの追加方法は、リソースのStringTableを開き[Insert]キーを押すと、Stringプロパティダイアログが表示されるので任意のID名を指定し、キャプションに文字列を入力します。IDの番号を指定したい場合は、ID名の後に"=番号"を指定します。このやり方で、作成後に変更も可能です。 また、コピー・ペーストも可能です。 ![]() CCFindReplaceDialog以外の直接の検索処理 検索・置換ダイアログ以外に、ここではダイアログを閉じた後も、検索が出来るようにしてみます。 [下方検索]メニューの処理は、以下の様になります。 void CMDIEditorView::OnFindNext()
{
if ( m_FindStr.GetLength())
FindText( m_FindStr, TRUE, m_bMachCase);
}
内容は、検索用文字列変数にデータが入っていた場合、すなわちここでは最後に検索ダイアログで指定された文字列を検索します。[上方検索]は、FindTex関数の2番目のパラメータがFALSEになるだけです。 Dアクセラレータの追加 メニュー作成時に、ショートカットキーを指定しましたが、これだけでは実際に効果がありません。 シュートカットキーを有効にするには、アクセラレータを指定しなければなりませ。 やり方は、リソースのAcceleratorを開き、[Insert]キーを押すと、Accelプロパティのダイアログが表示されます。 ここで、IDに目的のものを指定します。右の▼ボタンでリストから探すことが出来ます。 ![]() |
| ■ 実行してみましょう |
| 以上で、今回の追加は終了です。 ビルドして実行してみましょう。追加した処理を確認してみましょう。 ![]() 今回は、ここまでです。ステップ2では更に追加変更をしてみましょう |
| ■ サンプルプロジェクトファイルのダウンロード |