どのEdit Controlを編集しても、他のEdit Controlが同じ内容になるようにする。
複数のEdit Controlを用意して、どのコントロールの編集を行っても、他のコントロールに同様の内容が設定される、というのを書こうと思った。
それで、Edit Control を4つくらい貼り付けた。
作成された4つのEdit Controlには、IDC_EDIT1
, IDC_EDIT2
, IDC_EDIT3
, IDC_EDIT4
という ID が振られたようだ。
そして、そのうちの一つをダブルクリックして、ON_EN_CHANGE
に対するイベントハンドラを追加した。
BEGIN_MESSAGE_MAP(CMFCApplication2Dlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_EN_CHANGE(IDC_EDIT1, &CMFCApplicationDlg::OnEnChangeEdit1)
END_MESSAGE_MAP()
void CMFCApplicationDlg::OnEnChangeEdit1()
{
}
作成されたイベントハンドラは、OnEnChangeEdit1()
という名前の関数になった。
ほかの3つのコントロールも、この一つの関数でイベントをハンドルできるように、ソースコードを書き換えた。
BEGIN_MESSAGE_MAP(CMFCApplicationDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_EN_CHANGE(IDC_EDIT1, &CMFCApplicationDlg::OnEnChangeEdit)
ON_EN_CHANGE(IDC_EDIT2, &CMFCApplicationDlg::OnEnChangeEdit)
ON_EN_CHANGE(IDC_EDIT3, &CMFCApplicationDlg::OnEnChangeEdit)
ON_EN_CHANGE(IDC_EDIT4, &CMFCApplicationDlg::OnEnChangeEdit)
END_MESSAGE_MAP()
void CMFCApplicationDlg::OnEnChangeEdit()
{
}
関数名から、最後の1文字を削除して、イベントハンドラの設定の行をコピーして、ID指定の部分を変えた。
これで、どのコントロールに対して変更が行われても、この一つの関数が呼ばれるようになる。
修正された部分の文字列を取得する
とりあえず修正内容を取得できるかのテストのために、以下のように書いた:
void CMFCApplicationDlg::OnEnChangeEdit()
{
CWnd* pFocus = GetFocus();
CString _str;
pFocus->GetWindowText(_str);
_RPTW1(_CRT_WARN, L"CMFCApplicationDlg::OnEnChangeEdit() %s\n", _str);
}
修正される瞬間には、そのコントロールはフォーカスを持っているはずなので、フォーカスを持っているコントロールに対して GetWindowText()
を行って、修正内容を取得する。
_RPTW1()
マクロを使用して、取得した内容をデバッグプリントする。
左のコントロールから順に数文字ずつ入力すると、1文字入力するたびにイベントハンドラが呼ばれている。
どのコントロールに対して入力しても、同じOnEnChangeEdit()
関数が呼ばれている。
入力されたものを他のコントロールに設定する
修正された文字列を取得できたので、それを他のコントロールに書くという処理を書いた:
void CMFCApplicationDlg::OnEnChangeEdit()
{
CWnd* pFocus = GetFocus();
CString _str;
pFocus->GetWindowText(_str);
_RPTW1(_CRT_WARN, L"CMFCApplicationDlg::OnEnChangeEdit() %s\n", _str);
GetDlgItem(IDC_EDIT1)->SetWindowText(_str);
GetDlgItem(IDC_EDIT2)->SetWindowText(_str);
GetDlgItem(IDC_EDIT3)->SetWindowText(_str);
GetDlgItem(IDC_EDIT4)->SetWindowText(_str);
}
実行して、テキストエリアに文字を1文字打ち込むと、しばらく黙り込んだ後で Stack overflow エラーになった:
デバッグプリントが出力されるところには、以下の行が大量に出ていたので、たぶん再起呼び出しの無限ループ的なことになっているようだった:
CMFCApplicationDlg::OnEnChangeEdit() 1
CMFCApplicationDlg::OnEnChangeEdit() 1
CMFCApplicationDlg::OnEnChangeEdit() 1
・・・
プログラムを起動して、Edit Controlに対して入力を行うと、ON_EN_CHANGE
イベントが発生するが、SetWindowText()
を呼び出してもON_EN_CHANGE
イベントが発生するようだ。そのため、イベントハンドラ関数の処理中にイベントハンドラが呼ばれることになり、スタックを食いつぶしてしまっている。んだと思う。
処理中を示すフラグ変数を作って、無限処理地獄を回避する。
メンバ変数として、m_bOnEnChangeEdit_InProgress
というbool
型の変数を作り、OnEnChangeEdit()
関数が動作中であることを示すようにした。
まず、クラスの宣言内でbool
型の変数を作って:
class CMFCApplicationDlg : public CDialogEx
{
//(前略)
volatile bool m_bOnEnChangeEdit_InProgress; // 動作中なら true
//(後略)
}
コンストラクタで、その変数をfalse
に初期化しておく:
CMFCApplicationDlg::CMFCApplicationDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCAPPLICATION_DIALOG, pParent)
, m_bOnEnChangeEdit_InProgress { false }
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
再起呼び出しが起こるのは、SetWindowText()
を呼ぶ部分なので、その4行の前でフラグを立て、その4行の後ろで下ろすという処理を書いた。(※1, ※2)
それから、関数の先頭でフラグが立っているときは処理を行わず、return
する、という処理を書いた。(※3)
void CMFCApplicationDlg::OnEnChangeEdit()
{
if (m_bOnEnChangeEdit_InProgress) // フラグが立っているなら
return; // 何もせず return する(※3)
CWnd* pFocus = GetFocus();
CString _str;
pFocus->GetWindowText(_str);
_RPTW1(_CRT_WARN, L"CMFCApplicationDlg::OnEnChangeEdit() %s\n", _str);
m_bOnEnChangeEdit_InProgress = true; // フラグを立てて(※1)
GetDlgItem(IDC_EDIT1)->SetWindowText(_str);
GetDlgItem(IDC_EDIT2)->SetWindowText(_str);
GetDlgItem(IDC_EDIT3)->SetWindowText(_str);
GetDlgItem(IDC_EDIT4)->SetWindowText(_str);
m_bOnEnChangeEdit_InProgress = false; // フラグを下ろす(※2)
}
動くようにはなったが、なんか
これで動くようにはなったが、なんか変というか、"123" と入力したのに、"321"となってしまう。
DDXというものを使うとうまくいく
DDXというものを使うとうまくいくことが分かった。
まず、先ほど導入したフラグ変数は削除し、Edit Control
の一番左のやつを右クリックして「変数の追加」を行う。
カテゴリの部分が「コントロール」とか「Control」になっていたら「値」か「Value」に変更する。
変数の種類は「CString」になっているはず。
名前の部分には、(なんでもいいんだけど)、「m_xvEdit1」と入れておく。
これでウィザードを進めて完了させると、クラスの宣言の中にメンバ変数が追加される:
class CMFCApplicationDlg : public CDialogEx
{
//(前略)
CString m_xvEdit1;
//(後略)
}
それから、.cpp ファイルの中のコンストラクタには、追加した変数の初期化の行が追加される:
CMFCApplicationDlg::CMFCApplicationDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCAPPLICATION_DIALOG, pParent)
, m_xvEdit1(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
それから、DoDataExchange()
関数のなかに、DDX_Text
から始まる行も追加される:
void CMFCApplicationDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT1, m_xvEdit1);
}
これで、このm_xvEdit1
という変数を介して、IDC_EDIT1のIDを持つコントロールの内容を取得したり、設定できるようになる。
コントロールの内容を変数に持ってくるには、UpdateData(TRUE)
を呼ぶ。
逆に、変数の内容をコントロールに書き込むには、UpdateData(FALSE)
を呼ぶ。
コントロール コントロール
UpdateData(TRUE) ↓ ↑ UpdateData(FALSE)
m_xvEdit1 m_xvEdit1
今回は、コントロールが4つあるので、ウィザードで自動的に作られた行をコピーして、m_xvEdit2
, m_xvEdit3
, m_xvEdit4
を作ってしまう。
class CMFCApplicationDlg : public CDialogEx
{
//(前略)
CString m_xvEdit1;
CString m_xvEdit2;
CString m_xvEdit3;
CString m_xvEdit4;
//(後略)
}
CMFCApplicationDlg::CMFCApplicationDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCAPPLICATION_DIALOG, pParent)
, m_xvEdit1(_T(""))
, m_xvEdit2(_T(""))
, m_xvEdit3(_T(""))
, m_xvEdit4(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CMFCApplicationDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT1, m_xvEdit1);
DDX_Text(pDX, IDC_EDIT2, m_xvEdit2);
DDX_Text(pDX, IDC_EDIT3, m_xvEdit3);
DDX_Text(pDX, IDC_EDIT4, m_xvEdit4);
}
そのうえで、OnEnChangeEdit()
の内容を以下のようにする:
void CMFCApplicationDlg::OnEnChangeEdit()
{
CWnd* pFocus = GetFocus();
CString _str;
pFocus->GetWindowText(_str);
_RPTW1(_CRT_WARN, L"CMFCApplicationDlg::OnEnChangeEdit() %s\n", _str);
m_xvEdit1 = _str;
m_xvEdit2 = _str;
m_xvEdit3 = _str;
m_xvEdit4 = _str;
UpdateData(FALSE);
}
SetWindowText()
を呼ぶ代わりに、m_xvEditn
変数を経由させてコントロールの値を変更しています。
SetWindowText()
の呼び出しでは、そのコントロールが変更されたON_EN_CHANGE
というイベントが発生していましたが、DDXを使用した値の更新では、そのイベントは発生しないようです。