Delphi Million Tips コンポーネント/ライブラリ



マウスカーソルを画面上から一時消去す。 [D2][D3]

スクリーンセーバーや何かのプレゼンテーションソフトを作るとき、 邪魔になるカーソルを一時消去したい時があります。 マウスカーソルを消す方法は多くの参考書には WindowAPI の ShowCursor を使って、 次のようにする方法が紹介されています。

    ShowCursor(True);   //マウスを表示する
    ShowCursor(False);  //マウスを消去する
  

たしかにこの方法で簡単に消えるのですが、この API には恐るべき(!?)欠点があります。

この ShowCursor という関数は、 True のとき内部的な表示カウント変数を 1 増加し、 False のとき内部の変数を 1 減少させるという働きをします。 そして表示カウント変数が 0 以上の時、マウスが表示されるという仕組みになっています。 ここで気をつけなければならないのは、 ShowCursor(False) を 2 回呼び出したあとに ShowCursor(True) を 1 回呼んだだけではマウスカーソルが表示されないと言うことです。

再起動するまでマウスカーソルが画面から見えなくなってしまいます。 それなので、この方法はあまりおすすめしません。

実は Delphi の TScreen オブジェクトの Cursor プロパティでもカーソルを消すことができるので、 こっちの方を使いましょう(この方法は Delphi2 のヘルプに出てません)。

    Screen.Cursor := crNone;
  

とするだけでマウスカーソルが消えてくれます。

元に戻すときは、

    Screen.Cursor := crDefault;
  

としてやれば OK です。Delphi3 でももちろん使えます。


TreeView の項目を太字で表示する。 [D3]

コモンコントロールのマクロ、TreeView_SetItem を使い、 TTreeView の TreeNode を個別に太字表示することができます。 これを応用して、エクスプローラで「切り取り」したときのような、 半透明状態にすることもできます。

以下は TreeView の選択されたノードをボールドにする例です。

    
  uses CommCtrl;    //CommCtrl を追加

  procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode);
  var
    Item: TTVItem;
  begin
    if TreeView1.Selected = nil then Exit;
    with Item do begin
      mask := TVIF_STATE;
      hItem := TreeView1.Selected.ItemId;
      stateMask := TVIS_BOLD;
      state := stateMask and TVIS_BOLD;
    end;
    TreeView_SetItem(TreeView1.Handle, Item);
  end;

  procedure TForm1.TreeView1Changing(Sender: TObject; Node: TTreeNode;
    var AllowChange: Boolean);
  var
    Item: TTVItem;
  begin
    if TreeView1.Selected = nil then Exit;
    with Item do begin
      mask := TVIF_STATE;
      hItem := TreeView1.Selected.ItemId;
      stateMask := TVIS_BOLD;
      state := stateMask and 0;
    end;
    TreeView_SetItem(TreeView1.Handle, Item);
  end;
    
  

TCheckListBox のメモリリークの不具合を回避する。 [D3]

Delphi 3 に標準で附属の TCheckListBox にはメモリの解放漏れを起こすバグがあります。

TCheckListBox では Checked の状態を保持するために Items.Objects を利用します。 ですが、単純にチェック状態を Items.Objects に保持してしまうと、 AddObject や InsertObject で ポインタやオブジェクトを使うことができなくなってしまいます。 これを回避するために、TCheckListBox では TCheckListBoxDataWrapper というクラスを内部で定義して使っています。 TCheckListBoxDataWrapper はチェック状態とオブジェクトのポインタを保持し、 この TCheckListBoxDataWrapper オブジェクトが Items.Objects に保持されている仕組みです。

TCheckListBox は Items.Objects に入っている TCheckListBoxDataWrapper を破棄するのに、 DestroyWnd メソッドを override して、その中で破棄を行なっています。

しかし、DestroyWnd はコンポーネントをフォームに貼りつけたまま アプリケーションを終了させるときには呼び出されません。 DestroyWnd が呼び出されるのは RecreateWnd が呼ばれたときと、 親によって RemoveControl されたときだけです。 なぜこのような仕様になっているのかは分かりませんが、このために TCheckListBoxDataWrapper クラスのインスタンスが破棄されずにメモリリークを起こします。

この問題は単純ではないので TCheckListBox のカスタムコンポを作っても回避できません。

ソースファイルがあれば改造して何とかなりそうですが、 Destroy 時にはすでに ListBox コントロールのハンドルが破棄されているため、 Items プロパティにアクセス不可能です。 また WMDestroy のメッセージハンドラを書いて、その中で廃棄すればうまくいくように見えますが、 WMDestroy → Destroy の順番で破棄が行なわれるとは限りません

フォームに貼りつけたまま終了するときは前述の順番で破棄処理が行なわれますが、 CheckListBox1.Free のように直接 Free メソッドを呼び出された場合は、 Destroy → WMDestroy の順でメソッドが呼ばれます。 このため WMDestroy が呼ばれる時点でハンドルが破棄されているケースもありうるのです。 そのため WMDestroy メッセージハンドラでの破棄はエラーが発生するケースがあります。

これを回避するにはコンポーネントのソースを直接改造して、 単にフラグで回避すればいいのですが、あまりおすすめしたくないですね。

ただ、CheckListBox の Clear を呼べば、最終的に Items.Objects の TCheckListBoxDataWrapper を破棄してくれるようになっているので、 Form の OnDestroy イベントで(面倒ですが)Clear メソッドを呼んでやれば、 メモリリークを回避することが可能です。

    
  procedure TForm1.FormDestroy(Sender: TObject);
  begin
    CheckListBox1.Clear;   // ここでフォームに載っている TCheckListBox を Clear してやる。
    CheckListBox2.Clear;
  end;
    
  

※ 詳しくは確認していませんが、Delphi4 ではこの不具合は改善されたようです。


TEdit で入力された文字をすべて大文字/小文字にしたい。 [D2][D3][D4][D5]

TEdit に入力された文字を、すべて大文字、または、すべて小文字にしたいのですが、 どうすればよいのでしょうか。Visual Basic のテキストボックスのように、 OnKeyDown イベントや OnChange イベントにコードを書く必要があるのでしょうか。

実は TCustomEdit には CharCase というプロパティがあり、TEdit はこれを published にしています。 このプロパティを ecUpperCase に設定すれば入力される文字は常に大文字、 ecLowerCase にすれば常に小文字となります。 意外とこれを知らない人が多いので書いてみました。


Delphi4 の IDE ようなメニューバーを実現する。 [D4][D5]

Delphi4 ではフローティングツールバーが VCL でサポートされましたが、 Delphi4 の IDE のようにメインメニューもコントロールバー上に載せるにはどうしたらよいでしょうか。

実は TMenuBar というコンポーネントが 米 Borland のサイトからダウンロード できます。これを使うだけですが、それだけではナンなので(^^;)、使い方を簡単に書きます。 メニューバー使用例

  1. フォーム (Form1 とします)上に TControlBar を配置します。
  2. TMenuBar を TControlBar 上に置きます。(MenuBar1 とします)
  3. MenuBar1 の Menu プロパティに MainMenu(MainMenu1とします) を指定します。
  4. Form の Menu プロパティから MainMenu1 を外します。
  5. Form の OnShortCut イベントに次のように記述します。
    
  procedure TForm1.FormShortCut(var Msg: TWMKey;
    var Handled: Boolean);
  begin
    Handled := Handled or MainMenu1.IsShortCut(Msg);
  end;
    
  

OnShortCut にこの文を書かないと、メニューのショートカットが働かなくなります。

以上のことをするだけで、Delphi4 のようなメニューバーが実現できます。 当然 DragKind = dkDock, DragMode = dmAutomatic にしておけばフローティングメニューバーにもなります。

右の写真は、拙作の TCuteControlBar を使って NetscapeCommunicator 風にした例です。


Valid XHTML 1.1!