c++ builder / UI > 1000個のコンポーネントのうち、どれかが変更されたことを確認する > 項目の保存 / 比較

```txt:動作確認
C++ Builder XE4
```

関連 http://qiita.com/7of9/items/ea52f618de6b465299ab

## 背景

- フォームに1000個のコンポーネントがある
- そのどれかが変更されたら、「初期値と異なる」とする

1000個のコンポーネントのonChangeメソッドを使うのは面倒そうだ。

## 方法

ある時点のスナップショットを取る。
そのスナップショットと比較することで、変更があったかどうかを知る。

## code v0.1

TEdit, TRadioButton, TCheckBoxの3種類のコンポーネントに対応。
各項目の最大数は100としているが用途によって増やせばいい。

```Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TEdit *Edit1;
	TCheckBox *CheckBox1;
	TRadioButton *RadioButton1;
	TButton *B_save;
	TCheckBox *CheckBox2;
	TEdit *Edit2;
	TRadioButton *RadioButton2;
	TButton *B_check;
	void __fastcall B_saveClick(TObject *Sender);
	void __fastcall FormShow(TObject *Sender);
	void __fastcall B_checkClick(TObject *Sender);
private:	// ユーザー宣言
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
```

```Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------

static const int kMaxnumItems = 100;
typedef struct tag_Config {
	bool checkBoxChecked[kMaxnumItems];
	int numCheckbox;
	bool radioButtonChecked[kMaxnumItems];
	int numRadioButton;
	String editText[kMaxnumItems];
	int numEdit;
} Config_t;

static Config_t s_config;

static initConfig(void)
{
	s_config.numCheckbox = 0;
	s_config.numRadioButton = 0;
	s_config.numEdit = 0;
}

void __fastcall TForm1::B_saveClick(TObject *Sender)
{
	initConfig();

	for(int idx = 0; idx < ComponentCount; idx++) {
		TComponent *ptarget = Components[idx];

		if (dynamic_cast<TCheckBox *>(ptarget) != NULL) {
			TCheckBox *chkPtr = (TCheckBox *)ptarget;
			int itmidx = (s_config.numCheckbox)++;
			s_config.checkBoxChecked[itmidx] = chkPtr->Checked;
		}
		if (dynamic_cast<TRadioButton *>(ptarget) != NULL) {
			TRadioButton *rdbPtr = (TRadioButton *)ptarget;
			int itmidx = (s_config.numRadioButton)++;
			s_config.radioButtonChecked[itmidx] = rdbPtr->Checked;
		}
		if (dynamic_cast<TEdit *>(ptarget) != NULL) {
			TEdit *edPtr = (TEdit *)ptarget;
			int itmidx = (s_config.numEdit)++;
			s_config.editText[itmidx] = edPtr->Text;
		}
	}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
	initConfig();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::B_checkClick(TObject *Sender)
{
	int chkbxidx = 0; // index for TCheckBox
	int rdbidx = 0; // index for TRadioButton
	int edidx = 0; // index for TEdit

	bool changed = false;

	for(int idx = 0; idx < ComponentCount; idx++) {
		TComponent *ptarget = Components[idx];

		if (dynamic_cast<TCheckBox *>(ptarget) != NULL) {
			TCheckBox *chkPtr = (TCheckBox *)ptarget;
			if (s_config.checkBoxChecked[chkbxidx] != chkPtr->Checked) {
				changed = true;
				break;
			}
			chkbxidx++;
		}

		if (dynamic_cast<TRadioButton *>(ptarget) != NULL) {
			TRadioButton *rdbPtr = (TRadioButton *)ptarget;
			if (s_config.radioButtonChecked[rdbidx] != rdbPtr->Checked) {
				changed = true;
				break;
			}
			rdbidx++;
		}

		if (dynamic_cast<TEdit *>(ptarget) != NULL) {
			TEdit *edPtr = (TEdit *)ptarget;
			if (s_config.editText[edidx] != edPtr->Text) {
				changed = true;
				break;
			}
			edidx++;
		}
	}

	if (changed) {
		ShowMessage(L"Changed");
	} else {
		ShowMessage(L"Same as usual");
	}
}
//---------------------------------------------------------------------------
```

## UI例

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/03452f40-63f8-52a3-fbd4-3d67a47bed24.png)

B_saveを押した時にスナップショットを取り、B_check押下時に変更があったかどうかを知る。

変更があったら「Changed」が表示され、変更がなかったら「Same as usual」が表示される。


## 関連

dynamic_cast<>の使用はRemy Lebeauさんに教わった。
http://stackoverflow.com/questions/33060775/how-to-know-the-control-is-tlabel

@shiracamus さんに教わった書き方も拝借。
http://qiita.com/7of9/items/2e6e3c7880fafe8014d4#comment-ae913b64fdeba065f885

## 備考

Components[]のハッシュ値をとればもっと簡単になるかもしれない。

## code v0.2

異なるフォームにて使いまわせるように外部ファイル化した。

### 外部ファイル

```UtilSettingModified.h
//---------------------------------------------------------------------------

#ifndef UtilSettingModifiedH
#define UtilSettingModifiedH

//---------------------------------------------------------------------------

/*
フォーム上のコンポーネントの値が変更されたかをチェックする。
*/

static const int kMaxnumItems = 300;

class CUtilSettingModified {
private:
	typedef struct tag_Config {
	    bool checkBoxChecked[kMaxnumItems];
	    int numCheckbox;
	    bool radioButtonChecked[kMaxnumItems];
	    int numRadioButton;
	    String editText[kMaxnumItems];
	    int numEdit;
	} Config_t;

	Config_t m_config;

	void __fastcall initItems(void);

public:
	void __fastcall DoSave(TForm *formPtr);
	bool __fastcall HasChanged(TForm *formPtr);

	// コンストラクタ
	__fastcall CUtilSettingModified();	
};

#endif
```

```UtilSettingModified.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "UtilSettingModified.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)

#define DEBUG_DO_PRINT // デバッグ出力

/*
v0.1 2016/07/28
	- HasChanged()追加
	- DoSave()追加
	- initItems()追加
	- コンストラクタ追加
	- debug_outputDebugString()追加
*/

//---------------------------------------------------------------------------
static void debug_outputDebugString(String prefix, String msg)
{
	// あとでまとめてデバッグ出力をコメントアウトするため、関数にしている。

#ifdef DEBUG_DO_PRINT
	String work = L"UtilSettingModified >" + prefix + L": " + msg;
	OutputDebugString(work.c_str()); // @ debug_outputDebugString
#endif
}
//---------------------------------------------------------------------------
__fastcall CUtilSettingModified::CUtilSettingModified()
{
	initItems();
}
//---------------------------------------------------------------------------
void __fastcall CUtilSettingModified::initItems(void)
{
    m_config.numCheckbox = 0;
    m_config.numRadioButton = 0;
    m_config.numEdit = 0;	
}

//---------------------------------------------------------------------------
void __fastcall CUtilSettingModified::DoSave(TForm *formPtr)
{
    initItems();

    for(int idx = 0; idx < formPtr->ComponentCount; idx++) {
        TComponent *ptarget = formPtr->Components[idx];

        if (dynamic_cast<TCheckBox *>(ptarget) != NULL) {
            TCheckBox *chkPtr = (TCheckBox *)ptarget;
            int itmidx = (m_config.numCheckbox)++;
            m_config.checkBoxChecked[itmidx] = chkPtr->Checked;
        }
        if (dynamic_cast<TRadioButton *>(ptarget) != NULL) {
            TRadioButton *rdbPtr = (TRadioButton *)ptarget;
            int itmidx = (m_config.numRadioButton)++;
            m_config.radioButtonChecked[itmidx] = rdbPtr->Checked;
        }
        if (dynamic_cast<TEdit *>(ptarget) != NULL) {
            TEdit *edPtr = (TEdit *)ptarget;
            int itmidx = (m_config.numEdit)++;
            m_config.editText[itmidx] = edPtr->Text;
        }
    }
}

bool __fastcall CUtilSettingModified::HasChanged(TForm *formPtr)
{
    int chkbxidx = 0; // index for TCheckBox
    int rdbidx = 0; // index for TRadioButton
    int edidx = 0; // index for TEdit

    bool changed = false;

    for(int idx = 0; idx < formPtr->ComponentCount; idx++) {
        TComponent *ptarget = formPtr->Components[idx];

        if (dynamic_cast<TCheckBox *>(ptarget) != NULL) {
            TCheckBox *chkPtr = (TCheckBox *)ptarget;
            if (m_config.checkBoxChecked[chkbxidx] != chkPtr->Checked) {
                changed = true;
                break;
            }
            chkbxidx++;
        }

        if (dynamic_cast<TRadioButton *>(ptarget) != NULL) {
            TRadioButton *rdbPtr = (TRadioButton *)ptarget;
            if (m_config.radioButtonChecked[rdbidx] != rdbPtr->Checked) {
                changed = true;
                break;
            }
            rdbidx++;
        }

        if (dynamic_cast<TEdit *>(ptarget) != NULL) {
            TEdit *edPtr = (TEdit *)ptarget;
            if (m_config.editText[edidx] != edPtr->Text) {
                changed = true;
                break;
            }
            edidx++;
        }
    }

    return changed;
}
```

### 使用コード

```Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>

#include "UtilSettingModified.h"

//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TCheckBox *CheckBox1;
	TCheckBox *CheckBox2;
	TCheckBox *CheckBox3;
	TCheckBox *CheckBox4;
	TCheckBox *CheckBox5;
	TCheckBox *CheckBox6;
	TButton *B_save;
	TButton *B_check;
	void __fastcall B_saveClick(TObject *Sender);
	void __fastcall B_checkClick(TObject *Sender);
private:	// ユーザー宣言
	CUtilSettingModified *m_settingModified;
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
	__fastcall ~TForm1();
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
```

```Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
	m_settingModified = new CUtilSettingModified();
}
__fastcall TForm1::~TForm1()
{
	if (m_settingModified != NULL) {
		delete m_settingModified;
		m_settingModified = NULL;
	}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::B_saveClick(TObject *Sender)
{
	m_settingModified->DoSave(this);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::B_checkClick(TObject *Sender)
{
	if (m_settingModified->HasChanged(this)) {
		ShowMessage(L"Changed");
	} else {
     	ShowMessage(L"Same as before");
	}
}
//---------------------------------------------------------------------------
```

外部ファイル化することでUnit1自体の実装はすっきりするようになった。
ロジックをUIと分離できたかと思う。