top pageへ.

Programming Room.

C++Builder Tips.

Tiny Tips.

 人によっては取るに足らないような小ネタ集。
 この程度のことなら、C++Builderについて書かれた書籍や、他のサイトにも解説されていたりするものも多いと思います。
 とりあえず気付いた物を並べてみました。


おしながき

min/maxマクロ.

現象.

 標準Cライブラリにあるはずのmin/maxマクロですが、いざ使ってみようと思うと、「マクロが見つからない」といってコンパイラに怒られた経験があるのではないかと思います。ヘルプにもstdlib.hに含まれていると記述されているのにねー。
 で、stdlib.hを開いてみると、複数の定義が見つかります。公開しちゃっていいのかどうかわからないので、ここではstdlib.hの該当コードを載せたりしませんが、C++のテンプレートで書かれたものと、ANSI Cでマクロで書かれたものが見つかると思います。で、それらの前後が複数の #if で囲まれていて、英文のコメントを見ると、MFCがどーたらこーたら。何か怪しげ。

対策.

 めんどくさいし、stdlib.hに問題があるとしてもコンパイラに付属のヘッダを触るのも気分的にアレなので、自分で定義しちゃいましょう。
 コードは以下のとおりです。適当なヘッダにでもつっこんでください。もちろん自分で書いたヘッダにですよ。

#define        max(a, b)    (((a) >= (b))? (a): (b))
#define        min(a, b)    (((a) <= (b))? (a): (b))

FindFirstとFindCloseのコール.

現象.

 特定のフォルダ内のファイルを列挙するには、関数FindFirst/FindNext/FindCloseを使います。ヘルプを読む限り、基本的には、

  1. FindFirstで検索条件を指定して、最初のファイルを取得。
  2. FindNextを繰り返しコールして2番目以降のファイルを取得。
  3. 取得が完了したらFindCloseをコール。

という使い方だとわかります。これに対して一般的には、以下のようなコードを想像されるのではないかと思います。

    if (FindFirst(path, attribute, searchrec) == 0) {
        do {
            ファイルごとの処理を記述
        } while (FindNext(searchrec) == 0);
    }
    FindClose(searchrec);

 このコードは、問題なく動くこともありますが、動作不良を起こすこともあります。特に、初めのうちは動いていたのに、コードを触っているうちに原因不明の動作不良を起こすことがあります。FindFirst〜FindNext〜FindCloseと関係ないところを触って動作不良に陥ることもあります。

対策.

 結論からいうと、上記のコードの問題点は、FindFirstの結果に関係なく、FindCloseをコールしていることです。つまりFindCloseをコールしていいのは、FindFirstが成功した時のみなのです。この点はC++Builderのヘルプにも書かれていません。
 この点に対処した、ただしコードは以下のようになります。

    if (FindFirst(path, attribute, searchrec) == 0) {
        do {
            ファイルごとの処理を記述
        } while (FindNext(searchrec) == 0);
        FindClose(searchrec);       // ←ここがifのブロックの中に入っただけ
    }

 ヘルプのFindFirstの項目の「例」を見ると、確かに下の例と同等のコードが書かれています。しかし「FindCloseをコールしていいのは、FindFirstが成功した時のみ」なんてどこにも説明がありません。明記してくれないとこんなこと気付かないよね(T_T)

付記.

 ちなみに、ルートディレクトリ以外のフォルダをFindFirst/FindNextで検索した場合必ず、そのフォルダ自身を表す"."と、親フォルダを表す".."が最初に検出されます。大抵のプログラムではこれらは邪魔なだけなので、これら"."と".."を除外するコードが、上記doループ内に必要になります。


簡単に使えるダイアログ.

要求.

 何らかのツールを作っていると、ちょっとしたことでダイアログを表示したいことがよくあります。ダイアログパレットに用意されているものに使えそうなものがない場合、フォームエディタで自作するのが普通です。しかしそれほど大げさなものを必要としないこともよくあります。例えば以下のような場合。

対応.

そんなときのために、フォームを作らなくてもコードの記述だけで使えるダイアログが、Dialogsユニットに用意されています。
 特に簡単に扱えるものだけ、それらの特徴をまとめてみました。各々の詳細は、ヘルプでDialogsユニットを参照してください。

ダイアログ名 特徴 キャプション ボタン
ShowMessage 引数がメッセージ1つだけなので、最も簡単に使える。 実行ファイル名 OKのみ
MessageDlg キャプション、メッセージ、メッセージ横のアイコン、ボタンを指定可能。 ユーザ指定 11種の組み合わせ
InputBox キャプション、メッセージ、デフォルト値を指定可能。 ユーザ指定 OK、キャンセル
InputQuery キャプション、メッセージ、デフォルト値を指定可能。
戻り値でOKが押されたか否かを得られる。
ユーザ指定 OK、キャンセル


 Dialogsユニットではないのですが、TApplication::MessageBoxも同様に使用できます。

ダイアログ名 特徴 キャプション ボタン
TApplication::MessageBox キャプション、メッセージ、ボタンを指定可能。 ユーザ指定 6種の組み合わせ

iniファイルインターフェイスの活用.

常識.

 ちょっとC++Builderを使えば、iniファイルにアクセスする複数のクラスの存在を知ることになります。IniFilesユニットに属するもののうち、iniファイルを直接扱うクラスは以下の3つ。

クラス名特徴
TCustomIniFilesTIniFile/TMemIniFileの基底クラス。
TIniFilesiniファイルを扱う基本的なクラス。
TMemIniFileiniファイルの内容をメモリ上にバッファして読み書きする。大量のiniファイルアクセスを高速に行える。

活用.

 これらのクラスのコンストラクタは引数にファイル名をとります。ファイル名をフルパスで指定すれば、Windowsフォルダやアプリケーションを格納したフォルダ以外の、任意のフォルダにiniファイルを作ることが出来ます。当然、".ini"以外の拡張子を指定して使うことも可能。
 つまり、iniファイルと同じ書式・機能で十分であれば、iniファイル以外のファイルアクセスもできます。テキストファイルなので開発中のデバッグも簡単ですし、細かなフォーマットやエラー処理を自分で処理する必要もないので、開発が楽になります。

 CopyAllの手順ファイル(*.cap)や、同期モードの作る"CopyAll.casi"などで、このような使い方をしています。

類似.

 フォーマットは違いますが、フォームファイル(*.dfm)のインターフェイスであるTFiler/TWriter/TReaderも同様に扱うことが出来ると思います。試したことはありませんが。
 でもTFiler/TWriter/TReaderはバージョンによって動作が変わっているようです。事実C++Builder5と6では、ANSI以外のキャラクタコードを持つ文字のエンコード方法が変っていて、C++Builder6で生成したファイルはC++Builder5では読めません。その逆は当然出来るようですが。

注意.

 IniFilesユニットを使うときは、明示的に IniFiles.hpp をインクルードする必要があるようです。


TTrackBarの挙動.

OnChangeイベント.

 C++BuilderのヘルプではTTrackBarのOnChangeイベントは、Positionプロパティの値が変化したときに発生すると説明されています。しかし実際に動作させてみると、Positionプロパティの値が変化しなくても、ユーザ操作により発生するようです。
 OnChangeイベントが発生する条件と、その時のPositionプロパティの値を調べてみた結果は以下のとおりでした。

操作対象 操作内容 イベント発生回数 Positionプロパティの値
マウスによるドラッグ スライダ上で左ボタン押下 1 ドラッグ開始時のスライダ位置
左ボタン押下したままスライダをドラッグ 1 ドラッグ中のスライダ位置
左ボタンを離す 2 ドラッグ終了時のスライダ位置
マウスでバーをクリック バー上で左ボタン押下 1 新しいスライダ位置
そのまま左ボタンを押し続ける
(押し続けている間、一定時間間隔でスライダが移動)
1 新しいスライダ位置
左ボタンを離す 1 新しいスライダ位置
カーソルキー キー押下 1 新しいスライダ位置
キーリピート 1 新しいスライダ位置
キーを離す 1 新しいスライダ位置
PageUp/PageDownキー キ−押下 1 新しいスライダ位置
キーリピート 1 新しいスライダ位置
キーを離す 1 新しいスライダ位置


 つまり、スライダを移動させなくても、スライダを左クリックしただけで、計3回OnChangeイベントが発生します。スライダを動かさないのですから、この間は当然Positionプロパティの値が変化することはありません。
 またバーを短くクリックしても2回OnChangeイベントが発生しますが、この2回でPositionプロパティの値は同じです。キー操作の場合も同様ですが、キー操作の場合はさらに上限・下限を超える操作を行った場合にもイベント発生します。

 OnChangeイベントハンドラ内で時間のかかる処理が行われる場合、アプリケーションユーザに不利益になる場合があります。アプリケーションはPositionプロパティの値に従って処理を行うでしょうから、意味なく同じ処理を繰り返すことになり、その分アプリケーションユーザは待たされることになります。

 解決するには、イベントハンドラ内でPositionプロパティの値の変化を監視するしかないと思います。


▲page top.
Copyright 2005-2016, yosshie.