日曜日にBCB!



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


第13話「分割リカバー機能」

読み取りエラーFD寿分割で大量のデータをフロッピーディスケットに出力していると、まれに転送先で読み取りエラーが発生するディスケットがでてきました(決して寿分割のバグではなく、フロッピーディスケットが痛んでいたからです)。そんな時は一旦データをハードディスクに再度分割し、エラーが発生した分割ファイルだけをフロッピーディスケット経由で移動します。分割を中断した場合は、中断した以降のデータも移動します。ここまでは移動したから、あとこれと、これ以降を再度移動して... 結構手間がかかりますし、ハードディスクには移動の必要のないファイルも残って、それを消し忘れてしまって... と移動処理が面倒になってきてしまいます。
そんな時欲しくなったのが「分割リカバー機能」です。分割一覧の中の指定した一つのファイルや、指定したファイル以降だけを分割実行できれば、万が一フロッピーディスケットが読み取りエラーを発生しても、上記のような手間が省けます。幸いにも寿分割は分割ファイル一覧を表示していますので、分割実行のファイルを確認したり指定するのは簡単です。ただ、分割を実行したかどうかは現在の状態ではわからないので、一覧の中に「未出力」や「出力済み」のステータス表示も付け加えたいと思います。


分割情報の変更

まず、分割ファイルの取り扱いの変更について考えます。寿分割はマルチスレッドでファイル分割を行っています。GUIスレッドから分割スレッドに渡す分割情報の中の分割ファイルリストをどうするかを考えてみます。2つの方法が考えられます。
  1. 現状の分割ファイルリストはそのまま。別途分割するファイルインデックスリストを作成する。
  2. 分割ファイルリストは分割リカバー機能に対応し、実際に分割を実行するファイル情報のみを保持する。
分割ファイルリスト1の方が処理が簡単のような感じがします。しかし、分割スレッドから見れば、分割する必要もないファイルの情報までが渡されるのは不自然です。私は処理方法の選択の必要が生じた際、「どちらが自然か」を基準に考えます。たとえ自然に思える方法が他方より困難と思われても、あえて困難な方法を選択します。なぜなら、今は困難かもしれませんが、今後の機能拡張にも十分に耐えられる柔軟かつ強靭なプログラムに仕上げたいからです。私はこの手法を「自然設計」とか「ナチュラルプログラミング」と呼んでいます。オブジェクト指向設計で言う「データの抽象化」に近い概念だと思っています。
プログラムを変更する際、「確固たる『データの流れ』を作っておけば、プログラムなんてどうにでもなるものだ」と確信しています。データの流れはそんなに変わるものではありません。プログラムの都合でデータの流れを変更してしまうと、機能追加のたびにどんどんデータの流れが複雑になってしまい、結果的には全く改造できない硬直化したプログラムとなってしまいます。第4話で「データの流れ道」のお話しました。データの責任を明らかにすると、一見複雑そうなプログラムも読めてきます。プログラムを読む作業と言うのは、実はデータの流れを読む作業だからです。

ちょっと話が脱線しましたが、「分割ファイルリストは分割リカバー機能に対応し、実際に分割を実行するファイル情報のみを保持する」として分割情報の変更を考えます。
もうひとつ、分割スレッドに渡す分割情報には必要ありませんが、GUIスレッドの選択ファイル情報の取り扱いも重要です。これまでは分割一覧中の選択ファイルには意味がありませんでした。しかし、分割リカバリーでは意味を持ちます。分割実行中にはどのファイルを分割していたか選択ファイルで表示していました。分割が終了すると無条件に選択ファイル表示をなくしていましたが、リカバリー機能では分割実行前に選択指定ファイルをそのまま表示する必要もあります。


分割開始位置

分割開始位置は分割オブジェクトに記録するのが一番自然です。分割オブジェクトは第5話で「クラス作成の練習を兼ねています」と書きました。しかし、リカバー機能ではこの分割オブジェクトが生きてきました。決して第5話の段階でリカバー機能を考えていたわけではありません。しかし、オブジェクト指向とは後々でその威力を発揮するものです。
分割位置は分割一覧を作るListDivideFiles関数内で作ります。単に分割するファイルのサイズを累計するだけですから、処理はいたって簡単です。一見、ListDivideFiles関数内で分割位置を作成するのは無駄なように思えます。全てのファイルを分割する際には、分割開始位置は不要です。しかし、分割スレッドから見れば、分割ファイルの開始位置とサイズがわかっていれば、分割ファイルリストの並びがどうであろうと問題なくファイル分割を行えます。これまでは「分割位置は分割元ファイルの先頭から、分割サイズ順に並んでいる(結合バッチファイルを除く)」と言う前提条件がありました。しかし、リカバー機能を実装するとその前提条件は適用できません。分割スレッドGUIスレッドからの独立性をさらに高めるために、個々の分割ファイルの情報(分割オブジェクト)を保持する方が有利です。もし、分割開始位置を求めるのに時間がかかり、ユーザーレスポンスが悪くなるのなら別の方法を考えますが、処理自身も非常に単純で高速です。では、分割オブジェクトのクラス定義の分割ファイルクラスの変更を行います。

改造cDivideFileクラス

//---------------------------------------------------------------------------
class cDivideFile{
  private:
    long Offset;  // 分割開始オフセット
    long Size;    // 分割サイズ
  public:
    cDivideFile(int _Size){ Size = _Size; Offset = 0; }  // コンストラクタ
    void SetSize(int _Size){ Size = _Size; }             // 分割サイズ設定
    long GetSize(){ return(Size); }                      // 分割サイズ取得
    void SetOffset(int _Offset){ Offset = _Offset; }     // 分割開始オフセット設定
    long GetOffset(){ return(Offset); }                  // 分割開始オフセット取得
};
//---------------------------------------------------------------------------
分割開始位置はListDivideFiles関数で行います。

改造ListDivideFiles関数

//---------------------------------------------------------------------------
     :

// 分割先ファイル一覧作成(分割ファイル名作成と分割サイズの設定)
    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);
        File->SetOffset(SrcOffset);                               // 分割オフセット登録
        SrcOffset += File->GetSize();                             // 分割オフセット累計
        Item->Data = File;    // リスト-分割ファイルオブジェクトリンク
        SizeStr = Format("%3.0nKB", OPENARRAY(TVarRec, ((double)(File->GetSize())/1024.0)));
            // 分割サイズ文字列作成(KB単位)
        Item->SubItems->Add(SizeStr);   // 分割サイズ文字列設定
        FileSize -= CutSize;            // ファイルサイズ更新

// 分割先ファイルが1つの場合は結合バッチコマンドを作成しない
    :

}
//---------------------------------------------------------------------------


結合バッチファイル

結合バッチコマンドは従来どおりです。結合バッチコマンドをGUIスレッドから分割スレッドに渡す手順も従来どおりとします。結合バッチコマンドを結合バッチファイルに出力するかどうかは分割スレッドが判断します。

これまでの分割スレッドは結合バッチコマンドを分割ファイルの最後にファイル出力するという前提条件がありました。ところが、リカバー機能の追加によってこの前提条件は適用できません。そこで、 とします。分割スレッド分割実行関数に改造を加えますが、その前にこの関数の無駄を2つ取り除きたいと思います。

ひとつは分割情報内の分割サイズです。これまでは分割ファイルサイズを登録していましたが、これは分割実行関数の分割バッファサイズはGUIスレッドで分割ファイルリストをスキャンすれば求まります。そこで分割情報から分割サイズを削除し、GUIスレッド自身が分割サイズを求めるように変更します。TDivideThreadクラスとコンストラクタから分割サイズCutSizeの定義や設定を削除します。

改造TDivideThreadコンストラクタ

//---------------------------------------------------------------------------
__fastcall TDivideThread::TDivideThread(TForm *_OwnerForm,          // 呼出しフォーム
                                        unsigned int _Messages[],   // 通知メッセージID
                                        AnsiString _SourceFile,     // 分割元ファイル
                                        AnsiString _DstPath,        // 分割先パス
                                        TStringList *_DstFiles,     // 分割先ファイルリスト
                                                                    (分割サイズを削除)
                                        AnsiString _ConnectBatch)   // 結合バッチコマンド
    : TThread(true)
{
    FreeOnTerminate = true;             // スレッド終了時にスレッドオブジェクトを自動的に破棄
    OwnerForm       = _OwnerForm;       // 呼出しフォーム設定
    ReportMessage   = _Messages[0];     // 分割進捗状況通知メッセージ設定
    NoSpaceMessage  = _Messages[1];     // 分割先容量不足メッセージ設定
    SourceFile      = _SourceFile;      // 分割元ファイル設定
    DstPath         = _DstPath;         // 分割先パス設定
    DstFiles        = _DstFiles;        // 分割先ファイルリスト(名前+分割オブジェクト)のコピー設定
                                        (分割サイズ設定を削除)
    ConnectBatch    = _ConnectBatch;    // 結合バッチコマンド設定
}
//---------------------------------------------------------------------------
もうひとつは分割バッファサイズです。分割バッファは分割サイズ分の領域を確保していましたが、第8話の「マルチスレッド化」で進捗状況を表示するために分割ファイルをさらに16分割して出力するように変更しました。すると分割バッファの15/16は利用されない無駄な領域となります。今の寿分割ではせいぜい1.4M程度しか分割バッファを準備しません。今後の機能追加ではMO(光磁気ディスク)等への分割も行いたいと思います。すると、現状のアルゴリズムでは最大で1.3Gの分割バッファを準備する必要が出てきます。これでは分割が不可能になります。16分割したとしても80M強ですからOSにかなりの負荷をかけます。分割数の計算アルゴリズムを再検討する必要はあるのですが、どちらにしても15/16の領域の無駄遣いは避けたいと思います。なお、改造プログラムでは分割数の16をプログラムから、分割スレッドクラスヘッダファイルdividtrd.hでの定義に変更しています。
#define dfMaxNumDiv  16          // 1ファイル最大分割数

改造Execute関数

//---------------------------------------------------------------------------
void __fastcall TDivideThread::Execute()
{
// 分割元ファイル読込みオープン
    :

// 分割バッファ準備
    int CutBufferSize = 0;              // 最大分割バッファサイズ取得
    for (int i=0;i<DstFiles->Count;i++){                             // 全分割ファイル+バッチファイルをスキャン
        cDivideFile *File = (cDivideFile*)DstFiles->Objects[i];      // 分割ファイルオブジェクト取得
        if (File->GetSize()>CutBufferSize) CutBufferSize = File->GetSize();    // 最大分割バッファサイズ更新
    }
    char *buffer = new char [CutBufferSize/dfMaxNumDiv];             // 分割バッファメモリ確保
    if (Terminated){        // ユーザー中断?
        DivideStatus = dsUserBreak;     // ユーザー中断
        fclose(SrcFP);                  // 分割先ファイルクローズ
        return;                         // 分割中断
    }

// 分割数繰り返し
    for (int i=0;i<DstFiles->Count;i++){
        DivideIndex = i;               // 分割中ファイルインデックス
        int ProgressSize = 0;          // 書き込み進捗サイズクリア

    // 分割先ファイル情報取得
        :

    // 出力ファイルタイプ取得
        if (ExtractFileExt(DstFiles->Strings[i])!=".bat"){    // 分割ファイル?

        // 分割開始オフセット移動
            if (fseek(SrcFP, File->GetOffset(), SEEK_SET)!=0){
                DivideStatus = dsSrcError;      // 分割元ファイルエラー
                delete buffer;                  // 分割バッファ開放
                fclose(SrcFP);                  // 分割先ファイルクローズ
                return;                         // 分割中断
            }

        // 分割先ファイル作成
            int BlockSize = ACutSize/dfMaxNumDiv;                           // 書き込みブロックサイズ取得
            AnsiString DstFilePath = DstPath + DstFiles->Strings[i];        // 分割先ファイル名作成
                :
            }

        // ブロック書きこみループ
            :

        }
        else{               // バッチファイル?

            AnsiString DstFilePath = DstPath + DstFiles->Strings[i];  // 結合バッチファイル名作成
            FILE *DstFP = fopen(DstFilePath.c_str(), "wb");       // 結合バッチファイル作成オープン
                 :
        }
    }

// 分割バッファ開放
    delete buffer;

// 分割元ファイルクローズ
    fclose(SrcFP);
}
//---------------------------------------------------------------------------


分割メニューの追加

分割スレッドはリカバー機能に対応しました。続いてGUIスレッドの改造を行います。まずは、メニューの追加から行いましょう。

Caption Name 内容
「分割(D)」 DivideMNU 分割操作に関するメニュー。
  :  
  「分割実行(G)」 GoDivideMNU 分割を実行。
  「選択したファイルを分割(S)」 SelectedDivideMNU 選択した一ファイルの分割を実行。
  「選択したファイル以降を分割(F)」 FollowDivideMNU 選択したファイル以降の分割を実行。

ポップアップメニュー設計 選択した一ファイルや以降のファイル分割のメニューは、分割一覧からファイルを選択した場合のみ有効です。分割実行のようにツールバーにボタンを置くのではなく、分割ファイルを選択した時のみ有効ですからポップアップメニューから実行します。ポップアップメニューの追加を行います。まずポップアップメユーコントロールを配置します。コントロール名はPopupMNUとしましょう。分割ファイル一覧のリストビューListVEWのポップアップメニューにPopupMNUを指定します。 つづいてポップアップメニューのメニュー設計を行います。メニューデザイナーから、

Caption Name 内容
「選択したファイルを分割」 SelectedDividePOP 選択した一ファイルの分割を実行。
「選択したファイル以降を分割」 FollowDividePOP 選択したファイル以降の分割を実行。

とします。各ポップアップメニューはメインメニューと比較してCaptionにコントロール名の末尾がMNUからPOPに変わっているだけです。コールバック関数も共通化します。
追加した4つのメニュー(メインメニュー2つ+ポップアップメニュー2つ)が有効かどうかは定石のUpdateMenu関数で行います。メインメニューは分割ファイルが選択されているときは有効、選択されていなければ無効となります。ポップアップメニューは有効/無効ではなく、表示/非表示にするのが標準Look&Feelです。

改造UpdateMenu関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::UpdateMenu()
{
    PathMNU->Enabled = (ListVEW->Items->Count>2);     // 分割先メニューは分割先ファイル数が2つ以上で有効
    GoDivideMNU->Enabled = (ListVEW->Items->Count>2); // 分割実行メニューは分割先ファイル数が2つ以上で有効
    GoDivideBTN->Enabled = GoDivideMNU->Enabled;

    SelectedDivideMNU->Enabled = (ListVEW->Selected!=NULL);   // ファイルが選択されたら選択ファイル分割メニュー有効
    SelectedDividePOP->Visible = SelectedDivideMNU->Enabled;  // ファイルが選択されたら選択ファイル分割ポップアップ表示
    FollowDivideMNU->Enabled = (ListVEW->Selected!=NULL);     // ファイルが選択されたら以降ファイル分割メニュー有効
    FollowDividePOP->Visible = FollowDivideMNU->Enabled;      // ファイルが選択されたら以降ファイル分割ポップアップ表示

    StatusBAR->ShowHint = PathMNU->Enabled;           // 分割先変更ツールチップヒント表示切替え
}
//---------------------------------------------------------------------------
マウスとキーボードによるリストビューの選択時にメニューを更新します。また、リストビューをマウスで右クリックしてポップアップメニューを選択する際、自動的に分割ファイルが選択されます。しかし、Windowsの仕様でポップアップメニューが表示されてからリストビュー(分割ファイル)が選択されますので、ポップアップメニューが表示される直前にもメニュー更新を行う必要があります。分割ファイルリストのマウス選択コールバックListVEWClickとキーボード選択コールバックListVEWKeyUp、ポップアップメニュー表示コールバックPopupMNUPopupからUpdateMenu関数を呼び出します。
//---------------------------------------------------------------------------
void __fastcall TMainWND::ListVEWClick(TObject *Sender)
{
    UpdateMenu();
}
//---------------------------------------------------------------------------
void __fastcall TMainWND::ListVEWKeyUp(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
    UpdateMenu();
}
//---------------------------------------------------------------------------
void __fastcall TMainWND::PopupMNUPopup(TObject *Sender)
{
    UpdateMenu();
}
//---------------------------------------------------------------------------


3つの分割実行メニュー

3つの分割実行

2つのリカバー分割の実行処理を作成します。これまでの分割実行を含めて3通りの分割方法をそれぞれ個別のコールバック関数にするのか、それとも共通コールバック関数にするのかは正直迷いました。これまでの分割実行と異なるのは分割情報中の分割フィルリストの取り扱いだけです。他の処理は共通です。同じ処理を複数の場所で行うと、これからのプログラム変更でミスを生じやすくなります。結果的には共通コールバック関数とし、GoDivideMNUClick関数を改造することにしました。以前ツールバーとステータスバーの表示コールバック関数は似た処理でも別にしました。メニューとツールバーの表示コールバック関数は同じにしました。「プログラムを短くするために共通化する」ではプログラムは複雑になってしまいます。逆に「共通化はしない」では同じ処理を複数の場所で行ってしまいます。「どこを共通化し、どこを別にするか」ではなく、「処理する対象は何か」を中心に設計していくとオブジェクト指向設計となり、プログラムは簡単に、かつ柔軟性に富むと考えています。

もうひとつ、分割実行前に選択されていた分割ファイルを、分割終了時にも選択するために、メイン画面クラスのプロテクトメンバに選択中ファイルをリストアイテム(TListItem*)としてを記憶します。このアイテムは分割終了時だけでなく、分割中の進捗状況表示にも使用します。

改造TMainWNDクラス

//---------------------------------------------------------------------------
class TMainWND : public TForm
{
        :
private:    // ユーザー宣言
    stKotoParam   KotoParam;        // 寿分割パラメータ
    TDivideThread *DivideTRD;       // 分割スレッドオブジェクト
    TStringList   *DivideFiles;     // コピー分割ファイルリスト
    TListItem     *SelectedFile;    // 選択ファイル
        :
};
//---------------------------------------------------------------------------
では共通化した3通りの分割実行のコールバック関数をリカバー機能に対応させます。メニュー3つ、ツールボタン1つ、ポップアップメニュー2つから呼ばれるのでdynamic_cast処理を使って、どのメニュー、ツールボタンが選択されたかを判別する処理も必要です。

改造GoDivideMNUClick関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::GoDivideMNUClick(TObject *Sender)
{
// 分割リスト作成
    DivideFiles = new TStringList;      // 分割ファイルコピーリスト作成

    TMenuItem *MNU   = dynamic_cast<TMenuItem*>(Sender);      // メニューコントロール取得
    TToolButton *BTN = dynamic_cast<TToolButton*>(Sender);    // ボタンコントロール取得

    if ((MNU==GoDivideMNU) || (BTN==GoDivideBTN)){      // 全て分割(分割実行)
        SelectedFile = NULL;                            // 分割後選択ファイルなし
        for (int i=0;i<ListVEW->Items->Count;i++){      // 全ての分割ファイルの
            TListItem *Item = ListVEW->Items->Item[i];  // 分割ファイルオブジェクト取得
            DivideFiles->AddObject(Item->Caption, (TObject*)Item->Data);    // 名前と分割ファイルオブジェクトをコピー
        }
    }

    if ((MNU==SelectedDivideMNU) || (MNU==SelectedDividePOP)){  // 選択したファイルを分割
        SelectedFile = ListVEW->Selected;               // 選択分割ファイルオブジェクト取得
        DivideFiles->AddObject(SelectedFile->Caption, (TObject*)SelectedFile->Data);
                                                        // 名前と分割ファイルオブジェクトをコピー
    }

    if ((MNU==FollowDivideMNU) || (MNU==FollowDividePOP)){        // 選択したファイル以降を分割
        SelectedFile = ListVEW->Selected;                // 選択分割ファイルオブジェクト取得
        for (int i=SelectedFile->Index;i<ListVEW->Items->Count;i++){      // 全ての分割ファイルの
            TListItem *Item = ListVEW->Items->Item[i];   // 分割ファイルオブジェクト取得
            DivideFiles->AddObject(Item->Caption, (TObject*)Item->Data);    // 名前と分割ファイルオブジェクトをコピー
        }
    }

// 分割実行チェック
        :

// 分割スレッド生成
        :
    DivideTRD = new TDivideThread(this,     // スレッドオブジェクト生成
                                  Messages,
                                  OpenDLG->FileName,
                                  DstFilePath,
                                  DivideFiles,
                                  MakeConnectBatch(OpenDLG->FileName.c_str(), ListVEW->Items->Count-1));
    DivideTRD->OnTerminate = OnDivideTRDTerminate;    // 分割スレッド終了処理登録

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

// 分割スレッド実行
        :
}
//---------------------------------------------------------------------------


ステータス表示

以上でリカバー機能を追加しました。さらに分割ファイル一覧に「未分割」「分割中」「分割済」の出力ステータスを付け加えます(「分割済み」の'み'はステータスを3文字にそろえるために省略しました)。
「状態」カラム追加 まず、画面の修正から行います。分割ファイル一覧を表示するリストビューにもう一つサブアイテムを追加します。ListVEWColumnsプロパティーからカラムデザイナーを呼び出し、3番目のカラムを追加します。幅のバランスを調整しましょう。
Caption Width
名前 200
サイズ 70
状態 70

分割ファイル一覧を作成する際に、ステータスに「未出力」を設定します。

改造ListDivideFiles関数

//---------------------------------------------------------------------------
bool __fastcall TMainWND::ListDivideFiles(AnsiString FilePath)
{
        :

// 分割先ファイル一覧作成(分割ファイル名作成と分割サイズの設定)
            :
        Item->SubItems->Add(SizeStr);   // 分割サイズ文字列設定
        Item->SubItems->Add("未出力");  // '未出力'
        FileSize -= CutSize;            // ファイルサイズ更新
    }

// 分割先ファイルが1つの場合は結合バッチコマンドを作成しない
        :

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

// 成功を返す
    return(true);
}
//---------------------------------------------------------------------------
分割スレッドからGUIスレッドに分割進捗状況を通知する処理では1/16〜16/16の進捗タイミングで通知していました。「出力中」をGUIスレッドで表示するためには1ファイルの出力の開始時(0/16)のタイミングで進捗を通知します。進捗度1/16〜15/16で「分割中」のステータス表示を行うと、画面のちらつきが激しく見づらくなりますので、0パーセントと100パーセントのみでステータス表示を変更します。

改造Execute関数

//---------------------------------------------------------------------------
void __fastcall TDivideThread::Execute()
{
            :

        // ブロック書きこみループ
            DivideStatus = 0;                   // 分割開始
            Synchronize(ReportDividing);        // 分割開始報告
            if (Terminated){                    // ユーザー中断
                fclose(DstFP);                  // 分割先ファイルクローズ
                DivideStatus = dsUserBreak;     // ユーザー中断
                delete buffer;                  // 分割バッファ開放
                fclose(SrcFP);                  // 分割先ファイルクローズ
                return;                         // 分割中断
            }

            while(ProgressSize<ACutSize){        // 分割進捗サイズが分割サイズに達するまで繰り返し
                :
//---------------------------------------------------------------------------
GUIスレッドは分割スレッドから分割レポートで進捗状況を受け取ります。進捗度0パーセントでステータスに「分割中」を、進捗度100でステータスに「分割済」を表示します。同時にリカバー機能により、分割ファイルインデックス番号は分割リストの先頭からの番号ではなく、選択されたファイルからの番号となりますので、これに対応した改造も行います。

改造OnReportDividing関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::OnReportDividing(TMessage &Message)
{
    int Index =   (int)Message.WParam;      // 分割ファイルインデックス番号取得
    int Percent = (int)Message.LParam;      // 分割進捗状況取得
    int Offset;                             // 分割開始オフセット番号
    if (SelectedFile==NULL) Offset = 0;                      // 全分割の場合は先頭から
    else                    Offset = SelectedFile->Index;    // リカバー分割の場合は選択ファイルから
    ListVEW->Selected = ListVEW->Items->Item[Offset+Index];  // 分割中ファイル選択
    if (Percent==0)   ListVEW->Selected->SubItems->Strings[1] = "出力中";    // 1ファイル分割開始
    if (Percent==100) ListVEW->Selected->SubItems->Strings[1] = "出力済";    // 1ファイル分割終了
    ScrollListToViewItem(ListVEW);                      // 分割中のファイルが見えるようにスクロール
    DividePRG->Position = Percent;          // 分割進捗状況をプログレスバーに表示
}
//---------------------------------------------------------------------------
全ての分割完了時に、何らかのエラーもしくはユーザー中断が発生した場合には「出力中」のステータス表示を「未出力」に変更します。こうすることにより、どのファイルからリカバーする必要があるのか、ユーザーが簡単に判断できます。

改造OnDivideTRDTerminate関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::OnDivideTRDTerminate(TObject *Sender)
{
// 分割完了報告
    :
    if (Message!="") Application->MessageBox(Message.c_str(), "分割実行", MB_ICONEXCLAMATION);
                                                    // エラー発生時、メッセージ表示
    if (DivideTRD->GetDividedStatus()<0) ListVEW->Selected->SubItems->Strings[1] = "未出力";

// 分割開始画面設定
    :

}
//---------------------------------------------------------------------------


今回のバグ修正

名前変更 分割ファイル一覧を何気なくクリックしていると、分割ファイル名が変更できることに気がつきました。別に分割ファイル名が変更できること自体はバグではないのですが、分割元ファイル名の拡張子を連番で保持している分割先ファイル名を変更指定しまうと、結合作業に支障が生じます。そこで、名前の変更を禁止します。
分割ファイル一覧を表示しているListVEWのコントロールの内容を変更プロパティに対して禁止を与えます。 これでバグ修正は終わりです。


以上分割リカバー機能の追加は終わりです。使う機会がないのに越したことはありませんが、いざと言うときに重宝しています。ところでリストビューコントロールにはエクスプローラのように連続してない複数の項目を選択する機能もあります(MultiSelectプロパティーを有効にして、NextItemSelCountプロパティーを利用)。今回の分割リカバーにはこの機能は付けていません。私自身が必要性を感じていないからです。分割に失敗た時にリカバー機能を使うので、失敗したファイルが2個以上あるケースはないと思います。今後必要性を感じれば対応するかもしれませんが、今は一項目だけで十分です。

こちらから今回作成したBCBのプロジェクト一式をダウンロードできます(ヘルプ一式は含みません)。プロジェクトダウンロード(30,989バイト) BCBのバージョン3、4、5でビルドできます。BCBバージョン5ではの修正を行ってください。ヘルププロジェクト一式のダウンロードはこちら。ヘルプダウンロード(16,386バイト) 参考にしてください。

次回は分割サイズの追加を行いたいと思います。最近は1.4Mのフロッピーディスケットだけでなく、様々なリムーバブルメディアがあります。これを利用して分割ファイルを転送できれば楽になるでしょう。そこでDigital Design Technologyらしい独特で便利な分割サイズ変更機能の追加を考えています。お楽しみに。

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


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