profile
cppBuilder
fileIO

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

More than 1 year has passed since last update.
動作環境
C++ Builder XE4

前回: C++ Builder > ファイル処理検討 > 複数のプロファイルの切替え > _profファイルとdetailファイルでの読書き

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

使用外部ソース

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

  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

設定画面
qiita.png

新規プロファイル名指定ダイアログ表示
qiita.png

作成されたファイル
qiita.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内が読みやすくなり、複数プロジェクトへの適用作業を軽減することができるだろう。

上記で「動くようにした」上で「正しく(使いまわししやすく)」した。
参考: 動くようにする、正しくする、速くする。

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