日曜日にBCB!



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


第9話「もっと使いやすく」

第8話はマルチスレッド化で大変でしたが、プログラムが強靭かつ使いやすくなったと思います。今回は残りの、
を中心に機能を追加します。また、ちょっと便利な機能も2つほど付け加えたいと思います。


ドラッグアンドドロップ

ドラッグアンドドロップエクスプローラ間でのファイルのコピーや移動、ごみ箱に捨てたり、多くのアプリケーションでファイルを開く操作には、ドラッグアンドドロップが便利です。寿分割にも是非欲しい機能です。ドラッグアンドロップのプログラミング方法はWindowsで定まっており、BCB3ではオンラインヘルプの「C++Builderプログラミング/VCLプログラミング/ドラッグアンドドロップ」に記述されています。しかし、BCBのヘルプはわかりにくいと評判(?)で、これを読んだだけではなかなかわかりません。そう言う時には第1話で述べた情報入手源が頼りになります。私はいろいろなプログラムにドラッグアンドドロッププログラミングを流用して使用しているので、情報入手源の元の基はどこか忘れてしまいました。おそらく「無敵のBorland C++ Builder(小出 俊夫著、秀和システム、1997年)」だと思います。SBORLANDの会議室にもいくつか紹介されていました。
寿分割ではエクスプローラ等のプログラムからドラッグ(送り出し)されたファイル(正確にはファイル名)を寿分割がドロップ(受け取り)します。つまりドロップされたファイル名に対する処理を行います。手順は、
  1. WindowsAPIのDragAcceptFiles関数でウィンドウがドロップされたファイルを受け取ることをWindowsに登録。
  2. WindowsメッセージのWM_DROPFILESメッセージを受けてドロップ処理、ドロップファイル数、ファイル一覧から必要な処理を行い、ドロップのためにWindowsが使用したメモリを解放。
です。寿分割では2番目の処理で分割元ファイル名を取得し、ファイルを開くのと同様の処理を行います。

ドロップ受け取り登録

この処理はフォームが作成されたときに行います。まずは第7話のFormCreate関数の改造からはじめましょう

改造FormCreate関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::FormCreate(TObject *Sender)
{

    DragAcceptFiles(Handle, true);    // ドロップ受け取り登録
    UpdateMenu();    // メニュー更新
}
//---------------------------------------------------------------------------
DragAcceptFiles関数の第1引数は、寿分割メイン画面のハンドルです。第2引数がtrueの場合、受け取りを登録します。

続いてWindowsのWM_DROPFILESメッセージを受ける準備をします。main.hにWM_DROPFILESイベントにハンドラーを登録します。

BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(FDM_REPORTDIVIDING, TMessage, OnReportDividing)
MESSAGE_HANDLER(FDM_NOWRITESPACE, TMessage, OnNoWriteSpace)
MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, OnDropFiles)
END_MESSAGE_MAP(TForm)
同時にコールバック関数の宣言をTMainWNDクラスに登録します。
void __fastcall OnDropFiles(TWMDropFiles Message);

最後にドロップ処理以降を行います。分割ファイル名は第4話「データの流れ道」でオープンダイアログが責任を持つことを忘れないでください。
//---------------------------------------------------------------------------
void __fastcall TMainWND::OnDropFiles(TWMDropFiles Message)
{
    char FileName[256];                         // メッセージからファイル名を受け取るバッファ
    DragQueryFile((HDROP)Message.Drop, 0, FileName, 255);    // メッセージからファイル名を受け取る
    DragFinish((HDROP)Message.Drop);            // ドロップのためにWindowsが使用したメモリを解放
    OpenDLG->FileName = (AnsiString)FileName;   // 分割元ファイル名を設定
    ListDivideFiles(OpenDLG->FileName);         // 分割先ファイル一覧および結合バッチファイル名を設定
    SetupDestPath(OpenDLG->FileName);           // 分割先パスをステータスバーに設定。
    Application->BringToFront();                // 寿分割を最前面に移動
    UpdateMenu();                               // メニュー更新
}
//---------------------------------------------------------------------------
処理の流れは至って簡単です。注意することは、DragFinish関数で終了処理を必ず行うことと、オープンダイアログに忘れずに分割元ファイル名を設定すること(責任を持たせること)、Application::BringToFront関数で寿分割を最前面にもってくることです。ファイルをドラッグアンドドロップで受ける時には寿分割は最前面にあるとは限りませんので、忘れずに最前面移動の処理を入れます。

一見難しそうなドラッグアンドドロップも簡単にプログラミングできました。「ファイル」「開く」メニューを選択して、さらにオープンダイアログでフォルダを移動しながら、沢山のファイルから分割元ファイルを指定する手順より、はるかに素早く分割元ファイルを開けます。


ステータスバーツールチップヒント

分割先変更

もうひとつ素早く操作したいものに分割先の変更があります。分割元ファイル同様メニューから選択するのではなく、ワンクリックで分割先の変更ダイアログが出てくると便利です。直ぐに思い浮かぶのがツールバーボタンですが、あまりボタンが多くなるのも個人的には嫌いです。分割先はステータスバーに表示していますので、これをダブルクリックして分割先を変更したいと思います。Windowsの標準Look&Feelとは少々異なりますのでツールチップヒントも表示しましょう。
なおダブルクリックにする理由は、ウィンドをアクティブにするために寿分割のどこかをクリックした時、たまたまステータスバーをクリック(シングルクリック)してしまい分割先を変更したくないのに、分割先の変更ダイアログが出てしまうのを防ぐためです。ちゃんと考えているでしょ。ちなみに、分割ファイル一覧がないときにはこのツールチップヒントも非表示にします。

まずステータスバーにツールチップヒントを与えます。ステータスバーのプロパティに、
続いてステータスバーのダブルクリックのコールバックを登録します。既に分割先の変更はPathMNUClick関数で実現していますので、コールバック先のプロパティーを編集してこの関数にします。
ただし、ダブルクリックイベントは分割先変更が不可の場合でも発生します。分割先変更が不可の場合は、コールバック関数の中でなにもしないで処理を終了します。

改造PathMNUClick関数

//--------------------------------------------------------------------------- void __fastcall TMainWND::PathMNUClick(TObject *Sender) { if (!PathMNU->Enabled) return; // 分割先変更メニュー無効の場合は処理中断 AnsiString Path = StatusBAR->SimpleText; // 分割先パスを作業文字列に複写 if (SelectFolder(Handle, Path, "分割先を選択してください")){ // フォルダ選択実行 StatusBAR->SimpleText = Path; // 選択されたら分割パスを更新 } } //---------------------------------------------------------------------------
最後にツールチップヒントの表示/非表示を切替えます。お馴染みUpdateMenu関数で処理します。ツールチップヒントは分割先メニューが有効なときに表示し、無効なときには非表示になります。

改造UpdateMenu関数

//---------------------------------------------------------------------------
void __fastcall TMainWND::UpdateMenu()
{
    PathMNU->Enabled = (ListVEW->Items->Count>2);     // 分割先メニューは分割先ファイル数が2つ以上で有効
    GoDivideMNU->Enabled = (ListVEW->Items->Count>2); // 分割実行メニューは分割先ファイル数が2つ以上で有効
    StatusBAR->ShowHint = PathMNU->Enabled;           // 分割先変更ツールチップヒント表示切替え
}
//---------------------------------------------------------------------------
ステータスバーを非表示したときはどうするの? と質問も出そうですね。その時には分割先メニューにショートカットキーを割り当てれば素早く操作できます。ダブルクリックはワンクリックじゃないぞ、とクレームが出そうですが、そう言う方はメニューから操作するか、ダブルクリックを練習してください。また、コントロールパネルからマウスの設定をすればダブルクリックの好みのタイミングに変えられます。


均等分割追加

均等分割突然ですが、ここで均等分割の機能を追加したいと思います。分割ファイルは最後のファイルだけはサイズが小さくなりがちです。均等分割とはできるだけ分割ファイルのサイズを同じにする機能です。それでも最後のファイルは若干他よりは小さくなりますが、その差は数バイトです。
私は既に寿分割を職場でも使っています。フロッピーディスクに分割する時には気にならないのですが、分割ファイルをメール添付で送る時には、相手側のメール受信サーバーの関係で、ひとつひとつの分割ファイルサイズをできるだけ小さくしたいことがあります(当然圧縮もしていますし、会社は専用線ですから送受信の時間や課金は気にならないのですが)。ここ「日曜日にBCB!」の中で完成していない寿分割に対して、早くも機能追加をするのはちょっと考えたのですが、それも一つの例だと思いますので、突然ですが機能追加をします。

均等分割は、メニューに「均等に分割する」をチェックとして追加し、メニューがチェックされた場合はListDivideFiles関数で、分割ファイルサイズの調整を行います。では、まずメニューの追加から行いましょう。

Caption Name 内容
「分割(D)」 DivideMNU 分割操作に関するメニュー。
  「1.44M(H)」 Size1440MNU 分割サイズを2HDフロッピーディスクの容量の1.44Mバイト、または2DDフロッピーディスクの容量の720Mバイトとする排他チェック。
  「720K(D)」 Size0770MNU
  「-」 SEP21 メニューを見やすくするセパレータ。
  「分割先(P)...」 PathMNU 分割先を変更。
  「均等に分割(E)」 EquallyMNU 均等に分割するかのチェック。
  「-」 SEP22 メニューを見やすくするセパレータ。
  「分割実行(D)」 GoDivideMNU 分割を実行。

続いてEquallyMNUに対するコールバックを追加します。ここでは均等分割チェックの反転、分割ファイルリストおよびメニューの更新を行います。
//---------------------------------------------------------------------------
void __fastcall TMainWND::EquallyMNUClick(TObject *Sender)
{
    EquallyMNU->Checked = !EquallyMNU->Checked;         // チェック反転
    ListDivideFiles(OpenDLG->FileName);                 // 分割ファイルリスト更新
    UpdateMenu();                                       // メニュー更新
}
//---------------------------------------------------------------------------
実際の分割ファイルサイズの調整はListDivideFilesに実装します。分割サイズは以下で計算されます。

分割指定 分割サイズ
デフォルト 分割サイズ = 1.44Mまたは720K
均等 分割数 = 分割元ファイルサイズ/デフォルト分割サイズ+1
分割サイズ = 分割元ファイルサイズ/分割数 の切り上げ

これをListDivideFilesに実装します。

改造ListDivideFiles関数
//---------------------------------------------------------------------------
bool __fastcall TMainWND::ListDivideFiles(AnsiString FilePath)
{
    :
// 分割数の計算(分割ファイル数、結合バッチファイルは除く)
    int NumCut   = FileSize/CutSize+1;
    if (EquallyMNU->Checked){           // 均等分割の場合
        CutSize = (int)(ceil((double)FileSize/(double)NumCut));   // 分割サイズを調整
    }

// 分割先ファイル一覧作成(分割ファイル名作成と分割サイズの設定)
    :
}
//---------------------------------------------------------------------------


ツールバー追加

さて、最後の「使いやすい」機能としてツールバーを追加します。やっとか、と思われた方もいらっしゃると思います。ツールバーは他の機能を追加した後で実装したほうが説明が簡単なためにここまで引き伸ばしました。実装自体は非常に簡単です。

ツールボタン作成

まずツールバーに追加するボタンをメニューから選定します。寿分割のツールバーは有効および無効でボタンイメージを変えます。BCB3ではカーソルが通過するとイメージを変えるホットイメージもサポートしますが、ここでは使いません。お好みによっては使ってください。ボタンイメージはビットマップ形式でサイズは16x16、色は16色標準パレットとしましょう。

メニュー 有効イメージ 無効イメージ コメント
ファイル/新規作成 新規作成有効イメージ 新規作成無効イメージ  
ファイル/開く... 開く有効イメージ 開く無効イメージ  
分割/分割実行 分割実行有効イメージ 分割実行無効イメージ  
分割/分割中断 分割中断有効イメージ 分割中断無効イメージ 無効イメージは実際には表示されません
分割/均等分割 均等分割有効イメージ 均等分割無効イメージ  

上記イメージは一例です。ボタンのイメージがわかるものならなんでもいいでしょう。決して格好良くはないですが、私なりにはまあまあでしょうか。イメージを作るのが面倒な方は、こちらから私が作ったものをダウンロードできます ダウンロード(1,796バイト)。ご自由にお使いください。これを新たにToolBarButtonIMGToolBarDisableIMGの2つのイメージリストを作成して、表の行順に登録します。
ここで、分割サイズはツールボタンを作っていません。分割サイズは将来増える可能性があります。ユーザーが分割サイズをカスタマイズしたいケースも出るでしょう。そこでツールボタンではなく、ブルダウンリストをツールバーに追加します。ワードのフォントリストの様な感じです。具体的な実装方法は後ほど述べます。
ツールバー設計

ツールバー設計

メイン画面にツールバーを追加設計しましょう。
ツールバーは画面上(メニューの下)にデフォルトで配置されますのでデフォルトのままとします。

続いてツールバー上にボタンを配置します。ボタンの並びはメニュー順とします。でないと、ユーザーが混乱してしまいますので。ツールバー上にボタンを追加していきます。区切りにはセパレータを追加します。「新規作成」、「開く」のボタンとセパレータを一つ追加したところでちょっとストップです。ここで分割サイズリストを追加しますが、ツールバーポップアップメニューではリストは追加できません。コンボボックスコントロールを直接ツールバー上に作成します。このコンボボックスは、
とします。
さらに「分割実行」と「均等分割」のボタンを追加します。分割中断は分割実行ボタンと入れ替えて使います。これは各ボタンのImageIndexを実行時コントロールします。分割サイズを除いて出来あがったボタンに名前を付け、さらにコールバック関数を登録します。

コントロール Name
(名前)
ImageIndex
(イメージ番号)
コールバック
新規作成有効イメージ NewBTN 0 NewMNUClick関数
開く有効イメージ LoadBTN 1 LoadMNUClick関数
分割サイズリスト SizeLST SizeMNUClick関数
分割実行有効イメージ DivideBTN 2 GoDivideMNUClick関数
均等分割有効イメージ EquallyBTN 4 EquallyMNUClick関数
ここには載っていない分割中断ボタンは分割中断メニュー同様に実行時にコントロールします。

分割実行中ツールバー

分割実行中はツールバーを変更し、分割中断ボタンを表示します。分割実行のコールバックに改造を加え、ツールボタンのコールバックと分割中断ボタンのImageIndexを変更します。

改造 GodivdeMNUClick関数
//---------------------------------------------------------------------------
void __fastcall TMainWND::GoDivideMNUClick(TObject *Sender)
{
    :
// 分割実行中画面設定
    GoDivideMNU->Caption = "分割中断(&D)";            // 分割実行→中断メニュー変更
    GoDivideMNU->OnClick = StopDivideMNUClick;        // 分割中断コールバック登録
    GoDivideBTN->ImageIndex = 3;                      // 分割実行→中断ボタン変更
    GoDivideBTN->OnClick = StopDivideMNUClick;        // 分割中断コールバック登録
    GoDivideBTN->Hint = "分割中断";                   // 分割中断ボタンヒント登録
    DividePRG->Position = 0;                          // 分割状況プログレスバー初期化
    DividePRG->Visible = true;                        // 分割状況プログレスバー表示
    StatusBAR->Visible = false;                       // ステータスバー非表示

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

分割リスト処理

分割リストのリストアイテムはデザイン時に設定できます。しかし、初期に選択されているデフォルトアイテムは実行時しか設定できません。そこでFormCreate関数内にデフォルトの分割サイズ設定を追加します。

改造FormCreate関数
//---------------------------------------------------------------------------
void __fastcall TMainWND::FormCreate(TObject *Sender)
{
    SizeMNUClick(Size1440MNU);        // 初期分割サイズ→1.44M
    DragAcceptFiles(Handle, true);    // ドロップ受け取り登録
    UpdateMenu();    // メニュー更新
}
//---------------------------------------------------------------------------

分割リストを押したときのコールバック関数はボタンのコールバックと同様に共通化しています。新規作成ボタン 新規作成、開くボタン 開く、分割実行 分割実行の各ボタンはコールバック関数でメニューが選択されたのか、ボタンが選択されたのかは関係ありません。しかし、分割サイズリスト 分割リストと均等分割 均等分割では処理が異なります。何が選択されたかはコールバック引数のTObjectクラスのポインタSenderを解読します。BCBにはdynamic_castと言う便利な演算子があります。これを使ってメニューが選択されたのか、コンボボックスが選択されたのかを区別して処理を行います。
分割サイズメニュー・リストの改造を行います。

改造SizeMNUClick関数
//---------------------------------------------------------------------------
void __fastcall TMainWND::SizeMNUClick(TObject *Sender)
{
    TMenuItem *MNU = dynamic_cast<TMenuItem*>(Sender);    // メニューコントロール取得
    if (MNU!=NULL){                  // メニューが選択された
        MNU->Checked = true;         // メニュー選択
        if (Size1440MNU->Checked) SizeLST->ItemIndex = 0;   // 「1.44M」メニュー選択→「1.44M」リスト選択
        if (Size0720MNU->Checked) SizeLST->ItemIndex = 1;   // 「720K」メニュー選択→「720K」リスト選択
    }
    TComboBox *LST = dynamic_cast<TComboBox*>(Sender);    // コンボボックスコントロール取得
    if (LST!=NULL){                 // リストが選択された
        if (LST->ItemIndex==0) Size1440MNU->Checked = true; // 「1.44M」リスト選択→「1.44M」メニュー選択
        if (LST->ItemIndex==1) Size0720MNU->Checked = true; // 「720K」リスト選択→「720」メニュー選択
    }
    ListDivideFiles(OpenDLG->FileName);    // 分割ファイルリスト更新
    UpdateMenu();    // メニュー更新
}
//---------------------------------------------------------------------------

均等分割メニュー・リストの改造を行います。

改造EquallyMNUClick関数
//---------------------------------------------------------------------------
void __fastcall TMainWND::EquallyMNUClick(TObject *Sender)
{
    TMenuItem *ITEM = dynamic_cast<TMenuItem*>(Sender);    // メニューコントロール取得
    if (ITEM!=NULL){                             // メニューが選択された
        ITEM->Checked = !ITEM->Checked;          // メニューチェック反転
        EquallyBTN->Down = ITEM->Checked;        // ボタンチェック反転
    }
    TToolButton *BTN = dynamic_cast<TToolButton*>(Sender);    // ボタンコントロール取得
    if (BTN!=NULL){                              // ボタンが選択された
        EquallyMNU->Checked = BTN->Down;         // メニューチェック←ボタンチェック
    }
    ListDivideFiles(OpenDLG->FileName);    // 分割ファイルリスト更新
    UpdateMenu();    // メニュー更新
}
//---------------------------------------------------------------------------
これでツールバー実装が終わりました。


ツールバー表示・非表示

さて、全ての機能追加が終わったと思ったのですが、ここでひとつ忘れていました。実際、プログラムを機能追加していくと、新たな機能がそれまでの機能と関連して、また新たな機能を追加しなければ行けないことが多々あります。ここではツールバーを追加しましたが、ステータスバー同様に表示・非表示の切替えを実装するのを忘れるところでした。

実装方法はステータスバー表示・非表示と同じです。まずメニューの追加から行います。

Caption Name 内容
「表示(V)」 ViewMNU 表示に関するメニュー。
  「ツールバー(B)」 ToolBarMNU ツールバーの表示を切り替える。チェックメニュー
  「ステータスバー(B)」 StatusBarMNU ステータスバーの表示を切り替える。チェックメニュー

続いてツールバー表示・非表示のコールバック関数を作成します。手順はステータスバー表示・非表示と全く同じで、ツールバーコントロールTToolBarの表示プロパティVisibleを「表示」「ツールバー」メニューのチェックCheckedと連動させるだけです。
//---------------------------------------------------------------------------
void __fastcall TMainWND::ToolBarMNUClick(TObject *Sender)
{
    TMenuItem *MNU = (TMenuItem*)Sender;    // メニューオブジェクト取得
    MNU->Checked = (!MNU->Checked);         // メニューチェックの反転
    ToolBAR->Visible = MNU->Checked;        // ツールバー表示切替え
}
//---------------------------------------------------------------------------
ステータスバー表示・非表示のコールバック関数と同じ処理ですので、共通化しても良いのですが、ツールバーとステータスバーは分割サイズ選択のコールバック関数のように同じ目的で作られたコントロールではありませんし、将来は機能が異なってくることも考えられるので、私はあえて同一処理とはしません。


これで寿分割は本来の目的である、

  1. 操作がシンプル。最短で分割元ファイルを開いて実行の2ステップ動作で完了するもの。
  2. 分割サイズが2HDおよび2DDディスケットから選択できるもの。
  3. 分割先が変更できるもの。
  4. 2ギガ以上の空き容量でも正しくチェックできるもの。
  5. 結合バッチファイルを自動生成するもの。
が完成しました。かつ強靭で安心できるものです。機能、使いやすさとも私が満足できるものとなりました。なんたって、自分で作ったソフトウェアですから可愛さ百倍です。世界で一つしかない自分好みのソフトウェアです。また、自分の好みで機能を追加できます。自分の意図した通りに動きます。ただし、自分の過ちや愚かさをそのまま映し出した鏡でもありますが。

第9話ビルド結果


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


さて、次回はちょっと本来の目的を外れて「オンラインソフトへの道」と題してお話したいと思います。「このソフトで一攫千金!!」ともくろんでいるあなた、オンラインソフトの世界はそんなに甘いものではおまへんで、と意見もしたいと思います。次回が第1部の最終回です。お楽しみに。

今回のプログラミング行数: 45行
今回までのプログラミング行数: 540行


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