C++ Builder > (フォームとして)UIとロジック実装 > 複数のプロファイルの切替え > _profileファイルとdetailファイル

```txt:動作環境
C++ Builder XE4
```

前回: [C++ Builder > ファイル処理検討 > 複数のプロファイルの切替え > _profファイルとdetailファイルでの読書き](https://qiita.com/7of9/items/12f8a7501ddfc0a65da3)

上記の処理に関して自分が使いたい例を実装してみた。

### 使用外部ソース

二つの自作ソースを利用する。
**これらの自作ソースがないために、閲覧者の環境では動きません。**
コードは参考程度にどうぞ。

1. UtilSetting.cppと.h
2. UtilBackupTMemo.cppと.h

詳細は非公開。

処理内容は以下。

- UtilSetting
    - Form_GetData() : 対象のコンポーネント登録
    - Load() : JSONファイルからの読込み
    - Save() : JSONファイルへの保存
    - Form_SetData_AllKeys() : 読込んだ全ての項目をフォームに反映 (Tagによる制限なし)
    - GetSaveDir() : 保存先フォルダの取得
- UtilBackupTMemo
    - DoBackup() : TMemoのファイルへの保存
    - DoRecover() : ファイルからTMemoへの読込み
    - GetSaveDir() : 保存先フォルダの取得
    
(補足: namingがいまいちであるが、すでに使用されているため容易に名前変更できない。最初の実装時に名前をよく考えないとこうなる）。

### ファイルの構成

- Unit1: メイン画面
    - SettingUnitを開くボタンのみある
- SettingUnit: 設定画面
    - プロファイルの選択、削除ボタンがある
    - 選択プロファイル保存のためのOk, Cancelボタンがある
    - 詳細のTEditがある
- NewProfileDialog: 新規プロファイル名指定ダイアログ
    - SettingUnitから「Add」にて新規プロファイル追加時に使う
        - プロファイル追加は頻度が低いと思われるため、設定画面と分けた

### code 

```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 で管理されるコンポーネント
	TButton *B_setting;
	void __fastcall B_settingClick(TObject *Sender);
private:	// ユーザー宣言
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
```

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

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include "SettingUnit.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::B_settingClick(TObject *Sender)
{
	FormSetting->ShowModal();
}
//---------------------------------------------------------------------------
```

```SettingUnit.h
//---------------------------------------------------------------------------

#ifndef SettingUnitH
#define SettingUnitH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include "editex.h"
//---------------------------------------------------------------------------
class TFormSetting : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TComboBox *CMB_profile;
	TButton *B_add;
	TButton *B_delete;
	TGroupBox *GroupBox1;
	TEdit *E_color;
	TButton *B_ok;
	TButton *B_cancel;
	TButton *B_applyDetail;
	TLabel *Label1;
	TMemo *M_profileItems;
	TEdit *E_profileItemIndex;
	TLabel *Label2;
	TLabel *Label3;
	void __fastcall B_addClick(TObject *Sender);
	void __fastcall CMB_profileChange(TObject *Sender);
	void __fastcall B_deleteClick(TObject *Sender);
	void __fastcall B_okClick(TObject *Sender);
	void __fastcall B_cancelClick(TObject *Sender);
	void __fastcall B_applyDetailClick(TObject *Sender);
	void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
private:	// ユーザー宣言
	void __fastcall Setting_load_profile(void);
	void __fastcall Setting_load_detail(String profName);
	void __fastcall Setting_save_profile(void);
	void __fastcall Setting_save_detail(String profName);
	void __fastcall Setting_delete_detail(String profName);
	void __fastcall SetDefaultDetail(void);
public:		// ユーザー宣言

	// コンストラクタ・デストラクタ
	__fastcall TFormSetting(TComponent* Owner);
	__fastcall ~TFormSetting();
};
//---------------------------------------------------------------------------
extern PACKAGE TFormSetting *FormSetting;
//---------------------------------------------------------------------------
#endif
```

```SettingUnit.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <IOUtils.hpp>
#include "SettingUnit.h"
#include "NewProfileDialog.h"
//
#include "UtilSetting.h"
#include "UtilBackupTMemo.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "editex"
#pragma resource "*.dfm"
TFormSetting *FormSetting;
//---------------------------------------------------------------------------

// 設定保存関連
static const String kSettingFileExt = L".json";
static String s_settingProfileFileName = L""; // コンストラクタで設定すること
static String s_settingDetailFilePrefix = L""; // コンストラクタで設定すること
// TComboBox->Items保存用TMemo
static const String kSuffix_pofileItems = L"_profileItems.csv";
static CUtilBackupTMemo *s_bkMemo;
static String s_filename_profileItems; // コンストラクタで設定すること

//---------------------------------------------------------------------------
__fastcall TFormSetting::TFormSetting(TComponent* Owner)
	: TForm(Owner)
{
	s_settingProfileFileName = this->Name + L"_profile" + kSettingFileExt; // フォーム名を設定ファイル名に用いる
	s_settingDetailFilePrefix = this->Name + L"_detail"; // フォーム名を設定ファイル名に用いる
	s_filename_profileItems = this->Name + kSuffix_pofileItems;

	// Items保存用
	String dstDir = CUtilBackupTMemo::GetSaveDir();
	s_bkMemo = new CUtilBackupTMemo(M_profileItems, dstDir, s_filename_profileItems);

	// 読込み
	Setting_load_profile();
	Setting_load_detail(CMB_profile->Text);
}

__fastcall TFormSetting::~TFormSetting()
{
	// Items保存用
	delete s_bkMemo;
	s_bkMemo = NULL;
}
//---------------------------------------------------------------------------
void __fastcall TFormSetting::B_addClick(TObject *Sender)
{
	// 1. プロファイルの追加
	if (DialogNewProfile->ShowModal() != mrOk) {
		return;
	}
	String newItem = DialogNewProfile->GetProfileName();
	if (newItem.Length() == 0) {
		return; // error
	}
	CMB_profile->Items->Add(newItem);
	int cnt = CMB_profile->Items->Count;
	CMB_profile->ItemIndex = cnt - 1;

	// 2. 詳細をデフォルトにする
	SetDefaultDetail();

	// 3. 保存
	Setting_save_profile();
	Setting_save_detail(CMB_profile->Text);
}

void __fastcall TFormSetting::CMB_profileChange(TObject *Sender)
{
	Setting_load_detail(CMB_profile->Text);
}

void __fastcall TFormSetting::B_deleteClick(TObject *Sender)
{
	if (CMB_profile->Items->Count == 0) {
        return;
	}
	String cnfmsg = L"選択したプロファイル[" + CMB_profile->Text + L"]を削除しますか?";
	if (IDOK != MessageBox(Handle, cnfmsg.c_str(), L"確認",
		MB_ICONQUESTION | MB_OKCANCEL | MB_DEFBUTTON2) ) {
			return;
	}

	// 1. 現在のプロファイルの詳細を削除
	Setting_delete_detail(CMB_profile->Text);

	// 2. 現在のプロファイルをコンボボックスから削除
	String name = CMB_profile->Text;
	int idx = CMB_profile->Items->IndexOf(name);
	CMB_profile->Items->Delete(idx);

	// TODO: 0a > 1つの場合の削除テスト

	// 3. 既存プロファイルに切替え
	CMB_profile->ItemIndex = 0;
	Setting_load_detail(CMB_profile->Text);

	// 4. プロファイル保存
	Setting_save_profile();
}

void __fastcall TFormSetting::B_applyDetailClick(TObject *Sender)
{
	if (CMB_profile->Items->Count == 0) {
		return;
	}
	Setting_save_detail(CMB_profile->Text);
}

//---------------------------------------------------------------------------
// OkとCancel、閉じる処理
//
void __fastcall TFormSetting::B_okClick(TObject *Sender)
{
	if (CMB_profile->Items->Count == 0) {
		ShowMessage(L"プロファイルが設定されていません。");
		return;
	}

	Setting_save_profile();
	Setting_save_detail(CMB_profile->Text);

	Close();
}

void __fastcall TFormSetting::B_cancelClick(TObject *Sender)
{
	// 前のプロファイル読込み (FormCloseでも同じ処理をしている)
	Setting_load_profile();
	Setting_load_detail(CMB_profile->Text);

	Close();
}

void __fastcall TFormSetting::FormClose(TObject *Sender, TCloseAction &Action)
{
	// OK, Cancelボタンでない閉じる場合に対応
	Setting_load_profile();
	Setting_load_detail(CMB_profile->Text);
}

//---------------------------------------------------------------------------
// デフォルト設定
//
void __fastcall TFormSetting::SetDefaultDetail(void)
{
    // 例
	E_color->Text = L"Blue";
}

//---------------------------------------------------------------------------
// 設定の読込と保存
//
void __fastcall TFormSetting::Setting_load_profile(void)
{
	// 1. 設定の読込み (ItemIndex)
	TForm *formPtr = this;

	CUtilSetting *setPtr = new CUtilSetting();
	setPtr->Load(/* absPath=*/false, s_settingProfileFileName, formPtr->Name); // absPath=falseの場合、保存先フォルダを自動指定
	setPtr->Form_SetData_AllKeys(formPtr);

	delete setPtr;
	setPtr = NULL;

	// 2. 設定の読込み (Items)
	s_bkMemo->DoRecover();

	// 3. ItemsとItemIndexの読込み
	CMB_profile->Items->Assign(M_profileItems->Lines);
	CMB_profile->ItemIndex = StrToIntDef(E_profileItemIndex->Text, -1);
}

void __fastcall TFormSetting::Setting_save_profile(void)
{
	// 1. TComboBoxのItemsとItemIndexの退避
	M_profileItems->Lines->Assign(CMB_profile->Items);
	E_profileItemIndex->Text = IntToStr(CMB_profile->ItemIndex);

	// 2. 設定の保存 (ItemIndex)
	CUtilSetting *setPtr = new CUtilSetting();

	TForm *formPtr = this;
	setPtr->Form_GetData(E_profileItemIndex);
	setPtr->Save(/* absPath=*/false, s_settingProfileFileName, formPtr->Name); // absPath=falseの場合、保存先フォルダを自動指定

	delete setPtr;
	setPtr = NULL;

	// 3. 設定の保存 (Items)
	s_bkMemo->DoBackup();
}

void __fastcall TFormSetting::Setting_load_detail(String profName)
{
	if (profName.Length() == 0) {
		E_color->Text = L"";
		return;
	}

	// debug
	String msg = L"Setting_load_detail";
	OutputDebugString(msg.c_str());

	// 
	TForm *formPtr = this;

	CUtilSetting *setPtr = new CUtilSetting();

	String filename = s_settingDetailFilePrefix + L"." + profName;
	setPtr->Load(/* absPath=*/false, filename, formPtr->Name); // absPath=falseの場合、保存先フォルダを自動指定
	setPtr->Form_SetData_AllKeys(formPtr);

	delete setPtr;
	setPtr = NULL;
}

void __fastcall TFormSetting::Setting_save_detail(String profName)
{
	if (profName.Length() == 0) {
        return;
	}

	CUtilSetting *setPtr = new CUtilSetting();

	String filename = s_settingDetailFilePrefix + L"." + profName;
	TForm *formPtr = this;
	setPtr->Form_GetData(E_color);
	setPtr->Save(/* absPath=*/false, filename, formPtr->Name); // absPath=falseの場合、保存先フォルダを自動指定

	delete setPtr;
	setPtr = NULL;
}

void __fastcall TFormSetting::Setting_delete_detail(String profName)
{
	if (profName.Length() == 0) {
		return;
	}

	// 削除処理
	String trgDir = CUtilSetting::GetSaveDir();
	String filepath = trgDir + L"\\" + s_settingDetailFilePrefix + L"." + profName;
	if (FileExists(filepath) == false) {
		return;
	}

	try {
		TFile::Delete(filepath);
	} catch (Exception &exc) {
		OutputDebugString(exc.Message.c_str());
	}
}
//---------------------------------------------------------------------------
```

```NewProfileDialog.h
//----------------------------------------------------------------------------
#ifndef NewProfileDialogH
#define NewProfileDialogH
//----------------------------------------------------------------------------
#include <Buttons.hpp>
#include <StdCtrls.hpp>
#include <Controls.hpp>
#include <Forms.hpp>
#include <Graphics.hpp>
#include <Classes.hpp>
#include <SysUtils.hpp>
#include <Windows.hpp>
#include <System.hpp>
//----------------------------------------------------------------------------
class TDialogNewProfile : public TForm
{
__published:
	TLabel *Label1;
	TEdit *E_name;
	TButton *OKBtn;
	TButton *CancelBtn;
	void __fastcall FormShow(TObject *Sender);
private:
public:
	String __fastcall GetProfileName(void);
	virtual __fastcall TDialogNewProfile(TComponent* AOwner);
};
//----------------------------------------------------------------------------
extern PACKAGE TDialogNewProfile *DialogNewProfile;
//----------------------------------------------------------------------------
#endif    
```

```NewProfileDialog.cpp
//---------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "NewProfileDialog.h"
//--------------------------------------------------------------------- 
#pragma resource "*.dfm"
TDialogNewProfile *DialogNewProfile;
//---------------------------------------------------------------------
__fastcall TDialogNewProfile::TDialogNewProfile(TComponent* AOwner)
	: TForm(AOwner)
{
}

String __fastcall TDialogNewProfile::GetProfileName(void)
{
	return E_name->Text;
}
//---------------------------------------------------------------------
void __fastcall TDialogNewProfile::FormShow(TObject *Sender)
{
	E_name->Text = L"";
}
//---------------------------------------------------------------------------
```
    
### 実行例

メイン画面
![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/118965bd-9dff-d266-a40e-7da5cb6d7551.png)

設定画面
![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/be49a851-e6aa-815a-93d9-d24885824324.png)

新規プロファイル名指定ダイアログ表示
![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/c42794a6-7610-ee14-142c-d5b829c295fd.png)

作成されたファイル
![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/36669a3b-fdb0-771a-5ab4-19eb5d4f473a.png)

### ファイル例

```FormSetting_detail.DeltaFlyer
{"E_color":"Blue1"}
```

```FormSetting_profile.json
{"E_profileItemIndex":"1"}
```

```FormSetting_profileItems.csv
DeltaFlyer
DeltaFlyer2
Voyager
Runabout
```

### 注意点: 再利用性

フォームで実装しているため、複数プロジェクト間での再利用性は低い。

ロジックを分離することで、再利用性を高める方が良い。

このまま使うと将来のメンテナンスコストが跳ね上がる。
(フォームをコピーして、プロジェクト独自のコンポーネント追加、をすると似たようなコードが氾濫する。可読性も悪く、バグ対処もしにくくなる）。

## 処理の外部ソース化
(追記 2017/11/05)

上記の処理を外部ソース化した。
結果としてSettingUnit.cpp内が読みやすくなり、複数プロジェクトへの適用作業を軽減することができるだろう。

上記で「動くようにした」上で「正しく（使いまわししやすく）」した。
参考: [動くようにする、正しくする、速くする。](https://qiita.com/nonbiri15/items/4af917131d02d9101f53)

```SettingUnit.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <IOUtils.hpp>
#include "SettingUnit.h"
#include "NewProfileDialog.h"
//
#include "UtilSetting.h"
#include "UtilProfileSetting.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "editex"
#pragma resource "*.dfm"
TFormSetting *FormSetting;
//---------------------------------------------------------------------------

// UtilProfileSetting
static CUtilProfileSetting *s_utilProfileSettting; // コンストラクタで設定すること

//---------------------------------------------------------------------------
__fastcall TFormSetting::TFormSetting(TComponent* Owner)
	: TForm(Owner)
{
	Profile_setup();
}

__fastcall TFormSetting::~TFormSetting()
{
	Profile_delete();
}

//---------------------------------------------------------------------------
// ProfileSetting
//
void __fastcall TFormSetting::Profile_setup(void)
{
	// UtilProfileSetting
	s_utilProfileSettting = new CUtilProfileSetting(CMB_profile, M_profileItems, E_profileItemIndex, this);
	TObject *objList[] = {
		E_color,
		E_pilot,
	};
	int numObject = sizeof(objList) / sizeof(objList[0]);
	s_utilProfileSettting->RegisterDetailComponents(objList, numObject);
	s_utilProfileSettting->SetFunction_detailDefault(SetDefaultDetail);
	s_utilProfileSettting->SetFunction_detailClear(ClearDetail);
	//

	// 読込み
	s_utilProfileSettting->Profile_Load();
	s_utilProfileSettting->Detail_Load();
}

void __fastcall TFormSetting::Profile_delete(void)
{
	delete s_utilProfileSettting;
	s_utilProfileSettting = NULL;
}
//---------------------------------------------------------------------------
void __fastcall TFormSetting::B_addClick(TObject *Sender)
{
	if (DialogNewProfile->ShowModal() != mrOk) {
		return;
	}
	String newItem = DialogNewProfile->GetProfileName();
	s_utilProfileSettting->Profile_Add(newItem);
}

void __fastcall TFormSetting::CMB_profileChange(TObject *Sender)
{
	s_utilProfileSettting->Detail_Load();
}

void __fastcall TFormSetting::B_deleteClick(TObject *Sender)
{
	s_utilProfileSettting->Profile_Delete(Handle, /*english=*/false);
}

void __fastcall TFormSetting::B_applyDetailClick(TObject *Sender)
{
	if (CMB_profile->Items->Count == 0) {
		return;
	}
	s_utilProfileSettting->Detail_Save();
}

//---------------------------------------------------------------------------
// OkとCancel、閉じる処理
//
void __fastcall TFormSetting::B_okClick(TObject *Sender)
{
	if (CMB_profile->Items->Count == 0) {
		ShowMessage(L"プロファイルが設定されていません。");
		return;
	}

	s_utilProfileSettting->Profile_Save();
	s_utilProfileSettting->Detail_Save();

	Close();
}

void __fastcall TFormSetting::B_cancelClick(TObject *Sender)
{
	// 前のプロファイル読込み (FormCloseでも同じ処理をしている)
	s_utilProfileSettting->Profile_Load();
	s_utilProfileSettting->Detail_Load();

	Close();
}

void __fastcall TFormSetting::FormClose(TObject *Sender, TCloseAction &Action)
{
	// OK, Cancelボタンでない閉じる場合に対応
	s_utilProfileSettting->Profile_Load();
	s_utilProfileSettting->Detail_Load();
}

//---------------------------------------------------------------------------
// 設定のデフォルト代入・クリア
//
void __fastcall TFormSetting::SetDefaultDetail(void)
{
	// 例
	E_color->Text = L"Blue";
	E_pilot->Text = L"Someone";
}
void __fastcall TFormSetting::ClearDetail(void)
{
	// 例
	E_color->Text = L"";
	E_pilot->Text = L"";
}
//---------------------------------------------------------------------------
```

```UtilProfileSetting.h
//---------------------------------------------------------------------------

#ifndef UtilProfileSettingH
#define UtilProfileSettingH

//---------------------------------------------------------------------------
#include "UtilBackupTMemo.h"

/*
以下の設定項目の保存と読込みを行う
- プロファイル
- 詳細

依存ライブラリ: [UtilSetting]
依存ライブラリ: [UtilBackupTMemo]
*/

class CUtilProfileSetting {
private:
	TComboBox *m_comboPtr; // profileを選択するコンボボックスのポインタ
	TMemo *m_itemsPtr; // TComboBoxのItemsを保持するTMemo
	TEdit *m_itemindexPtr; // TComboBoxのItemIndexを保持するTEdit
	//
	TForm *m_formPtr; // 設定の読込み、保存対象フォーム
	TObjectList *m_objList; // 詳細のコンポーネントを登録する
	int m_numObject; // 詳細のコンポーネント数
	CUtilBackupTMemo *m_backupTmemo; // TComboBoxのItemsの保存と読込みに使う
	String m_filename_profile; // profileのJSONファイル名
	// デフォルト値の設定関数ポインタ
	typedef void __fastcall (__closure *TMethodDefault)(void);
	TMethodDefault m_funcDefaultPtr; 
	typedef void __fastcall (__closure *TMethodClear)(void);
	TMethodClear m_funcClearPtr;
	//
	String getDetailFilename(String profName);
public:
	// プロファイル
	void __fastcall Profile_Save(void);
	void __fastcall Profile_Load(void);
	void __fastcall Profile_Add(String newItem);
	void __fastcall Profile_Delete(HWND handle, bool english);
	// 詳細
	void __fastcall Detail_Save(void);
	void __fastcall Detail_Load(void);
	void __fastcall Detail_Delete(void);

	// コンストラクタ・デストラクタ、セットアップ関連
	__fastcall CUtilProfileSetting(TComboBox *cmbPtr, TMemo *itemsPtr, TEdit *itemindexPtr, TForm *formPtr);
	void __fastcall RegisterDetailComponents(TObject *objs[], int numObject);
	void __fastcall SetFunction_detailDefault(TMethodDefault ptr);
	void __fastcall SetFunction_detailClear(TMethodClear ptr);
	__fastcall ~CUtilProfileSetting();
};

#endif
```