日曜日にBCB!



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


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

リムーバブルメディア 最近は大容量のメディアが大流行です。MO、スマートメディア、メモリスティック、SDカード、大容量FD。CD-RWも一種のリムーバブルメディアですね。以前にもご紹介したように、私は仕事柄大きなサイズのデータを頻繁に取り扱います。それらのデータを移動するために寿分割を作りました。作った当初は1.4Mのフロッピーディスケットでファイル移動が出来れば満足だったのですが、どうしてももっと大容量のメディアを使ってファイル移動をしたくなりました。人間とはなんとも欲深いものでしょうか。まあ、それが技術の進歩を生んだのですから。そこで分割サイズを従来の1.4Mと720Kバイトの固定以外に、ユーザーが自由にカスタマイズする機能を追加します。どうも話が長くなるので3回のに分けて紹介します。

私が欲しい分割サイズカスタマイズ機能は、
です。空き容量全てに分割する場合、残り容量が少ないとメディアの枚数ばかりが増えてしまいますので、残り容量に対する分割スキップ条件もつけます。


設計方針

分割サイズをカスタマイズするためには、以下の機能が必要です。 分割サイズはどの様に表現すれば良いでしょうか。以下のように仕様を決めます。

項目 内容
分割サイズ 分割サイズ[バイト]、0バイトは空き容量全てに分割することを示す
スキップサイズ 残り容量が値より小さい場合は分割をスキップ、単位は次項に示す
スキップ単位 バイト/キロバイト/メガバイト/全容量に対するパーセントのいづれか

以前作成した分割ファイルクラスと共通する部分もありますが、分割スレッドとは直接関係無い部分もありますので、専用のクラスとして作成します。同時に便利なメソッドも作成します。当然分割スレッドにも必要な情報ですから、分割ファイルクラスには継承を利用します。

分割サイズクラス

//---------------------------------------------------------------------------
class cCutInfo{
  protected:
    long       CutSize;             // 分割サイズ[バイト]
    long       SkipSize;            // スキップサイズ
    char       SkipUnit;            // スキップサイズ単位('B'、'K', 'M', '%')
  public:
    cCutInfo(long _CutSize, long _SkipSize=0, char _SkipUnit='B'){  // コンストラクタ
        CutSize     = _CutSize;
        SkipSize    = _SkipSize;
        SkipUnit    = _SkipUnit;
    }
    cSutSize(cCutInfo *Source){                             // コピーコンストラクタ
        CutSize     = Source->CutSize;
        SkipSize    = Source->SkipSize;
        SkipUnit    = Source->SkipUnit;
    }
    void SetCutSize(long _CutSize){ CutSize = _CutSize; }   // 分割サイズ設定
    void SetSkipSize(long _SkipSize, char _SkipUnit='B'){   // スキップサイズ設定
        SkipSize    = _SkipSize;
        SkipUnit    = _SkipUnit;
    }
    void ClearSkip(){                                       // スキップサイズクリア
        SkipSize = 0;
    }
    int GetCutSize(){ return(CutSize); }                    // 分割サイズ取得
    long GetSkipSize(){ return(SkipSize); }                 // スキップサイズ取得
    char GetSkipUnit(){ return(SkipUnit); }                 // スキップ単位取得
    long GetCutSize(long FreeSize, long DiskSize){          // スキップ考慮分割ファイルサイズ取得
                    // FreeSize = 空き容量[バイト]、DiskSize = ディスク容量[バイト]
        switch (SkipUnit){
            case 'K':   return(FreeSize - SkipSize*1024);
            case 'M':   return(FreeSize - SkipSize*1024*1024);
            case 'B':   return(FreeSize - SkipSize);
            case '%':   return((long)((double)FreeSize * (1.0 - (double)SkipSize*0.01)));
            default:    return(DiskSize);
        }
    }
};
//---------------------------------------------------------------------------
このクラスをdiskutil.hに定義します。


サイズ編集画面(フルサイズ)

分割サイズ編集画面

続いて分割サイズの編集画面を設計しましょう。画面では以下の情報を編集します。 また、リムーバブルメディアの全容量や空き容量を取得する機能もつけます。この機能はオプション的な位置付けとして、画面に表示/非表示を選べるようにします。
分割名は自由に編集します。分割サイズはユーザーが直接入力しますが、残り全て分割の際にはスキップ条件を簡単に編集できるようにします。

編集画面と言うのはユーザーの我が侭を一手に引き受けるところです。作るのに根気が要ります。私が一番嫌いなプログラミングです。かと言って手を抜くわけいにはいきません。たまに、この様な編集データをメモ帳などで作らせるプログラムを見ることがありますが、単に開発者が手抜きしているだけで、使う立場に立っていないなとあきれてしまいます。面倒ですが、しっかり作りましょう。
画面設計

コントロール名 コントロール プロパティ イベント
EditDLG TForm BorderStyle=bsDialog、Position=poScreenCenter フォーム作成
SizeLBL TLabel Caption=一覧(&L)、AutoSize=true  
SizeGRD TStringGrid ColCount=2、RowCount=2、DefaultColWith=120、DefaultRowHeight=18、FixedRow=1、Option=goFixedVertLinegoFixedHorzLinegoVertLinegoHorzLinegoDrawFocusSelectedgoColSizinggoThumbTrackingをtrue、その他はfalse クリックキー押しキー離しセル選択
SetBTN TButton Caption=Ok、Default=true、ModalResult=mbOk  
CancelBTN TButton Caption=キャンセル(半角)、Cancel=true、ModalResult=mbCancel  
UpBTN TBitBtn NumGlyph=2、Glyph=上矢印 クリック
DownBTN TBitBtn NumGlyph=2、Glyph=下矢印 クリック
MeasureBTN TButton Caption=取得(&M) <<、分割サイズ取得と連動 クリック
DeleteBTN TButton Caption=削除(&D) クリック
HelpBTN TButton Caption=ヘルプ(&H)(半角) クリック
RemainGRP TGroupBox Caption=空き容量全て、RemainCHK・SizeEDT・SizeUD・UnitBOX・RemainLBL・AddRemainBTNを格納  
RemainCHK TCheckBox Caption=残り  
SizeEDT TEdit   変更キー押し
SizeUD TUpDown Associate=SizeEDT  
UnitBOX TComboBox Items=%・バイト・キロバイト・メガバイト(半角)、Style=csDropDownList 項目選択
RemainLBL TLabel Caption=以下の場合は出力しない(&S)、AutoSize=true  
AddRemainBTN TButton Caption=追加(&A)、更新時は「更新(&U) クリック
GetSizeGRP TGroupBox Caption=分割サイズ取得(半角)、DriveLBL・DriveBOX・RefreshBTN・TotalByteCHK・FreeByteCHK・AddCheckedBTNを格納  
DriveLBL TLabel Caption=リムーバブルドライブ(&E)(半角)、AutoSize=true  
DriveBOX TComboBox Style=csOwnerDrawFixed 項目描画項目選択
RefreshBTN TButton Caption=最新の情報に更新(&R) クリック
TotalByteRDO TRadioButton Caption=全容量(&T)  
FreeByteRDO TRadioButton Caption=空き容量(&F)  
AddCheckedBTN TButton Caption=追加(&A) クリック

分割サイズ取得グループGetSizeGRPを表示していないときは分割サイズ取得ボタンMeasureBTNを「取得(&M) >>」に、ウィンドクライアント高さClientHeightは分割サイズ取得グループGetSizeGRPの上位置Topに設定します。表示しているときは「取得(&M) <<」に、ウィンドクライアント高さは分割サイズ取得グループの高さ+7とします。
空き容量全ての追加ボタンAddRemainBTNは、分割一覧が指定サイズを示している時は「追加(&A)」に、空き容量全てを示している時は「更新(&U)」にします。

分割サイズ一覧列タイトル 分割サイズ一覧はエディタだけでは全てを設定することは出来ません。フォームの作成時に列タイトル等を設定します。行毎に指定サイズ分割なのか、空き容量全て分割なのかを識別するために、TStringGridObjectsプロパティーを利用します。 「取得」ボタンの初期化も行います。

FormCreate関数

//---------------------------------------------------------------------------
void __fastcall TEditDLG::FormCreate(TObject *Sender)
{
    LayoutSizeGrid();                   // 分割一覧リストレアウト
    UnitBOX->ItemIndex  = 1;            // バイト
    UnitBOX->Tag = UnitBOX->ItemIndex;  // 直前選択単位
    MeasureBTNClick(NULL);              // 「取得」ボタン設定
}
//---------------------------------------------------------------------------

LayoutSizeGrid関数

分割サイズ一覧を作成します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::LayoutSizeGrid()
{
// SizeGRD Column '分割名'   + '分割サイズ'(半角) .... バイト
//                '残り全て' + '' ... 残り全て、スキップ条件なし
//                           + '残り???バイト以上゚'
//                           + '残り???キロバイト以上'
//                           + '残り???メガバイト以上'
//                           + '残り???%以上'
    JustifyGridWidth();                         // 列幅調整
    SizeGRD->Cells[0][0] = "名前";              // 1列目タイトル
    SizeGRD->Cells[1][0] = "サイズ/分割条件";   // 2列目タイトル(半角)
    SizeGRD->Cells[0][1] = "";                  // 2行目クリア
    SizeGRD->Cells[1][1] = "";
    SizeGRD->Objects[0][1] = (TObject*)0;       // 編集可
}
//---------------------------------------------------------------------------

JustifyGridWidth関数

分割サイズの列幅を調整します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::JustifyGridWidth()
{
    int ColW = SizeGRD->Width - SizeGRD->ColWidths[0] - SizeGRD->GridLineWidth*3 - 3;   // 2列目幅計算
    int GridH = (SizeGRD->DefaultRowHeight + SizeGRD->GridLineWidth)*SizeGRD->RowCount + SizeGRD->GridLineWidth;  // グリッド総高さ計算
    if (GridH>SizeGRD->Height) ColW -= ::GetSystemMetrics(SM_CXVSCROLL);    // 垂直スクロールバーが表示される場合は、2列幅を減少
    SizeGRD->ColWidths[1] = ColW;       // 2列目幅設定
}
//---------------------------------------------------------------------------

MeasureBTNClick関数

分割サイズ取得グループの表示/非表示を切り替えます。「取得」ボタンのコールバック関数です。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::MeasureBTNClick(TObject *Sender)
{
    if (MeasureBTN->Caption=="取得(&M) <<"){    // 「分割サイズ取得」非表示
        ClientHeight = GetSizeGRP->Top;         // ウィンド高さ→小
        MeasureBTN->Caption  = "取得(&M) >>";   // 取得ボタン→拡張
        DriveBOX->TabStop       = false;        // 「分割サイズ取得」タブ移動→無効
        RefreshBTN->TabStop     = false;
        TotalByteRDO->TabStop   = false;
        FreeByteRDO->TabStop    = false;
        AddMeasuredBTN->TabStop = false;
    }
    else{                                       // 「分割サイズ取得」表示
        ClientHeight = GetSizeGRP->Top + GetSizeGRP->Height + 7;    // ウィンド高さ→大
        MeasureBTN->Caption = "取得(&M) <<";    // 取得ボタン→縮小
        DriveBOX->TabStop       = true;         // 「分割サイズ取得」タブ移動→有効
        RefreshBTN->TabStop     = true;
        TotalByteRDO->TabStop   = true;
        FreeByteRDO->TabStop    = true;
        AddMeasuredBTN->TabStop = true;
    }
}
//---------------------------------------------------------------------------


分割サイズ編集実行

分割サイズ編集はメイン画面からExecute関数を呼び出して実行します。分割サイズ情報は文字列リストを介して情報をやりとりします。文字列リストには、 を持たせます。
Execute関数では、
  1. 分割サイズ一覧の設定
  2. リムーバブルドライブの空き容量更新
  3. ボタン状態の更新
  4. モーダル状態でのユーザー入力待ち
  5. OKが押されたときの分割サイズ一覧の更新
を行います。

Execute関数

//---------------------------------------------------------------------------
bool __fastcall TEditDLG::Execute(TStringList *CutSizes)
{
// 分割サイズ一覧の設定
    SizeGRD->RowCount = 2;   // 分割サイズ行数=2

    for (int i=0;i<CutSizes->Count;i++){ // 分割サイズリスト→一覧設定
        SizeGRD->Cells[0][i+1] = CutSizes->Strings[i];         // 分割名設定
        cCutInfo *CutInfo = (cCutInfo*)CutSizes->Objects[i];   // 分割情報取得
        if (CutInfo->GetCutSize()==0){      // 残り全て?
            SizeGRD->Cells[1][i+1] = SetRemainInfo(CutInfo->GetSkipSize(), CutInfo->GetSkipUnit());  // 残り全て設定
            SizeGRD->Objects[0][i+1] = (TObject*)1;     // 編集不可
        }
        else{               // 指定サイズ
            SizeGRD->Cells[1][i+1] = FormatD2T((double)CutInfo->GetCutSize());    // 分割サイズ設定
            SizeGRD->Objects[0][i+1] = (TObject*)0;     // 編集可
        }
    }
    SizeGRD->Cells[0][CutSizes->Count+1] = "";    // 末尾に空白追加
    SizeGRD->Cells[1][CutSizes->Count+1] = "";
    SizeGRD->Objects[0][CutSizes->Count+1] = (TObject*)0;   // 編集可
    SizeGRD->RowCount = CutSizes->Count+2;

// リムーバブルドライブリスト更新
    RefreshDriveAll();

// コントロール状態の更新
    UpdateMenu();

// モーダル状態でのユーザー入力待ち
    ActiveControl = SizeGRD;    // サイズ一覧にフォーカス
    ShowModal();                // モーダル表示

// OKが押されたときの分割サイズ一覧の更新
    if (ModalResult==mrOk){        // Okが押された?
        ClearCutSizes(CutSizes);   // 分割サイズリストクリア
        int CutSize, SkipSize;     // 分割サイズ、スキップサイズ
        char SkipUnit;             // スキップサイズ単位
        for (int i=1;i<(SizeGRD->RowCount-1);i++){    // 分割サイズ一覧(末尾を除く)ループ
            CutSize = 0;
            SkipSize = 0;
            SkipUnit = ' ';
            if (SizeGRD->Objects[0][i]!=(TObject*)0){       // 残り全て?
                GetRemainInfo(SizeGRD->Cells[1][i], SkipSize, SkipUnit);   // 残り全て取得
            }
            else{                                           // 指定サイズ
                CutSize = aKtoi(SizeGRD->Cells[1][i]);      // 指定サイズ取得
            }
            cCutInfo *EditCutSize = new cCutInfo(CutSize, SkipSize, SkipUnit);        // 分割サイズ情報作成
            CutSizes->AddObject(SizeGRD->Cells[0][i].Trim(), (TObject*)EditCutSize);  // 分割サイズ情報追加
        }
    }

    return(ModalResult==mrOk);    // Okが押されたらtrue、それ以外はfalseを返す
}
//---------------------------------------------------------------------------

SetRemainInfo関数

空き容量全ての分割条件文字列を作成します。
//---------------------------------------------------------------------------
AnsiString __fastcall TEditDLG::SetRemainInfo(int SkipSize, char SkipUnit)
{
    if (SkipSize==0) return(NoSkipCase);        // スキップ条件なし

    AnsiString InfoStr = "残り" + AnsiString(SkipSize);
    switch (SkipUnit){
        case 'B':   InfoStr = InfoStr + "バイト以上";         break;    // 半角
        case 'K':   InfoStr = InfoStr + "キロバイト以上";     break;    // 半角
        case 'M':   InfoStr = InfoStr + "メガバイト以上";     break;    // 半角
        case '%':   InfoStr = InfoStr + "%以上";              break;
    }
    return(InfoStr);
}
//---------------------------------------------------------------------------

GetRemainInfo関数

空き容量全ての分割条件文字列からスキップサイズ、スキップサイズ単位を取得します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::GetRemainInfo(AnsiString EditStr, int &SkipSize, char &SkipUnit)
{
    SkipSize = 0;
    SkipUnit = ' ';

    if (EditStr==NoSkipCase) return;        // スキップ条件なし

    EditStr = EditStr.SubString(5, EditStr.Length() - 8);

    int delimPos = EditStr.Pos("メガバイト");    // 半角
    if (delimPos!=0){
        EditStr = EditStr.SubString(1, delimPos-1);
        SkipSize = EditStr.ToInt();
        SkipUnit = 'M';
        return;
    }

    delimPos = EditStr.Pos("キロバイト");    // 半角
    if (delimPos!=0){
        EditStr = EditStr.SubString(1, delimPos-1);
        SkipSize = EditStr.ToInt();
        SkipUnit = 'K';
        return;
    }

    delimPos = EditStr.Pos("バイト");    // 半角
    if (delimPos!=0){
        EditStr = EditStr.SubString(1, delimPos-1);
        SkipSize = EditStr.ToInt();
        SkipUnit = 'B';
        return;
    }

    delimPos = EditStr.Pos("%");
    EditStr = EditStr.SubString(1, delimPos-1);
    SkipSize = EditStr.ToInt();
    SkipUnit = '%';
}
//---------------------------------------------------------------------------

RefreshDriveAll関数

リムーバブルドライブリストRefreshDriveAll関数では、ドライブ名('A:\'〜'Z:\')を設定し、表示時にDriveBOXのOnDrawItemイベントのコールバック関数DriveBOXDrawItem関数でドライブアイコンと一緒に表示します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::RefreshDriveAll()
{
    int BOXItemIndex = DriveBOX->ItemIndex;    // 選択ドライブリストアイテムを退避
    char* DriveName = "_:\\";                  // ドライブ名作成バッファ
    DriveBOX->Items->Clear();                  // ドライブリストアイテムクリア
    for (int DriveCap='A';DriveCap<='Z';DriveCap++){       // 全てのドライブに対して
        DriveName[0] = (char)DriveCap;                     // ドライブ名'A:\'〜'Z:\'作成
        if (::GetDriveType(DriveName)==DRIVE_REMOVABLE){   // リムーバブルドライブ?
            DriveBOX->Items->Add(DriveName);  // ドライブリストに追加
        }
    }

    if (BOXItemIndex==-1) BOXItemIndex = 0;    // 選択ドライブリストアイテムが無い場合先頭を指す
    DriveBOX->ItemIndex = BOXItemIndex;        // 選択ドライブリストアイテム復帰
}
//---------------------------------------------------------------------------

DriveBOXDrawItem関数

リムーバブルのドライブ名とドライブアイコンを表示します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::DriveBOXDrawItem(TWinControl *Control,
      int Index, TRect &Rect, TOwnerDrawState State)
{
    TComboBox *BOX = (TComboBox*)Control;    // リストコントロール
    AnsiString DriveName = BOX->Items->Strings[Index];   // ドライブ名'A:\'〜'Z:\'取得

    SHFILEINFO shfi;    // シェルファイル情報構造体(API)
    SHGetFileInfo(DriveName.c_str(), 0, &shfi, sizeof(shfi), SHGFI_SMALLICON|SHGFI_ICON);  // ドライブアイコン取得

    BOX->Canvas->Brush->Color = clWindow;       // リスト背景色設定
    BOX->Canvas->Font->Color  = clWindowText;   // リストフォント色設定
    BOX->Canvas->FillRect(Rect);                // リスト消去(背景色で塗りつぶす)
    DrawIconEx(BOX->Canvas->Handle, Rect.Left+2, Rect.Top, shfi.hIcon, 16, 16, 0, NULL, DI_NORMAL); // アイコンを描画
    RECT rect;                                  // ドライブ名('?:\')描画
    rect.left  = Rect.Left+22;  rect.top = Rect.Top;
    rect.right = Rect.Right;    rect.bottom = Rect.Bottom;
    ::DrawText(BOX->Canvas->Handle, BOX->Items->Strings[Index].c_str(), BOX->Items->Strings[Index].Length(),
               &rect, DT_LEFT|DT_SINGLELINE|DT_VCENTER);
}
//---------------------------------------------------------------------------

ClearCutSizes関数

分割サイズリストのクリアはサイズ編集画面には依存しません。従って独立関数として、diskutil.cppに定義します。
//---------------------------------------------------------------------------
void ClearCutSizes(TStringList *CutSizes)
{
    for (int i=CutSizes->Count-1;i>0;i--){    // 分割サイズリストを前方ループ
        cCutInfo *CutInfo = (cCutInfo*)CutSizes->Objects[i];    // 分割サイズオブジェクト取得
        delete CutInfo;                                               // オブジェクト破棄
    }
    CutSizes->Clear();    // 分割サイズリストクリア
}
//---------------------------------------------------------------------------

FormatD2T関数

分割サイズは3桁区切りで表示します。サイズxを文字列に変換します。これもサイズ編集画面には依存しません。従って独立関数として、diskutil.cppに定義します。なお、将来を考えてテラバイトの変換にも対応します。
//---------------------------------------------------------------------------
AnsiString FormatD2T(double x)
{
    int Tera = (int)(x/1000000000000.0);            // テラバイト
    x -= (double)Tera*1000000000000.0;              // テラバイト未満のこり
    int Giga = (int)(x/1000000000.0);               // ギガバイト
    int ix = (int)(x - (double)Giga*1000000000.0);  // ギガバイト未満のこり
    int Mega = ix/1000000;                          // メガバイト
    ix -= Mega*1000000;                             // メガバイト未満のこり
    int Kilo = ix/1000;                             // キロバイト
    int Byte = ix - Kilo*1000;                      // バイト
    AnsiString s;                                   // 書式出力文字列
    if (Giga!=0) s = Format("%d,%3d,%3d,%3d", OPENARRAY(TVarRec, (Giga, Mega, Kilo, Byte)));    // G,M,K,B
    else{
        if (Mega!=0) s = Format("%d,%3d,%3d", OPENARRAY(TVarRec, (Mega, Kilo, Byte)));          // M,K,B
        else{
            if (Kilo!=0) s = Format("%d,%3d", OPENARRAY(TVarRec, (Kilo, Byte)));                // K,B
            else         s = Format("%d", OPENARRAY(TVarRec, (Byte)));                          // B
        }
    }
    for (int i=1;i<=s.Length();i++) if (s[i]==' ') s[i] = '0';

    return(s);
}
//---------------------------------------------------------------------------


分割名/サイズ編集

分割名/サイズはスプレッドシートに使われるグリッド形式です。ユーザーがグリッド中のセルで文字列を編集するのですが、ユーザーにとっては操作しやすいグリッドは、プログラムからすると結構面倒です。ただ入力させるだけではなく、状況に応じていろいろなことをする必要があります。サイズ編集グリッドでは以下のイベントに対してコールバックをプログラミングします。

SizeGRDClick関数

//---------------------------------------------------------------------------
void __fastcall TEditDLG::SizeGRDClick(TObject *Sender)
{
    UpdateMenu();   // コントロール状態の更新
}
//---------------------------------------------------------------------------

SizeGRDKeyPress関数

//---------------------------------------------------------------------------
void __fastcall TEditDLG::SizeGRDKeyPress(TObject *Sender, char &Key)
{
    if (Key==VK_RETURN){    // リターンキーが押されたら
        if (SizeGRD->Col==0){    // 「名前」列→「サイズ」列
            SizeGRD->Col = 1;
        }
        else{                    // 「サイズ」列→「名前」列
            SizeGRD->Col = 0;
            if (SizeGRD->Cells[0][SizeGRD->RowCount-1]!=""){       // 新規行追加
                SizeGRD->RowCount++;
                SizeGRD->Cells[0][SizeGRD->RowCount-1] = "";
                SizeGRD->Cells[1][SizeGRD->RowCount-1] = "";
                SizeGRD->Objects[0][SizeGRD->RowCount-1] = (TObject*)0;
                SizeGRD->Row = SizeGRD->RowCount-1;
            }
            else{                                                  // 次行
                if (SizeGRD->Row<(SizeGRD->RowCount-1)) SizeGRD->Row++;
            }
        }
    }
}
//---------------------------------------------------------------------------

SizeGRDKeyUp関数

//---------------------------------------------------------------------------
void __fastcall TEditDLG::SizeGRDKeyUp(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
    UpdateMenu();   // コントロール状態の更新
}
//---------------------------------------------------------------------------

SizeGRDSelectCell関数

//---------------------------------------------------------------------------
void __fastcall TEditDLG::SizeGRDSelectCell(TObject *Sender, int Col,
      int Row, bool &CanSelect)
{
    if ((Col==0) || (SizeGRD->Objects[0][Row]==(TObject*)0)){   // 名前列または指定サイズ
        SizeGRD->Options << goAlwaysShowEditor << goEditing;        // 編集可
    }
    else{
        SizeGRD->Options >> goAlwaysShowEditor >> goEditing;        // 編集不可
    }

    if (SizeGRD->Objects[0][Row]==(TObject*)0){                 // 指定サイズ
        AddRemainBTN->Caption = "追加(&A)";                         // 残り全て「追加」
    }
    else{                                                       // 以外→残り全て分割設定
        int SkipSize;
        char SkipUnit;
        GetRemainInfo(SizeGRD->Cells[1][Row], SkipSize, SkipUnit);
        if (SkipSize==0){                     // スキップ条件なし?
            RemainCHK->Checked = false;
            SizeEDT->Text = "1";
        }
        else{                                 // スキップ条件あり
            RemainCHK->Checked = true;
            SizeEDT->Text = (AnsiString)SkipSize;
            switch (SkipUnit){
                case '%':   UnitBOX->ItemIndex = 0; break;  // %
                case 'B':   UnitBOX->ItemIndex = 1; break;  // バイト
                case 'K':   UnitBOX->ItemIndex = 2; break;  // キロバイト
                case 'M':   UnitBOX->ItemIndex = 3; break;  // メガバイト
            }
        }
        AddRemainBTN->Caption = "更新(&U)";                       // 残り全て「更新」
    }
}
//---------------------------------------------------------------------------

UpDownBTNClick関数

分割一覧の行単位の入れ替えは上下矢印のボタンで行います。二つのボタンのコールバックを共通化します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::UpDownBTNClick(TObject *Sender)
{
    int Index = SizeGRD->Row;                         // 選択行取得
    AnsiString Name = SizeGRD->Cells[0][Index];       // 選択分割名取得
    AnsiString Size = SizeGRD->Cells[1][Index];       // 選択サイズ/分割条件取得
    TObject *Editable = SizeGRD->Objects[0][Index];   // 選択編集可不可取得

    TBitBtn *BTN = (TBitBtn*)Sender;                  // クリックされたボタン取得
    int Target;                                       // 移動ターゲット
    if (BTN==UpBTN)   Target = -1;                    // 上ボタン
    if (BTN==DownBTN) Target = +1;                    // 下ボタン

    SizeGRD->Cells[0][Index]   = SizeGRD->Cells[0][Index+Target];    // ターゲット行移動
    SizeGRD->Cells[1][Index]   = SizeGRD->Cells[1][Index+Target];
    SizeGRD->Objects[0][Index] = SizeGRD->Objects[0][Index+Target];
    SizeGRD->Cells[0][Index+Target] = Name;                          // 選択行移動
    SizeGRD->Cells[1][Index+Target] = Size;
    SizeGRD->Objects[0][Index+Target] = Editable;
    SizeGRD->Row += Target;                                          // 選択移動

    UpdateMenu();   // コントロール状態の更新
}
//---------------------------------------------------------------------------

DeleteBTNClick関数

選択した分割サイズを削除します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::DeleteBTNClick(TObject *Sender)
{
    AnsiString Message = "'" + SizeGRD->Cells[0][SizeGRD->Row] + " - " + SizeGRD->Cells[1][SizeGRD->Row];  // 削除確認メッセージ
    if (SizeGRD->Objects[0][SizeGRD->Row]==0) Message = Message +  + "バイト";      // 指定サイズ?(「バイト」は半角)
    Message = Message + "' を削除しますか?";
    int yesno = Application->MessageBox(Message.c_str(), "分割サイズ削除", MB_ICONQUESTION|MB_YESNO);   // 削除確認
    if (yesno==ID_YES){           // 削除実行?
        for (int i=SizeGRD->Row;i<SizeGRD->RowCount;i++){       // 編集データ前詰め
            SizeGRD->Cells[0][i] = SizeGRD->Cells[0][i+1];
            SizeGRD->Cells[1][i] = SizeGRD->Cells[1][i+1];
            SizeGRD->Objects[0][i] = SizeGRD->Objects[0][i+1];
        }
        SizeGRD->RowCount--;         // 1行減
        JustifyGridWidth();          // 列幅調整
    }

    UpdateMenu();   // コントロール状態の更新
}
//---------------------------------------------------------------------------

HelpBTNClick関数

サイズ編集画面のヘルプを表示します。ヘルプコンテキストIDのIDH_EDITSIZEはヘルプファイル作成で決めます。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::HelpBTNClick(TObject *Sender)
{
    Application->HelpCommand(HELP_CONTEXT, IDH_EDITSIZE);    // ヘルプを表示
}
//---------------------------------------------------------------------------


空き容量全て分割編集

空き容量全てに対する分割では分割スキップ条件を編集します。サイズ編集で直接ユーザーがスキップ条件を入力すると、誤った入力をする可能性があり、チェックが必要になります。空き容量全て分割では専用の編集グループを作成します。編集グループでは、 を行います。分割一覧で既に入力した空き容量全て分割を選択している場合はその内容を更新、それ以外は新たに追加とします。なお、分割スキップをして、かつスキップサイズが入力されていない(空白)場合は、追加ボタンは押せないようにします。

SizeEDTChange関数

スキップサイズが入力(変更)された場合、追加ボタンの有効/無効を設定します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::SizeEDTChange(TObject *Sender)
{
    AddRemainBTN->Enabled = true;        // 追加ボタン有効
    if (RemainCHK->Checked){             // 分割スキップを指定した場合
        AddRemainBTN->Enabled = (SizeEDT->Text!="");   // スキップサイズが入力されていない場合は追加ボタン無効
    }
}
//---------------------------------------------------------------------------

SizeEDTKeyPress関数

スキップサイズが入力された場合、その値(整数値)を検査し、範囲外となるときは入力を無効にします。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::SizeEDTKeyPress(TObject *Sender, char &Key)
{
    if ((!isdigit((int)Key)) && (Key!=VK_BACK) && (Key!=VK_DELETE)) Key = 0;
    if (isdigit((int)Key)){
        AnsiString ValueString = SizeEDT->Text + AnsiString(Key);
        int Value = ValueString.ToInt();
        if ((Value<SizeUD->Min) || (Value>SizeUD->Max)) Key = 0;
    }
}
//---------------------------------------------------------------------------

UnitBOXChange関数

スキップ単位が変更された場合、スキップサイズの上限・下限を設定します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::UnitBOXChange(TObject *Sender)
{
    TComboBox *BOX = (TComboBox*)Sender;    // スキップサイズリスト取得
    if (BOX->ItemIndex==0){     // %選択
        if (BOX->Tag!=0){       // バイト→%
            int Precent = SizeEDT->Text.ToInt();    // パーセント値取得
            if (Precent<1)  Precent = 1;            // 下限チェック
            if (Precent>99) Precent = 99;           // 上限チェック
            SizeUD->Max = 99;                       // 上限値設定
            SizeEDT->Text = Format("%d", OPENARRAY(TVarRec, (Precent)));  // パーセント値更新
        }
    }
    else{                       // バイト選択
        if (BOX->Tag==0){       // %→バイト
            int Byte = SizeEDT->Text.ToInt();       // バイト値取得
            if (Byte<1)    Byte = 1;                // 下限チェック
            if (Byte>1024) Byte = 1024;             // 上限チェック
            SizeUD->Max = 1024;                     // 上限値設定
            SizeEDT->Text = Format("%d", OPENARRAY(TVarRec, (Byte)));    // バイト値更新
        }
    }
    BOX->Tag = BOX->ItemIndex;  // 直前選択単位
}
//---------------------------------------------------------------------------

AddRemainBTNClick関数

空き容量全ての追加を行います。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::AddRemainBTNClick(TObject *Sender)
{
    int Index;   // 追加位置
    if (AddRemainBTN->Caption=="追加(&A)") Index = SizeGRD->RowCount-1;  // 「追加」の場合、末尾に追加
    else                                   Index = SizeGRD->Row;         // 「更新」の場合、指定位置を更新

    SizeGRD->Cells[0][Index] = RemainAllTitle;      // デフォルト分割名設定
    if (RemainCHK->Checked) SizeGRD->Cells[1][Index] = "残り" + SizeEDT->Text + UnitBOX->Text + "以上";  // スキップ条件設定
    else                    SizeGRD->Cells[1][Index] = NoSkipCase;
    SizeGRD->Objects[0][Index] = (TObject*)1;         // 編集不可

    if (Index==(SizeGRD->RowCount-1)){  // 最後に追加した場合
        SizeGRD->RowCount++;                // 行追加
        SizeGRD->Row = Index;               // 追加した行に移動
        SizeGRD->Col = 0;                   // 名前列に移動
        JustifyGridWidth();                 // 列幅調整
        SizeGRD->SetFocus();                // 一覧グリッドにコントロール移動
    }

    UpdateMenu();   // コントロール状態の更新
}
//---------------------------------------------------------------------------


リムーバブル分割サイズ取得

リムーバブルメディアの分割サイズを取得します。メディアの全容量と空き容量を取得し、分割サイズ一覧に追加します。既にリムーバブルドライブ一覧はRefreshDriveAll関数で、リストのオーナードローはDriveBOXDrawItem関数で作成しました。あとはドライブ一覧の変更イベントと、最新の情報に更新と追加ボタンが押されたイベントに対する処理を行います。

DriveBOXChange関数

ドライブ一覧が変更された場合、全容量と空き容量を取得します。「最新の情報に更新」ボタンを不可にする処理を行っているのは、メディアが準備されていないドライブに容量を得るためにアクセスするとしばらく待たされ、その間にこのボタンが押されて多重イベントが発生することを防ぐためです。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::DriveBOXChange(TObject *Sender)
{
    RefreshBTN->Enabled = false;            // 「最新の情報に更新」ボタン不可
    Application->ProcessMessages();         // 上記ボタンを直ちに更新
    TComboBox *BOX = (TComboBox*)Sender;    // リストコントロール取得
    AnsiString DriveName = BOX->Items->Strings[BOX->ItemIndex];    // ドライブパス取得 ('A:\'等)

    TotalByte = GetDiskSize(DriveName);     // ドライブ全容量取得
    FreeByte = GetDiskFree(DriveName);      // ドライブ空き容量取得
    if (TotalByte==-1.0){                   // 取得失敗(メディアが準備されていない)
        TotalByteRDO->Enabled = false;             // 全容量ラジオボタン不可
        FreeByteRDO->Enabled = false;              // 空き容量ラジオボタン不可
        AddMeasuredBTN->Enabled = false;           // 追加ボタン不可
        TotalByteRDO->Caption = "全容量(&A)";      // 全サイズ表示なし
        FreeByteRDO->Caption  = "空き容量(&F)";    // 空きサイズ表示なし
    }
    else{                              // 取得成功
        TotalByteRDO->Enabled = true;              // 全容量ラジオボタン可
        FreeByteRDO->Enabled = true;               // 空き容量ラジオボタン可
        AddMeasuredBTN->Enabled = true;            // 追加ボタン可
        TotalByteRDO->Caption = "全容量(&A)  " + FormatD2T(TotalByte) + "バイト";     // 全容量サイズ表示(半角)
        FreeByteRDO->Caption  = "空き容量(&F)  " + FormatD2T(FreeByte) + "バイト";    // 空き容量サイズ表示(半角)
    }

    RefreshBTN->Enabled = true;             // 「最新の情報に更新」ボタン可
    Application->ProcessMessages();         // 上記ボタンを直ちに更新
}
//---------------------------------------------------------------------------

RefreshBTNClick関数

現在のドライブの状態を最新に更新します。DriveBoxChange関数を呼び出します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::RefreshBTNClick(TObject *Sender)
{
    DriveBOXChange(DriveBOX);    // ドライブ再選択
}
//---------------------------------------------------------------------------

AddMeasuredBTNClick関数

取得したメディアサイズを分割サイズ一覧に追加します。
//---------------------------------------------------------------------------
void __fastcall TEditDLG::AddMeasuredBTNClick(TObject *Sender)
{
    int Index = SizeGRD->RowCount-1;            // 末尾に追加
    SizeGRD->Cells[0][Index] = NewSizeTitle;    // 新しい分割サイズ名設定
    if (TotalByteRDO->Checked) SizeGRD->Cells[1][Index] = FormatD2T((double)TotalByte);  // 全容量サイズ設定
    if (FreeByteRDO->Checked)  SizeGRD->Cells[1][Index] = FormatD2T((double)FreeByte);   // 空き容量サイズ設定
    SizeGRD->Objects[0][Index] = (TObject*)0;   // 編集可
    SizeGRD->RowCount++;            // 行追加
    SizeGRD->Row = Index;           // 追加した行に移動
    SizeGRD->Col = 0;               // 名前列に移動
    JustifyGridWidth();             // 列幅調整
    SizeGRD->SetFocus();            // 一覧グリッドにコントロール移動

    UpdateMenu();   // コントロール状態の更新
}
//---------------------------------------------------------------------------

GetDiskSize関数

メディアの全容量を取得します。GetDiskFree関数とほぼ同じです。GetDiskFree関数同様、再利用性を考えて独立関数としdiskutil.cppに定義します。
//---------------------------------------------------------------------------
double GetDiskSize(AnsiString DriveName)
// DriveName "#:\" ローカルドライブ名
//           "\\ComputerName\ShareName\Directory\.." ネットワーク統一命名規約
{
    static BOOL (WINAPI *APIGetDiskFreeSpaceEx)(LPCSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
                                // 関数のエントリポインタを保持する変数
    double TotalSize = -1.0;    // 全容量(実数表現)
    ULARGE_INTEGER FreeAvBytes; // 使用可能空き容量(64ビット整数表現)
    ULARGE_INTEGER TotalBytes;  // 総容量(64ビット整数表現)
    ULARGE_INTEGER FreeBytes;   // 空き容量(64ビット整数表現)

    HANDLE DLLInstance = LoadLibrary("KERNEL32.DLL");   // DLL動的ロード
    APIGetDiskFreeSpaceEx = (BOOL(WINAPI *)(LPCSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER))(GetProcAddress(DLLInstance, "GetDiskFreeSpaceExA"));
                                // GetDiskFreeSpaceExA関数エントリ取得
    if (APIGetDiskFreeSpaceEx==NULL){           // GetDiskFreeSpaceExA関数がない(初代Win95)
        DWORD SecPerClu, BytePerSec, NumFreeClu, TotalClu; // クラスタ・セクタ情報
        int Result = GetDiskFreeSpace(DriveName.c_str(), &SecPerClu, &BytePerSec, &NumFreeClu, &TotalClu);
                               // 旧API関数で全容量取得
        if (Result) TotalSize = (int)SecPerClu*(int)BytePerSec*(int)TotalClu;
    }
    else{                                       // GetDiskFreeSpaceExA関数がある(それ以降のWindows)
        int Result = APIGetDiskFreeSpaceEx(DriveName.c_str(), &FreeAvBytes, &TotalBytes, &FreeBytes);
                               // 新API関数で全容量取得
        if (Result) TotalSize = (double)TotalBytes.u.HighPart*4294967296.0 + (double)TotalBytes.u.LowPart;
    }

    FreeLibrary(DLLInstance);  // DLL解放

    return(TotalSize);    // 全容量(実数表現)を返す
}
//---------------------------------------------------------------------------


おなじみUpdateMenu

分割編集画面のボタン等を状況に合わせて有効/無効に設定します。第7話に出てきたUpdateMenu関数を分割編集画面にも作ります。コントロールするのは分割サイズの削除ボタンと並べ替えボタンです。空き容量全てと分割サイズグループは個々の関数で既にコントロールされています。

UpdateMenu関数

//---------------------------------------------------------------------------
void __fastcall TEditDLG::UpdateMenu()
{
    DeleteBTN->Enabled = (SizeGRD->Row<(SizeGRD->RowCount-1));    // 削除ボタンコントロール

    UpBTN->Enabled = true;                                            // 上移動ボタンコントロール
    if (SizeGRD->Row==1) UpBTN->Enabled = false;
    if (SizeGRD->Row==(SizeGRD->RowCount-1)) UpBTN->Enabled = false;

    if (SizeGRD->Row<(SizeGRD->RowCount-2)) DownBTN->Enabled = true;  // 下移動ボタンコントロール
    else                                    DownBTN->Enabled = false;
}
//---------------------------------------------------------------------------


定数定義

変更の可能性がある定数はヘッダファイルedit.hに定義します。
//---------------------------------------------------------------------------
#define NewSizeTitle   "名称未設定"
#define RemainAllTitle "残り全て"
#define NoSkipCase     "スキップ条件なし"    // 半角
//---------------------------------------------------------------------------


分割サイズ編集画面の作成は以上で終わりです。大切なことは編集データを如何に表現し、如何に管理するかです。編集作業に対するプログラミングと言うのはデータが停留しがちで、その割りにはユーザー入力に対する処理が増えます。結構面倒なプログラミングだと言うことが分かって頂けましたでしょうか? 説明するのも面倒でプログラムリストの羅列になってしまいました。

次回はメイン画面に対する分割サイズのカスタマイズ化を行います。初期化ファイルとのやりとり、メニュー設計、分割スレッドへの受け渡しとまだまだプログラミング作業は残っています。よろしくお付き合いください。

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


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