どのEdit Controlを編集しても、他のEdit Controlが同じ内容になるようにする。

# どのEdit Controlを編集しても、他のEdit Controlが同じ内容になるようにする。
複数のEdit Controlを用意して、どのコントロールの編集を行っても、他のコントロールに同様の内容が設定される、というのを書こうと思った。

それで、Edit Control を4つくらい貼り付けた。

![pic1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135506/da9af2cb-77d7-6376-038c-62f365026b92.png)

作成された４つのEdit Controlには、`IDC_EDIT1`, `IDC_EDIT2`, `IDC_EDIT3`, `IDC_EDIT4` という ID が振られたようだ。

![Resource_h.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135506/1583ed2c-5791-548a-8ae2-c0825e4e28a2.png "Resource.h")

そして、そのうちの一つをダブルクリックして、`ON_EN_CHANGE` に対するイベントハンドラを追加した。

```cpp:MFCApplicationDlg.cpp
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つのコントロールも、この一つの関数でイベントをハンドルできるように、ソースコードを書き換えた。

```cpp:MFCApplicationDlg.cpp
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指定の部分を変えた。

これで、どのコントロールに対して変更が行われても、この一つの関数が呼ばれるようになる。

## 修正された部分の文字列を取得する
とりあえず修正内容を取得できるかのテストのために、以下のように書いた：
```cpp:MFCApplicationDlg.cpp
void CMFCApplicationDlg::OnEnChangeEdit()
{
	CWnd* pFocus = GetFocus();
	CString _str;
	pFocus->GetWindowText(_str);

 	_RPTW1(_CRT_WARN, L"CMFCApplicationDlg::OnEnChangeEdit() %s\n", _str);
}
```
修正される瞬間には、そのコントロールはフォーカスを持っているはずなので、フォーカスを持っているコントロールに対して `GetWindowText()`を行って、修正内容を取得する。

`_RPTW1()`マクロを使用して、取得した内容をデバッグプリントする。

![pic2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135506/f64ec8d5-31a3-0876-519d-bbf27eb5cb5f.png)

左のコントロールから順に数文字ずつ入力すると、1文字入力するたびにイベントハンドラが呼ばれている。

どのコントロールに対して入力しても、同じ`OnEnChangeEdit()`関数が呼ばれている。

## 入力されたものを他のコントロールに設定する

修正された文字列を取得できたので、それを他のコントロールに書くという処理を書いた：

```cpp:MFCApplicationDlg.cpp
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 エラーになった：

![StackOverflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135506/416a6bdd-8551-8f39-db89-763d0bad957f.png)

デバッグプリントが出力されるところには、以下の行が大量に出ていたので、たぶん再起呼び出しの無限ループ的なことになっているようだった：
```
CMFCApplicationDlg::OnEnChangeEdit() 1
CMFCApplicationDlg::OnEnChangeEdit() 1
CMFCApplicationDlg::OnEnChangeEdit() 1
・・・
```

プログラムを起動して、Edit Controlに対して入力を行うと、`ON_EN_CHANGE`イベントが発生するが、`SetWindowText()`を呼び出しても`ON_EN_CHANGE`イベントが発生するようだ。そのため、イベントハンドラ関数の処理中にイベントハンドラが呼ばれることになり、スタックを食いつぶしてしまっている。んだと思う。

## 処理中を示すフラグ変数を作って、無限処理地獄を回避する。

メンバ変数として、`m_bOnEnChangeEdit_InProgress` という`bool`型の変数を作り、`OnEnChangeEdit()`関数が動作中であることを示すようにした。

まず、クラスの宣言内で`bool`型の変数を作って：
```cpp:MFCApplicationDlg.h
class CMFCApplicationDlg : public CDialogEx
{
	//（前略）
   	volatile bool m_bOnEnChangeEdit_InProgress;	// 動作中なら true
	//（後略）
}
```

コンストラクタで、その変数を`false`に初期化しておく：
```cpp:MFCApplicationDlg.cpp
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)

```cpp:MFCApplicationDlg.cpp
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"となってしまう。

![321.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135506/5865e48d-f43d-d15a-d248-ddc9759f45bc.png)

## DDXというものを使うとうまくいく

DDXというものを使うとうまくいくことが分かった。

まず、先ほど導入したフラグ変数は削除し、`Edit Control`の一番左のやつを右クリックして「変数の追加」を行う。

カテゴリの部分が「コントロール」とか「Control」になっていたら「値」か「Value」に変更する。

変数の種類は「CString」になっているはず。

名前の部分には、（なんでもいいんだけど）、「m_xvEdit1」と入れておく。

これでウィザードを進めて完了させると、クラスの宣言の中にメンバ変数が追加される：

```cpp:MFCApplicationDlg.h
class CMFCApplicationDlg : public CDialogEx
{
	//（前略）
	CString m_xvEdit1;
	//（後略）
}
```

それから、.cpp ファイルの中のコンストラクタには、追加した変数の初期化の行が追加される：

```cpp:MFCApplicationDlg.cpp
CMFCApplicationDlg::CMFCApplicationDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_MFCAPPLICATION_DIALOG, pParent)
	, m_xvEdit1(_T(""))
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
```

それから、`DoDataExchange()`関数のなかに、`DDX_Text`から始まる行も追加される：

```cpp:MFCApplicationDlg.cpp
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` を作ってしまう。

```cpp:MFCApplicationDlg.h
class CMFCApplicationDlg : public CDialogEx
{
	//（前略）
	CString m_xvEdit1;
	CString m_xvEdit2;
	CString m_xvEdit3;
	CString m_xvEdit4;
	//（後略）
}
```

```cpp:MFCApplicationDlg.cpp
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()`の内容を以下のようにする：

```cpp:MFCApplicationDlg.cpp
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を使用した値の更新では、そのイベントは発生しないようです。

![running.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135506/afe90459-9db8-5ef2-596d-d84dcc173dbf.gif)