0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-09-15

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

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

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

pic1.png

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

Resource_h.png

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

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

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指定の部分を変えた。

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

修正された部分の文字列を取得する

とりあえず修正内容を取得できるかのテストのために、以下のように書いた:

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

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

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

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

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

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

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

CMFCApplicationDlg::OnEnChangeEdit() 1
CMFCApplicationDlg::OnEnChangeEdit() 1
CMFCApplicationDlg::OnEnChangeEdit() 1
・・・

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

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

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

まず、クラスの宣言内でbool型の変数を作って:

MFCApplicationDlg.h
class CMFCApplicationDlg : public CDialogEx
{
	//(前略)
   	volatile bool m_bOnEnChangeEdit_InProgress;	// 動作中なら true
	//(後略)
}

コンストラクタで、その変数をfalseに初期化しておく:

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)

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

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

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

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

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

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

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

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

MFCApplicationDlg.h
class CMFCApplicationDlg : public CDialogEx
{
	//(前略)
	CString m_xvEdit1;
	//(後略)
}

それから、.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から始まる行も追加される:

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 を作ってしまう。

MFCApplicationDlg.h
class CMFCApplicationDlg : public CDialogEx
{
	//(前略)
	CString m_xvEdit1;
	CString m_xvEdit2;
	CString m_xvEdit3;
	CString m_xvEdit4;
	//(後略)
}
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()の内容を以下のようにする:

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?