日曜日にBCB!



Do It Yourselfでひとつプログラムでも作ってみませんか。


第15話「分割サイズカスタマイズ機能 その2」

前回に引き続き分割サイズカスタマイズ機能をプログラミングします。今回はメイン画面への変更です。メイン画面の変更点は以下の通りです。
  1. 分割サイズ情報の取り扱いの変更
  2. 分割サイズ情報を初期化ファイルから取得
  3. 分割サイズメニュー・リストの更新
  4. 分割サイズ編集画面の呼出し
  5. 分割スレッドとの情報受け渡しの変更
  6. メニュー・ツールバー更新の変更
  7. 分割サイズ情報を初期化ファイルに更新


分割サイズ情報の取り扱いの変更

これまでの寿分割では分割サイズを固定として、分割サイズは「1.44M」と「720K」のメニューTagプロパティーに定数として組込んでいました。しかしカスタマイズに対応するためには、分割サイズだけでは情報が不足します。前回作成した分割サイズのインスタンスを保持する必要があります。
分割サイズ情報はどこに記憶しましょうか? 分割サイズメニューの個々のメニューアイテムに、個々の分割サイズ情報を持たせるのが一番良いでしょう。しかし問題があります。分割サイズを沢山作ると、分割サイズメニューやサイズリストも多くなります。使う方からするとメニューやリストには表示する数があまり多いと使いづらくなります。そこで寿分割では定義した分割サイズを最大5個表示したいと思います。すると、分割サイズメニューの個々のメニューアイテムに分割サイズ情報を持たせることができなくなります。従って設定パラメータ構造体分割サイズのリストとして保持します。

改造設定パラメータ構造体

//---------------------------------------------------------------------------
// 設定パラメータ構造体
struct stKotoParam{
    TFont         *ViewFont;        // 表示フォント
    TColor        BGColor;          // 背景色
    bool          MenuVisible;      // メニュー表示
    bool          ToolBarVisible;   // ツールバー表示
    bool          StatusBarVisible; // ステータスバー表示
    enWINLOCATION WinLocation;      // ウィンド位置情報
    TStringList   *CutSizes;        // 分割サイズリスト
};
//---------------------------------------------------------------------------
分割サイズがメニューやリストから選択されたら、対応する分割サイズリストのリストアイテムから分割サイズ情報を取得するようにします。


分割サイズ情報を初期化ファイルから取得

分割サイズリストは初期化ファイルに記録して、寿分割を再度起動したときも利用できるようにします。まず、分割サイズ情報を初期化ファイルから読み取り、設定パラメータ構造体に記録します。ただし、寿分割を初めて起動した場合は、バージョンアップした場合は初期化ファイルに記録されていない場合もあります。そこで、初期化ファイルに記録されていないときはデフォルトとして、従来と同じく1.44Mと720Kとスキップ条件なしの空き容量全ての分割サイズ情報を自動的に作成します。
初期化ファイルには分割サイズ情報を以下の形式で記録します。

セクション キー 内容
Size 0、1、2、...の連番 文字列 名前+";"+サイズ+";"+スキップサイズ[単位]

例えば5つの分割サイズリストは以下のように記録します。
[Size]
0=2HD;1457664;0
1=2DD;728832;0
2=残り全て;0;0
3=残り全て;0;10%
4=残り全て;0;100K

なお、分割サイズ、スキップサイズは省略可とします。省略した場合は直前の';'も省略します。

セクション名をmain.hに追加します。
#define dfIniFileName "kotocut.ini"
#define ViewSection "View"
#define FontSection "Font"
#define PositionSection "Position"
#define SizeSection "Size"

まず寿分割の起動時に初期化ファイル読込み、分割サイズメニューの表示を行います。

改FormCreate関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::FormCreate(TObject *Sender)
{
// 分割サイズリスト作成
    KotoParam.CutSizes = new TStringList;           // 文字列リストオブジェクトインスタンス

// 初期設定
    AnsiString IniFilePath = ExtractFilePath(Application->ExeName) + dfIniFileName;  // 初期化ファイル名作成
    if (!FileExists(IniFilePath)) CreateIniFile(IniFilePath);   // 初期化ファイルがない場合は自動作成
    TIniFile *IniFile = new TIniFile(IniFilePath);              // 初期化ファイル取得
    GetCutSizes(IniFile);        // 分割サイズリスト取得
    StyleWindow(IniFile);        // ウィンド再現(フォント、位置、コントロール表示)
    delete IniFile;              // 初期化ファイル終了

    SetSizeMenu();                    // 分割サイズメニュー設定
    SizeMNUClick(Size1MNU);           // 初期分割サイズ→先頭
    DragAcceptFiles(Handle, true);    // ドロップ受け取り登録

// 分割ファイル起動時引数
    :

    UpdateMenu();    // メニュー更新
}
//---------------------------------------------------------------------------

GetCutSizes関数

初期化ファイルから分割サイズを読み込みます。
分割サイズ情報はVCL関数のReadSection関数を使って一旦文字列に全て読込みます。その後、各リストアイテムを'='や';'の区切り文字毎に取得し、分割名、分割サイズ、スキップサイズと単位を取得します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::GetCutSizes(TIniFile *IniFile)
{
    if (KotoParam.CutSizes->Count!=0) ClearCutSizes(KotoParam.CutSizes); // 既存分割サイズリストクリア

    TStringList *SectionList = new TStringList;             // 初期化ファイルセクション文字列リストインスタンス
    IniFile->ReadSections(SectionList);                     // セクション文字列リスト読込み
    if (SectionList->IndexOf(SizeSection)==-1){             // セクションがない(初めて起動、バージョンアップ)?
        Application->MessageBox("分割サイズの設定を自動的に行います",    // バージョンアップメッセージ表示(半角)
                                "寿分割バージョンアップ",
                                MB_OK);
        cCutInfo *CutSize;                          // 分割サイズ情報オブジェクトポインタ
        CutSize = new cCutInfo(SizeOf1_44M);                              // 1.44M分割サイズ情報オブジェクトインスタンス
        KotoParam.CutSizes->AddObject(NameOf1_44M, (TObject*)CutSize);   // 分割サイズリストに追加
        CutSize = new cCutInfo(SizeOf720K);                               // 720K分割サイズ情報オブジェクトインスタンス
        KotoParam.CutSizes->AddObject(NameOf720K,  (TObject*)CutSize);   // 分割サイズリストに追加
        CutSize = new cCutInfo(0, 0);                                     // 空き容量全て分割情報オブジェクトインスタンス
        KotoParam.CutSizes->AddObject(RemainAllTitle,  (TObject*)CutSize); // 分割サイズリストに追加
        return;            // バージョンアップ終了
    }

    IniFile->ReadSectionValues(SizeSection, KotoParam.CutSizes);    // 分割サイズ一覧取得

    int i;     // 読込み行数カウンタ(エラー発生時には読み込み件数を示す)
    for (i=0;i<KotoParam.CutSizes->Count;i++){      // 分割名、サイズ情報分離

        AnsiString InfoString = KotoParam.CutSizes->Strings[i];     // 分割情報文字列取得
        AnsiString ParamString;                                     // 分割各パラメータ取得バッファ
        int SizeByte, SkipByte=0;                                   // 分割サイズ、スキップサイズバッファ
        char SkipUnit=' ';                                          // スキップサイズ単位バッファ

        int delimPos = InfoString.Pos("=");         // キー区切り記号'='位置取得
        if (delimPos==0) goto error;                // 書式エラー?
        InfoString = InfoString.SubString(delimPos+1, InfoString.Length() - delimPos);  // 残り情報文字列取得

        delimPos = InfoString.Pos(";");             // 分割名区切り記号'='位置取得
        if (delimPos==0) goto error;                // 書式エラー?
        KotoParam.CutSizes->Strings[i] = InfoString.SubString(1, delimPos-1);           // 分割名取得
        InfoString = InfoString.SubString(delimPos+1, InfoString.Length() - delimPos);  // 残り情報文字列取得

        delimPos = InfoString.Pos(";");             // 分割サイズ区切り記号';'位置取得
        if (delimPos==0) ParamString = InfoString;                                      // 分割サイズ文字列取得
        else             ParamString = InfoString.SubString(1, delimPos-1);
        SizeByte = ParamString.ToInt();                                                 // 分割サイズ取得

        if (delimPos!=0){       // スキップサイズ、単位指定あり

            InfoString = InfoString.SubString(delimPos+1, InfoString.Length() - delimPos);  // 残り文字列取得
            delimPos = InfoString.Pos(";");         // スキップサイズ区切り記号';'位置取得
            if (delimPos==0) ParamString = InfoString;
            else             ParamString = InfoString.SubString(1, delimPos-1);        // スキップサイズ文字列取得
            switch (ParamString[ParamString.Length()]){                                // スキップ単位取得
                case 'M':   SkipUnit = 'M'; break;  // メガバイト
                case 'K':   SkipUnit = 'K'; break;  // キロバイト
                case 'B':   SkipUnit = 'B'; break;  // バイト
                case '%':   SkipUnit = '%'; break;  // %
            }
            if (SkipUnit!=' ') ParamString = ParamString.SubString(1, ParamString.Length()-1);
            SkipByte = ParamString.ToInt();                                            // スキップサイズ取得
        }

        cCutInfo *CutSize;                                        // 分割サイズ情報オブジェクトポインタ
        CutSize = new cCutInfo(SizeByte, SkipByte, SkipUnit);     // 分割サイズ情報オブジェクトインスタンス
        KotoParam.CutSizes->Objects[i] = (TObject*)CutSize;       // 分割サイズリストに追加
    }

    return;

error:        // エラー処理
    Application->MessageBox("初期化ファイルの分割サイズ定義に誤りがあります",          // エラーメッセージ表示(半角)
                             Application->Title.c_str(), MB_ICONEXCLAMATION | MB_OK);
    while(i<KotoParam.CutSizes->Count) KotoParam.CutSizes->Delete(i);                  // 取得済み分割サイズ情報削除
}
//---------------------------------------------------------------------------


分割サイズメニュー・リストの更新

初期化ファイルから取得した分割サイズ情報リストに基づいて分割サイズメニューとツールバー上の分割サイズリストを更新します。メニューとリストをカスタマイズに対応して変更します。

メニュー変更

「1.44M(H)」と「720K(D)」のメニューを廃止して、5つの分割サイズメニューを追加します。これらは分割サイズ情報の中の分割サイズ名を表示します。従ってメニュー設計時にはホットキーのみとします。同時に分割サイズ編集メニューも設けましょう

Caption Name 内容
「分割(D)」 DivideMNU 分割操作に関するメニュー。
  「(1)」 Size1MNU 分割サイズ1選択メニュー。
  「(2)」 Size2MNU 分割サイズ2選択メニュー。
  「(3)」 Size3MNU 分割サイズ3選択メニュー。
  「(4)」 Size4MNU 分割サイズ4選択メニュー。
  「(5)」 Size5MNU 分割サイズ5選択メニュー。
  「サイズ編集...」 EditSizeMNU 分割サイズ編集呼出し。
  「−」 SEP21 メニューを見やすくするセパレータ。

リスト変更

ツールバー上の分割サイズリスト分割サイズリストは、画面設計時に「1.44M」と「720K」のリストアイテムを設定していました。カスタマイズ対応により、リストアイテムは分割サイズ情報の中の分割サイズ名を設定しますので、画面設計時のリストアイテムは空としておきます。また、分割名は従来より長くなることが予想されますので、リストの表示幅も広げておきます。

SetSizeMenu関数

あとは分割サイズ情報に基づいて最大5つの分割名をメニューとリストに設定します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::SetSizeMenu()
{
    static TMenuItem *SizeMenus[] = { Size1MNU, Size2MNU, Size3MNU, Size4MNU, Size5MNU };    // 分割メニューコントロールリスト

    int NumSizes = KotoParam.CutSizes->Count;            // 分割サイズ情報数取得
    if (NumSizes>MaxCutMenus) NumSizes = MaxCutMenus;    // 表示最大5個まで
    SizeLST->Items->Clear();                             // ツールバーサイズリストクリア

    int i;          // 分割リストインデックス
    for (i=0;i<NumSizes;i++){          // 分割サイズメニュー作成ループ
        AnsiString SizeStr = KotoParam.CutSizes->Strings[i];             // 分割名取得
        cCutInfo *CutInfo = (cCutInfo*)KotoParam.CutSizes->Objects[i];   // 分割情報取得
        if (CutInfo->GetCutSize()==0){      // 残り全て?
            if (CutInfo->GetSkipSize()==0){    // スキップ条件なし?
                SizeStr = SizeStr + "(" + NoSkipCase + ")";  // 分割名に「(スキップ条件なし)」追加
            }
            else{                              // スキップ条件あり?
                SizeStr = SizeStr + "(残り" + AnsiString(CutInfo->GetSkipSize());  // 分割名に「(残りnn...)」追加
                switch (CutInfo->GetSkipUnit()){                                   // スキップ条件単位
                    case 'B':   SizeStr = SizeStr + "バイト以上)";    break;       // バイト(半角)
                    case 'K':   SizeStr = SizeStr + "キロバイト以上)";  break;     // キロバイト(半角)
                    case 'M':   SizeStr = SizeStr + "メガバイト以上)"; break;      // メガバイト(半角)
                    case '%':   SizeStr = SizeStr + "%以上)";       break;         // %
                }
            }
        }
        SizeMenus[i]->Caption = SizeStr + "(&" + AnsiString(i+1) + ")";    // 分割名+ホットキー設定
        SizeLST->Items->Add(SizeStr);                                      // リストアイテム追加
        SizeMenus[i]->Tag = CutInfo->GetCutSize();                         // 分割サイズ設定
        SizeMenus[i]->Visible = true;                                      // メニュー表示
    }
    for (;i<MaxCutMenus;i++){       // 分割サイズリストが5つ未満の時
        SizeMenus[i]->Caption = "";       // 分割名クリア
        SizeMenus[i]->Tag = 0;            // 分割サイズクリア
        SizeMenus[i]->Visible = false;    // メニュー非表示
    }
}
//---------------------------------------------------------------------------

改造SizeMNUClick関数

分割サイズメニューとリストが選択されたときのコールバック処理を5つの分割サイズアイテムに対応します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::SizeMNUClick(TObject *Sender)
{
    static TMenuItem *SizeMenus[] = { Size1MNU, Size2MNU, Size3MNU, Size4MNU, Size5MNU };    // 分割メニューコントロールリスト

    TMenuItem *MNU = dynamic_cast<TMenuItem*>(Sender);    // メニューコントロール取得
    if (MNU!=NULL){                  // メニューが選択された
        MNU->Checked = true;         // メニュー選択
        for (int i=0;i<MaxCutMenus;i++){        // 分割名検索
            if (SizeMenus[i]->Checked) SizeLST->ItemIndex = i;   // メニュー選択→リスト選択
        }
    }
    TComboBox *LST = dynamic_cast<TComboBox*>(Sender);    // コンボボックスコントロール取得
    if (LST!=NULL){                 // リストが選択された
        SizeMenus[LST->ItemIndex]->Checked = true;               // リスト選択→メニュー選択
    }
    ListDivideFiles(OpenDLG->FileName);    // 分割ファイルリスト更新
    UpdateMenu();    // メニュー更新
}
//---------------------------------------------------------------------------

改造ListDivideFiles関数

分割ファイルリスト表示を改造します。分割サイズ取得、分割数計算、一覧作成、結合バッチ作成等かなり変更点があります。ちょっと大変ですが、
  1. 分割サイズは分割サイズ情報リストから取得する。
  2. 分割数は空き容量全ての場合は分割実行まで決まらない。
  3. 結合バッチコマンドは空き容量全ての場合は分割実行まで決まらない。
をしっかりと確認しながらプログラミングします。
空き容量全て分割では分割を実行するまで分割サイズはわかりません。分割サイズは未定のため、リスト中のサイズ列は「---」とします。
//---------------------------------------------------------------------------
bool __fastcall TMainWND::ListDivideFiles(AnsiString FilePath)
{
    :
// 分割サイズの取得
    cCutInfo *CutInfo = GetCutInfo();           // 分割ファイル情報取得
    int CutSize = CutInfo->GetCutSize();        // 分割サイズ取得

// 分割数の計算(分割ファイル数、結合バッチファイルは除く)
    int NumCut;
    if (CutSize==0){  // 空き容量全て分割
        NumCut = 1;     // 暫定分割数
    }
    else{
        NumCut = FileSize/CutSize+1;        // 指定サイズ分割
        if (EquallyMNU->Checked){           // 均等分割の場合
            CutSize = (int)(ceil((double)FileSize/(double)NumCut));   // 分割サイズを調整
        }
    }

// 分割先ファイル一覧作成(分割ファイル名作成と分割サイズの設定)
    AnsiString FileName = ExtractFileName(FilePath);    // 分割ファイル名のみを取得(パスの削除)
    AnsiString SizeStr;         // 分割サイズ文字列バッファ
    int        SrcOffset = 0;   // 分割元オフセット
    for (int i=0;i<NumCut;i++){    // 分割数ループ
        TListItem *Item = ListVEW->Items->Add();                     // 分割先ファイルリストアイテム追加
        Item->Caption = ChangeFileExt(FileName, MakeOrderExt(i));    // 分割先ファイル名設定
        Item->ImageIndex = 0;                                        // 分割先ファイルアイコン設定
        cDivideFile *File;                                           // 分割ファイルオブジェクト
        if (FileSize>CutSize) File = new cDivideFile(CutSize);       // 分割サイズ設定
        else                  File = new cDivideFile(FileSize);
        if (CutSize>0){                     // 指定サイズ分割
            File->SetOffset(SrcOffset);                               // 分割オフセット登録
            SrcOffset += File->GetSize();                             // 分割オフセット累計
        }
        else{                               // 空き容量全て分割
            File->SetOffset(0);                                                 // 暫定分割オフセット登録
            File->SetSkipSize(CutInfo->GetSkipSize(), CutInfo->GetSkipUnit());  // スキップサイズ登録
        }
        Item->Data = File;    // リスト-分割ファイルオブジェクトリンク
        if (CutSize>0) SizeStr = Format("%3.0nKB", OPENARRAY(TVarRec, ((double)(File->GetSize())/1024.0)));
            // 分割サイズ文字列作成(KB単位)
        else           SizeStr = (SizedAtDivide);
            // 分割サイズ未決定
        Item->SubItems->Add(SizeStr);   // 分割サイズ文字列設定
        Item->SubItems->Add("未出力");  // '未出力'
        FileSize -= CutSize;            // ファイルサイズ更新
    }

// 分割先ファイルが1つの場合は結合バッチコマンドを作成しない
    if ((NumCut==1)&&(CutSize!=0)) return(true);

// 結合バッチフコマンド作成
    AnsiString ConnectBatch;        // 結合バッチコマンド
    if (CutSize>0) ConnectBatch = MakeConnectBatch(FileName, NumCut);   // 指定サイズ分割→結合バッチコマンド作成
    else           ConnectBatch = "";                                   // 空き容量全て分割→結合バッチコマンドなし
    TListItem *Item = ListVEW->Items->Add();    // 分割バッチファイルリストアイテム追加
    Item->Caption = ChangeFileExt(FileName, ".bat");    // 分割バッチファイル名設定
    Item->ImageIndex = 1;                               // 結合バッチファイルアイコン設定

// 結合バッチファイルサイズ設定
    cDivideFile *File = new cDivideFile(ConnectBatch.Length());    // 分割サイズ設定
    Item->Data = File;    // リスト-分割ファイルオブジェクトリンク

// 結合バッチファイル情報設定
    if (CutSize>0) SizeStr = Format("%3.0nB", OPENARRAY(TVarRec, ((double)File->GetSize())));
        // 結合バッチファイルサイズ文字列設定(B単位)
    else           SizeStr = SizedAtDivide;
        // 結合バッチファイルサイズ未決定
    Item->SubItems->Add(SizeStr);   // 結合バッチファイルサイズ設定
    Item->SubItems->Add("未出力");  // '未出力'

// 成功を返す
    return(true);
}
//---------------------------------------------------------------------------

GetCutInfo関数(旧名GetCutSizs関数)

分割サイズメニューの変更に伴い分割サイズの取得を変更します。さらに、単に分割サイズをバイト単位で取得していましたが、スキップサイズやスキップ単位の情報も取得する必要があります。従って、分割サイズオブジェクトを返すように変更します。同時に関数名もGetCutInfoと変更します。
//---------------------------------------------------------------------------
cCutInfo* __fastcall TMainWND::GetCutInfo()
{
    static TMenuItem *SizeMenus[] = { Size1MNU, Size2MNU, Size3MNU, Size4MNU, Size5MNU };    // 分割メニューコントロールリスト

    for (int i=0;i<MaxCutMenus;i++){    // メニュー選択ループ
        if (SizeMenus[i]->Checked){     // メニューが選択されている?
            return((cCutInfo*)KotoParam.CutSizes->Objects[i]);  // 選択メニューの分割情報を返す
        }
    }

    return(NULL);       // 全てのメニューが選択されていない
}
//---------------------------------------------------------------------------


分割サイズ編集画面の呼出し

まず、分割サイズ画面を使用できるようにメイン画面から「ファイル」「ユニットを使う」メニューで分割サイズ編集画面を指定します。
分割サイズ編集メニューのコールバック処理として、分割サイズ編集画面を呼び出します。分割サイズリストが編集されたら、メニュー・ツールバーの更新とリスト中の先頭アイテムを選択します。

EditSizeMNUClick関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::EditSizeMNUClick(TObject *Sender)
{
    if (EditDLG->Execute(KotoParam.CutSizes)){      // 分割サイズ編集呼出し
        SetSizeMenu();                    // 分割サイズメニュー更新
        SizeMNUClick(Size1MNU);           // 初期分割サイズ→先頭
    }
}
//---------------------------------------------------------------------------


分割スレッドとの情報受け渡しの変更

GUIスレッド分割スレッド間の分割情報の受け渡しは「空き容量全て」に対応すると、かなり変更があります。まずは第8話「マルチスレッド化」の各スレッドの役割分担の変更から考えましょう。
GUIスレッド 「分割スレッド君、ファイル分割をしてくれたまえ。必要な情報はこれだ。分割先に容量がなくなったりしたら声をかけてくれ。」
分割スレッド 「はいわかりました。分割サイズは空き容量全てですね」...v( .. )ブンカツブンカツ
GUIスレッド (p_q) ← ユーザー対応中
分割スレッド 「一つ分割しました。分割したサイズは○○○バイトです。」
GUIスレッド 「よしよし、分割一覧に表示したぞ。」
分割スレッド 「分割一覧を更新してください。」
GUIスレッド 「よしよし、分割一覧を更新したぞ。」
分割スレッド 「分割先容量がなくなりました。」
GUIスレッド 「そうかそうか。分割先メディアを交換したぞ。」
分割スレッド v( .. )ブンカツブンカツ
GUIスレッド (p_q) ← ユーザー対応中
分割スレッド 「結合バッチコマンドを教えてください。」
GUIスレッド 「よしよし、これが結合バッチコマンドだ。」
分割スレッド 「分割は終わりました。これが報告書です。」
GUIスレッド 「ふむふむ、問題なく終わったな。ご苦労様。」
分割スレッド 「では、失礼します。」 m(_ _)m
お互いのスレッドのやり取りを見ると、少々冗長なように思われるかもしれません。しかし、一つ一つの機能やメッセージを単純化することによって、不具合がまぎれこむことを防ぐのが私のやり方です。

分割情報の変更

GUIスレッドから分割スレッドに情報を引き渡す分割サイズクラスcDivideFileを空き容量全ての分割に対応します。既に全て分割に対応して分割情報クラスcCutInfoを作っています。分割サイズクラスは分割情報クラスの情報を含みますので、C++のクラス継承を使って分割サイズクラスに機能追加します。クラス継承を行うことによって分割サイズクラスは分割情報クラスで定義されているメンバー関数を利用することが出来ます。差分プログラミングは同じ処理を複数の箇所で行うことを防ぎ、プログラムのメンテナンスや再利用が容易になります。

改造cDivideFileクラス

//---------------------------------------------------------------------------
class cDivideFile: public cCutInfo{                 // cCutInfoを継承
  private:
    long Offset;        // 分割開始オフセット
                        // 分割サイズを削除
  public:
    cDivideFile(int _Size):cCutInfo(_Size){ Offset = 0; }   // コンストラクタ
    cDivideFile(cDivideFile *Source):cCutInfo(0){           // cCutInfo→cDivideInfoコピーコンストラクタ
        Offset      = Source->Offset;                   // 分割オフセット設定
        CutSize     = Source->CutSize;                  // 分割サイズコピー
        SkipSize    = Source->SkipSize;                 // スキップサイズコピー
        SkipUnit    = Source->SkipUnit;                 // スキップ単位コピー
    }
    void SetSize(int _Size){ SetCutSize(_Size); }           // 分割サイズ設定
    long GetSize(){ return(GetCutSize()); }                 // 分割サイズ取得
    void SetOffset(int _Offset){ Offset = _Offset; }        // 分割開始オフセット設定
    long GetOffset(){ return(Offset); }                     // 分割開始オフセット取得
};
//---------------------------------------------------------------------------

上記のGUIスレッドと分割スレッドの会話を元に、タイミングチャートを改造します。

改造タイミングチャート


タイミングチャートからも空き明らかなように、3つのメッセージを追加します。


分割サイズ更新通知

メイン画面のヘッダファイルにメッセージIDを追加します。
//---------------------------------------------------------------------------
    :
#define FDM_UPDATESIZE (WM_USER+4)                  // 分割サイズ更新
    // WParam (int) 分割ファイルインデックス番号(0〜分割数+1)
    // WParam (int) 分割サイズ(バイト)
    // Result (void)
//---------------------------------------------------------------------------

同じくヘッダフィルにメッセージイベントハンドラを追加します。
//---------------------------------------------------------------------------
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(FDM_REPORTDIVIDING, TMessage, OnReportDividing)
MESSAGE_HANDLER(FDM_NOWRITESPACE, TMessage, OnNoWriteSpace)
MESSAGE_HANDLER(FDM_MOVETOCORNER, TMessage, OnMoveToCorner)
MESSAGE_HANDLER(FDM_UPDATESIZE, TMessage, OnUpdateSize)
MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, OnDropFiles)
END_MESSAGE_MAP(TForm)
//---------------------------------------------------------------------------

イベントに対するコールバック関数を追加します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::OnUpdateSize(TMessage &Message)
{
    int Index = (int)Message.WParam;         // 分割ファイルインデックス番号取得
    int Size  = (double)Message.LParam;      // 分割サイズ(バイト)取得
    int Offset;                              // 分割開始オフセット番号
    if (SelectedFile==NULL) Offset = 0;                             // 全分割の場合は先頭から
    else                    Offset = SelectedFile->Index;           // リカバー分割の場合は選択ファイルから
    ListVEW->Selected = ListVEW->Items->Item[Offset+Index];         // 分割中ファイル選択
    AnsiString SizeStr;                                             // 分割サイズ文字列
    if (ExtractFileExt(ListVEW->Selected->Caption)!=".bat"){        // 分割ファイル?
        SizeStr = FormatD2T((double)Size/1024.0)+"KB";              // KB単位サイズ文字列変換
        if (SizeStr=="0") SizeStr = "1";                            // 1KB以下は1KB表示
    }
    else{                                                           // 結合バッチファイル
        SizeStr = FormatD2T((double)Size)+"B";                      // B単位サイズ文字列変換
    }
    cDivideFile *File = (cDivideFile*)(ListVEW->Selected->Data);    // 分割ファイルオブジェクト取得
    File->SetSize(Size);                                            // 分割サイズ設定
    ListVEW->Selected->SubItems->Strings[0] = SizeStr;              // 分割サイズ表示
}
//---------------------------------------------------------------------------


分割リスト更新通知

メイン画面のヘッダファイルにメッセージIDを追加します。
//---------------------------------------------------------------------------
    :
#define FDM_UPDATELIST (WM_USER+5)                  // 分割リスト更新
    // WParam (int) 分割ファイルインデックス番号(0〜分割数+1)
    // WParam (cDivideFile*) 分割ファイル情報
    // Result (void)
//---------------------------------------------------------------------------

同じくヘッダフィルにメッセージイベントハンドラを追加します。
//---------------------------------------------------------------------------
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(FDM_REPORTDIVIDING, TMessage, OnReportDividing)
MESSAGE_HANDLER(FDM_NOWRITESPACE, TMessage, OnNoWriteSpace)
MESSAGE_HANDLER(FDM_MOVETOCORNER, TMessage, OnMoveToCorner)
MESSAGE_HANDLER(FDM_UPDATESIZE, TMessage, OnUpdateSize)
MESSAGE_HANDLER(FDM_UPDATELIST, TMessage, OnUpdateList)
MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, OnDropFiles)
END_MESSAGE_MAP(TForm)
//---------------------------------------------------------------------------

イベントに対するコールバック関数を追加します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::OnUpdateList(TMessage &Message)
{
    int Index = (int)Message.WParam;                        // 追加位置(前)
    cDivideFile *AddFile = (cDivideFile*)Message.LParam;    // 追加ファイル情報
    int Offset;                                             // 分割開始オフセット番号
    if (SelectedFile==NULL) Offset = Index;                 // 全分割の場合は先頭から
    else                    Offset = SelectedFile->Index + Index;   // リカバー分割の場合は選択ファイルから

    TListItem *Item;        // 追加アイテム
    if (Offset==(ListVEW->Items->Count-1)) Item = ListVEW->Items->Add();            // 末尾に追加
    else                                   Item = ListVEW->Items->Insert(Offset+1); // 途中に挿入
    AnsiString FileName = ExtractFileName(OpenDLG->FileName);           // 分割ファイル名のみを取得(パスの削除)
    Item->Caption = ChangeFileExt(FileName, MakeOrderExt(Offset+1));    // 分割先ファイル名設定
    Item->ImageIndex = 0;                                               // 分割先ファイルアイコン設定
    cDivideFile *File = new cDivideFile(AddFile);                       // 分割ファイルオブジェクト
    Item->Data = File;                      // リスト-分割ファイルオブジェクトリンク
    Item->SubItems->Add(SizedAtDivide);     // 分割サイズ文字列設定
    Item->SubItems->Add("未出力");          // '未出力'
}
//---------------------------------------------------------------------------


結合バッチ作成要求

メイン画面のヘッダファイルにメッセージIDを追加します。
//---------------------------------------------------------------------------
    :
#define FDM_MAKEBATCH (WM_USER+6)                   // 結合バッチ作成要求
    // WParam (char*) 結合バッチ文字列作成バッファ
    // WParam (int)   文字列作成バッファ長(0=長さのみを返す)
    // Result (int)   結合バッチ長さ
//---------------------------------------------------------------------------

同じくヘッダフィルにメッセージイベントハンドラを追加します。
//---------------------------------------------------------------------------
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(FDM_REPORTDIVIDING, TMessage, OnReportDividing)
MESSAGE_HANDLER(FDM_NOWRITESPACE, TMessage, OnNoWriteSpace)
MESSAGE_HANDLER(FDM_MOVETOCORNER, TMessage, OnMoveToCorner)
MESSAGE_HANDLER(FDM_UPDATESIZE, TMessage, OnUpdateSize)
MESSAGE_HANDLER(FDM_MAKEBATCH, TMessage, OnMakeConnectBatch)
MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, OnDropFiles)
END_MESSAGE_MAP(TForm)
//---------------------------------------------------------------------------

イベントに対するコールバック関数を追加します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::OnMakeConnectBatch(TMessage &Message)
{
    char *Buffer = (char*)Message.WParam;                           // 結合バッチ作成バッファ取得
    int  Length  = (int)Message.LParam;                             // 結合バッチバッファ長
    AnsiString FileName = ExtractFileName(OpenDLG->FileName);       // 分割ファイル名のみを取得(パスの削除)
    int NumCut = ListVEW->Items->Count-1;                           // 分割数取得(結合バッチファイルを除く)
    AnsiString ConnectBatch = MakeConnectBatch(FileName, NumCut);   // 結合バッチコマンド作成
    Message.Result = ConnectBatch.Length() + 1;                     // 結合バッチバッファ長を返す
    if (Length>0) strcpy(Buffer, ConnectBatch.c_str());             // 結合バッチコピー
}
//---------------------------------------------------------------------------

個々のイベントは独立性が高いので、プログラミングは容易です。

分割実行

分割実行に対しては、空き容量全て分割時には結合バッチコマンドを作らないことと、分割スレッドからGUIスレッドへの上記3つのメッセージの追加を行います。

改造GoDivideMNUClick関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::GoDivideMNUClick(TObject *Sender)
{
    :
// 分割スレッド生成
    AnsiString DstFilePath = StatusBAR->SimpleText;    // 分割先パス作成
    if (DstFilePath[DstFilePath.Length()]!='\\'){      // パス区切り文字追加
        DstFilePath = DstFilePath + "\\";
    }
    AnsiString ConnectBatch;                           // 結合バッチコマンド
    if (ListVEW->Items->Count>2){                      // 分割ファイルが3つ以上?
        ConnectBatch = MakeConnectBatch(OpenDLG->FileName.c_str(), ListVEW->Items->Count-1);    // 結合バッチコマンド作成
    }
    else{                                              // 分割ファイルが2つ以下?
        ConnectBatch = "";                             // 結合バッチコマンドは後で作成
    }
    unsigned int Messages[5];               // 5つのユーザー定義メッセージを登録
    Messages[0] = FDM_REPORTDIVIDING;       // 分割進捗状況通知
    Messages[1] = FDM_NOWRITESPACE;         // 分割先容量不足通知
    Messages[2] = FDM_UPDATESIZE;           // 分割サイズ更新通知
    Messages[3] = FDM_UPDATELIST;           // 分割リスト更新通知
    Messages[4] = FDM_MAKEBATCH;            // 結合バッチ作成要求
    DivideTRD = new TDivideThread(this,     // スレッドオブジェクト生成
                                  Messages,
                                  OpenDLG->FileName,
                                  DstFilePath,
                                  DivideFiles,
                                  ConnectBatch);
    DivideTRD->OnTerminate = OnDivideTRDTerminate;    // 分割スレッド終了処理登録

// 分割実行中画面設定
    :
}
//---------------------------------------------------------------------------


メニュー・ツールバー更新の変更

指定サイズ分割と空き容量分割では使用できるメニューやツールバーに違いがあります。

分割サイズ 指定サイズ 空き容量全て
分割実行 分割ファイル3つ以上(結合バッチを含む) 分割ファイル2つ以上(結合バッチを含む)、かつリムーバブルメディア
分割先変更 分割ファイル3つ以上(結合バッチを含む)
リカバリー分割 分割実行後に分割サイズが決まっていれば可
均等分割 不可

以上の条件を満たすようにメニュー・ツールバーを更新するUpdateMenu関数を改造します。

改造UpdateMenu関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::UpdateMenu()
{
// 分割実行・分割先変更設定
    cCutInfo *CutInfo = GetCutInfo();       // 分割情報取得
    int CutSize = CutInfo->GetCutSize();    // 分割サイズ取得
    bool Dividable = false;                 // 分割実行可不可
    bool Changable = false;                 // 分割先変更可不可
    if (CutSize==0){                        // 空き容量全て分割?
        if (ListVEW->Items->Count>=2){          // 分割ファイルが2つ以上?
            Changable = true;                       // 分割先変更可
            AnsiString DriveName = ExtractFileDrive(StatusBAR->SimpleText) + "\\";   // ドライブ名取得
            bool Removable = (GetDriveType(DriveName.c_str())==DRIVE_REMOVABLE);     // ドライブタイプ取得
            if (Removable) Dividable = true;        // リムーバブルメディア→分割実行可
        }
    }
    else{                                   // 指定サイズ分割
        Dividable = (ListVEW->Items->Count>2);  // 分割実行は分割ファイルが3つ以上
        Changable = true;                       // 分割先変更可
    }
    PathMNU->Enabled     = Changable;           // 分割先メニューは分割先ファイル数が2つ以上で有効
    GoDivideMNU->Enabled = Dividable;           // 分割実行メニューは分割先ファイル数が2つ以上で有効
    GoDivideBTN->Enabled = Dividable;           // 分割実行ボタンは分割先ファイル数が2つ以上で有効

// リカバリー分割メニュー設定
    SelectedDivideMNU->Enabled = false;         // 残り分割メニューを暫定的に不可
    if (ListVEW->Selected!=NULL){               // 分割ファイルが選択されている?
        if (ListVEW->Selected->SubItems->Strings[0]!=SizedAtDivide){  // 分割サイズ決定?
            SelectedDivideMNU->Enabled = true;                        // 残り分割メニュー可
        }
    }
    SelectedDividePOP->Visible = SelectedDivideMNU->Enabled;  // ファイルが選択されたら選択ファイル分割ポップアップ表示
    FollowDivideMNU->Enabled = SelectedDivideMNU->Enabled;    // ファイルが選択されたら以降ファイル分割メニュー有効
    FollowDividePOP->Visible = FollowDivideMNU->Enabled;      // ファイルが選択されたら以降ファイル分割ポップアップ表示

// 均等分割メニュー設定
    EquallyMNU->Enabled = (CutSize!=0);             // 均等分割メニューは指定サイズ分割で有効
    if (CutSize==0) EquallyMNU->Checked = false;    // 空き容量全て分割の場合は均等分割不可
    EquallyBTN->Enabled = EquallyMNU->Enabled;      // 均等分割ボタン有効・無効設定
    EquallyBTN->Down = EquallyMNU->Checked;         // 均等分割ボタンオン・オフ設定

    StatusBAR->ShowHint = PathMNU->Enabled;         // 分割先変更ツールチップヒント表示切替え
}
//---------------------------------------------------------------------------

もう一つ変更点があります。空き容量全て分割はリムーバブルドライブにのみ有効です。従って、分割先を変更したときにもメニュー・ツールバーの状態を更新する必要があります。分割先変更コールバックからUpdateMenu関数を呼び出します。

改造PathMNUClick関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::PathMNUClick(TObject *Sender)
{
    if (!PathMNU->Enabled) return;               // 分割先変更メニュー無効の場合は処理中断
    AnsiString Path = StatusBAR->SimpleText;    // 分割先パスを作業文字列に複写
    if (SelectFolder(Handle, Path, "分割先を選択してください")){    // フォルダ選択実行
        StatusBAR->SimpleText = Path;    // 選択されたら分割パスを更新
    }
    UpdateMenu();    // メニュー更新;
}
//---------------------------------------------------------------------------


分割サイズ情報を初期化ファイルに更新

寿分割の終了時には、分割サイズ情報を初期化ファイルに出力します。また、設定パラメータ内の分割サイズリストを破棄します。

改造FormCloseQuery関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::FormCloseQuery(TObject *Sender, bool &CanClose)
{
    :
    WriteIniFile(IniFile);      // 設定状態保存
    delete IniFile;             // 初期化ファイル終了
    ClearCutSizes(KotoParam.CutSizes);  // 分割サイズリストクリア
    delete KotoParam.CutSizes;          // 分割サイズリスト破棄
}
//---------------------------------------------------------------------------

初期化ファイルへの分割サイズ情報出力は、初期化ファイルへの分割サイズ情報の記録形式に則ります。

改造WriteIniFile関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::WriteIniFile(TIniFile *IniFile)
{
    static TMenuItem *SizeMenus[] = { Size1MNU, Size2MNU, Size3MNU, Size4MNU, Size5MNU };    // 分割メニューコントロールリスト

    :

    IniFile->WriteString(SizeSection, "0", "");     // セクション消去のためのダミー書きこみ
    IniFile->EraseSection(SizeSection);             // 分割情報セクション消去
    for (int i=0;i<KotoParam.CutSizes->Count;i++){  // 分割情報ループ
        AnsiString Num(i);      // キー文字列(0,1,2,...)
        cCutInfo *CutInfo = (cCutInfo*)KotoParam.CutSizes->Objects[i];     // 分割情報取得
        AnsiString SizeString = KotoParam.CutSizes->Strings[i] + ";"       // 分割情報文字列作成
                              + AnsiString(CutInfo->GetCutSize()) + ";"
                              + AnsiString(CutInfo->GetSkipSize())
                              + AnsiString(CutInfo->GetSkipUnit());
        IniFile->WriteString(SizeSection, Num, SizeString);                // 分割情報書きこみ
    }
}
//---------------------------------------------------------------------------


定数定義

変更の可能性がある定数はヘッダファイルmain.hに定義します。
//---------------------------------------------------------------------------
// システム定数
#define MaxCutMenus 5
#define NameOf1_44M "1.44M"         // 自動設定分割名「1.44M」
#define NameOf720K  "720K"          // 自動設定分割名「720K」
#define SizeOf1_44M 1457664         // 自動設定分割サイズ1.44M
#define SizeOf720K  728832          // 自動設定分割サイズ720K
#define SizedAtDivide "---"         // 分割サイズ未定の場合の表示
//---------------------------------------------------------------------------


メイン画面の作成は以上で終わりです。分割サイズ編集画面同様データ入力、チェック、編集、出力等結構面倒です。

次回は分割スレッドに対する分割サイズのカスタマイズ化を行います。寿分割の心臓部に対する再々の変更で、ややこしいところです。よろしくお付き合いください。

今回のプログラミング行数: 315行
今回までのプログラミング行数: 1,770行


目次に戻る目次に戻る トップに戻るトップに戻る