Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2010年01月05日] [更新 2017年03月14日] Delphi サンプルプログラム集
127_フォームとシステムメニュー操作関係
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
127_FormSystemMenuETC.zip [7,856 KB] 2017年03月14日版 (EXE 同梱)



  • 2012年01月14日
  • Delphi XE で EXE を再作成。コード整形と一部のサンプルにコード追加
  • 2017年03月14日
  • フォームを使用しない、フォームを非表示と、フォームの移動リサイズ関係のサンプルを、新規に作成したページに移動




フォームとシステムメニュー操作関係


フォームのシステムメニューの削除や追加変更等と、それと関係しているかも知れないフォームの操作関係の参考コードです。


[備考]
フォームの [最小化] [最大化] は、フォームのサイズが変わるわけですから、フォームのリサイズ関係の制御と言えます。





01_フォームのシステムメニューを削除


フォームの [最小化] [最大化] [閉じる] ボタンは、プロパティ BorderIcons で無効にできますが、このサンプルは GetSystemMenu 関数と DeleteMenu 関数を使用して、フォームのシステムメニューを削除して非表示にし、その機能を使用できないようにします。ただし、[閉じる] ボタンは非表示にはできません。


図1
設計時画面
図2
実行開始後のメニュー
  • これがデフォルトのメニュー
図3
Button1 クリック後のメニュー
  • [元のサイズに戻す] が削除されている
図4
Button2 クリック後のメニュー
  • [サイズ変更] が削除されている
  • フォームのサイズが変更不可となる
図5
Button3 クリック後のメニュー
  • [閉じる]が削除されている
  • ただし [×] ボタンは無効になるだけ
    非表示にはできない
  • 閉じるには [Alt] + [F4] で
図6
タスクバーのメニューも同じ
  • 順番に Button クリックを実行した場合

BorderIcons プロパティの biSystemMenu を False にすると、システムの閉じるボタン [X] を非表示にできますが、下図のように、アイコンも表示しなくなります。また、最大化、最小化のボタン等も表示しなくなります。


図7
BorderIcons に biSystemMenu がない場合

リスト1
フォームのシステムメニューの削除
unit FormSystemMenuDeleteUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  上から1番目のシステムメニューを削除
//  メニュー項目の位置( MF_BYPOSITION)ではなく、メニュー名で指定する場合は
//  DeleteMenu(hSysMenu, SC_MOVE, MF_BYCOMMAND);
//  のようにする(Button3Clickイベントのコード参照)
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  hSysmenu : hMenu;
begin
  //フォームのシステムメニューのハンドルを取得
  hSysmenu := GetSystemMenu(Handle, False);
  //上から1番目のメニューを削除
  DeleteMenu(hSysMenu, 0, MF_BYPOSITION);
end;

//=============================================================================
//  上から2番目のシステムメニューを削除
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  hSysmenu : hMenu;
begin
  //フォームのシステムメニューのハンドルを取得
  hSysmenu := GetSystemMenu(Handle, False);
  //上から2番目のメニューを削除
  DeleteMenu(hSysMenu, 1, MF_BYPOSITION);
end;

//=============================================================================
//  SC_CLOSE(閉じる)メニューを削除
//
//  参考
//  [閉じるボタン]は削除できない。非表示にもできない
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  hSysmenu : hMenu;
  AItemCnt : Integer;
begin
  //フォームのシステムメニューのハンドルを取得
  hSysmenu := GetSystemMenu(Handle, False);
  //閉じるメニューを無効化
  DeleteMenu(hSysMenu, SC_CLOSE, MF_BYCOMMAND);

  //現在のメニューの項目数を取得して一番下の区切り線を削除
  AItemCnt := GetMenuItemCount(hSysmenu);
  DeleteMenu(hSysmenu, AItemCnt - 1, MF_BYPOSITION);
end;

end.




02_フォームのシステムメニューを追加


このサンプルは GetSystemMenu 関数と InsertMenuItem 関数を使用して、フォームのシステムメニューを追加します。


図8
設計時画面
図9
実行開始後のメニュー
  • これがデフォルトのメニュー
図10
ボタンクリック後のメニュー
  • [追加メニュ] (上から2番目)
    [テストメニュー] (下から2番目)
    [テスト項目] (一番下)
    が追加されている
  • [テストメニュー] はメニュー表示開始時に淡色に設定している
図11
タスクバーも同じメニュー
図12
[テスト項目] をクリック

リスト2
フォームのシステムメニューの追加
unit FormSystemMenuInsertUnit;

interface

uses
  {$IF CompilerVersion > 22.99}
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, 
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, 
  Vcl.StdCtrls;
  {$ELSE}
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
  {$IFEND}

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  protected
    procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND;
    procedure WMInitMenuPopup(var Message: TWMInitMenuPopup);
      message WM_INITMENUPOPUP;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

// 追加メニューのID値
// 値は任意の整数値
const
  MyMenu1 = 101;
  MyMenu2 = 102;
  MyMenu3 = 103;

{$R *.dfm}

//=============================================================================
//  システムメニューを追加
//  InsertMenu関数の第3引数にはいろいろ値がある
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  hSysmenu : hMenu;
  AItemCnt : Integer;
begin
  //フォームのシステムメニューのハンドルを取得
  hSysmenu := GetSystemMenu(Handle, False);

  //現在の項目数を取得
  AItemCnt := GetMenuItemCount(hSysmenu);

  //現在のメニュー一番下に区切り線(第4,5引数を0にする)を追加
  InsertMenu(hSysmenu, AItemCnt, MF_BYPOSITION, 0, nil);
  //現在のメニューの一番下にメニューを追加
  InsertMenu(hSysmenu, AItemCnt + 1, MF_BYPOSITION, MyMenu1, 'テストメニュー');
  //現在のメニューの一番下にメニューを追加
  InsertMenu(hSysmenu, AItemCnt + 2, MF_BYPOSITION, MyMenu2, 'テスト項目');

  //[移動]メニュー(SC_MOVE)の前に[追加メニュー]を追加
  //操作不可にして淡色表示に
  InsertMenu(hSysmenu, SC_MOVE, MF_BYCOMMAND or
                                MF_DISABLED  or

                                MF_GRAYED, MyMenu3, '追加メニュー');
end;

//=============================================================================
//  メニューを選択した時のメッセージ処理
//=============================================================================
procedure TForm1.WMSysCommand(var Message: TWMSysCommand);
begin
  case Message.CmdType of
    MyMenu1 : Showmessage('テストメニューです。 ');
    MyMenu2 : Showmessage('テスト項目です。 ');
  end;

  inherited;
end;

//=============================================================================
//  メニューを表示する時のメッセージ処理
//  追加したMyMenu1をグレイ表示にして使用不可とする
//=============================================================================
procedure TForm1.WMInitMenuPopup(var Message: TWMInitMenuPopup);
begin
   inherited;

   if Message.SystemMenu then
   begin
     EnableMenuItem(Message.MenuPopup, MyMenu1, MF_BYCOMMAND or MF_GRAYED);
   end;
end;

end.




03_キャプションバー ( タイトルバー ) のマウスイベント


キャプションバーをマウスでクリックした時に、何かを処理したい場合の参考です。このサンプルでは、押下したマウスボタンによってフォームの色が変化するようにしています。
キャプションバー (タイトルバー) 等の非クライアント領域のメッセージには WM_NC 系のメッセージが利用できます。

キャプションバーの最小化や最大化等のアイコン類のクリックを検出する方法は、後述の [08_アプリのアイコン・最小化・最大化・閉じるボタンのメッセージ] を参考にしてください。



図13
設計時画面
図14
マウスホイールをクリック

リスト3
キャプションバー (タイトルバー) のマウスイベント
unit CaptionBarMouseEventUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  protected
    procedure WMNCLButtonDown(var Message: TWMNCLButtonDown);
      message WM_NCLBUTTONDOWN;
    procedure WMNCRButtonDown(var Message: TWMNCRButtonDown);
      message WM_NCRBUTTONDOWN;
    procedure WMNCLButtonDblClk(var Message: TWMNCLButtonDblClk);
      message WM_NCLBUTTONDBLCLK;
    procedure WMNCMButtonDown(var Message: TWMNCMButtonDown);
      message WM_NCMBUTTONDOWN;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  [終了]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Close;
end;

//=============================================================================
//  マウス左ボタンダウン
//  WM_NCLBUTTONDOWNメッセージ
//=============================================================================
procedure TForm1.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
  Self.Color := clRed;

  //元の動作を実行
  //これを削除すれば動作無効
  inherited;
end;

//=============================================================================
//  マウス右ボタンダウン
//  WM_NCRBUTTONDOWNメッセージ
//=============================================================================
procedure TForm1.WMNCRButtonDown(var Message: TWMNCRButtonDown);
begin
  Self.Color := clGreen;

  //元の動作を実行
  //これを削除すれば動作無効
  inherited;
end;

//=============================================================================
//  マウス左ボタンダブルクリック
//  inheritedを削除して元の動作(最大化と元のサイズに戻すを無効に
//  WM_NCLBUTTONDBLCLKメッセージ
//=============================================================================
procedure TForm1.WMNCLButtonDblClk(var Message: TWMNCLButtonDblClk);
begin
  Self.Color := clBlue;
end;

//=============================================================================
//  マウス中央(ホイール)ボタンダウン
//  WM_NCMBUTTONDOWNメッセージ
//=============================================================================
procedure TForm1.WMNCMButtonDown(var Message: TWMNCMButtonDown);
begin
  Self.Color := clYellow;

  //元の動作を実行
  //これを削除すれば動作無効
  inherited;
end;

end.




04_フォーム表示終了直後の処理


フォームの表示が終了した (画面に見えた) 時点で何かを処理したい場合の参考コードです。ここでは単にダイアログを表示するだけとなっています。


図15
設計時画面
図16
実行開始後
  • OnShow 内で表示しているが。フォームは表示されていない
図17
フォーム表示終了後のダイアログ
  • 後ろに見えているのがフォーム

リスト4
フォーム表示終了直後の処理
unit FormShowingChangedUnit;

interface

uses
  {$IF CompilerVersion > 22.99}
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, 
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, 
  Vcl.StdCtrls, Vcl.ExtCtrls;
  {$ELSE}
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;
  {$IFEND}

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
  protected
    procedure CMShowingchanged(var Message: TMessage);
      message CM_SHOWINGCHANGED;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  [終了]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Close;
end;

//=============================================================================
//  フォーム表示後のメッセージ
//
//  CM_SHOWINGCHANGEDメッセージを処理する
//  CM_SHOWINGCHANGEDは表示の状態が変化した時に発生する
//  非表示になる時も発生するので、Visibleプロパティで判断する
//=============================================================================
procedure TForm1.CMShowingchanged(var Message: TMessage);
begin
  //元のメッセージを処理
  inherited;

  if Visible then begin
    Update;
    //表示後の処理
    ShowMessage('フォーム表示終了しました。 ');
  end;
end;

//=============================================================================
//  フォーム表示中のイベント
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  ShowMessage('フォーム表示中です。 ');
end;

end.




05_フォームの背景 ( TCanvas ) をグラデーション描画


GraphUtil ユニットには追加のグラフィック処理関係の関数類があります。これを利用して、フォームの背景をグラデーションで塗り潰す例です。ついでに半透明にしてみました。半透明はフォームのプロパティ (このサンプルではオブジェクト・インスペクタ) で設定します。
Windows API に GradientFill という似た名前の関数がありますが、こちらは長方形と三角形の塗りつぶし用の関数です。


図18
設計時画面
図19
実行結果

リスト5
グラデーションフォーム
unit GradientFormUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormPaint(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses GraphUtil;

{$R *.dfm}

//=============================================================================
//  [終了]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Close;
end;

//=============================================================================
//  フォームの背景をグラデーションに
//  フォームのCanvasに描画したものはOnPaintに描画しないと他のウィンドウに隠され
//  た際に消去されてしまう
//  そこで、このサンプルでは、OnPaintで処理して、常に描画が行われるようにして
//  いる
//
//  usesにGraphUtilが必要
//  AEndColorをclGradientInactiveCaptionにするとデフォルトのグラデーション配色,
//=============================================================================
procedure TForm1.FormPaint(Sender: TObject);
var
  Rect: TRect;
begin
 Rect := Self.ClientRect;
 GradientFillCanvas(Canvas, clWhite, clBlue, Rect, gdVertical);
end;

end.




06_フォームが閉じる時のイベントとメッセージ


フォームあるいはアプリケーションを終了する時に、どんなイベントやメッセージを通過するかを調査するためのテストプログラムです。終了するとプロジェクトフォルダ内の Close.txt にその記録を保存します。

フォームを閉じる時のイベントには、OnCloseQuery と OnClose がありますが、OnCloseQuery の方が早く発生します。この OnCloseQuery イベントの CanClose 引数の値を False にするとフォームを閉じなくすることができます。False のままだと、強制終了しない限り永遠に閉じませんから、何かしらの条件で CanClose を True にするようにします。



[備考]
本サンプルでは、タスクマネージャ等で強制終了された場合のメッセージは受信できません。
WMI 関係のイベントを利用すると、タスクマネージャー等での強制終了も検出できます。



図20
設計時画面
図21
フォームを閉じると Close.txt ができる
図22
[Alt] + [F4] で終了した場合

リスト6
フォームを閉じた時のイベントとメッセージを調査
unit FormColseMessageUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, AppEvnts;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    ApplicationEvents1: TApplicationEvents;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure ApplicationEvents1Activate(Sender: TObject);
    procedure ApplicationEvents1Deactivate(Sender: TObject);
    procedure ApplicationEvents1ShortCut(var Msg: TWMKey; var Handled: Boolean);
  private
    { Private 宣言 }
    SL : TStringList;
  protected
    procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND;
    procedure WMQueryEndSession(var Message: TWMQueryEndSession);
      message WM_QUERYENDSESSION;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  TStringList作成
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
   SL := TStringList.Create;
end;

//=============================================================================
//  FormのOnCloseQuery
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  SL.Add('OnCloseQuery');
end;

//=============================================================================
//  FormのOnClose
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  SL.Add('OnClose');
end;

//=============================================================================
//  記録内容をファイルに保存
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  SL.Add('OnDestroy');
  SL.SaveToFile('Close.txt');
  FreeAndNil(SL);
end;

//=============================================================================
//  [Close]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Close;
end;

//=============================================================================
//  [WM_CLOSE]ボタン
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
   PostMessage(Handle, WM_CLOSE, 0, 0);
end;

//=============================================================================
//  [Terminate]ボタン
//  OnDestroyしか発生しない
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
  Application.Terminate;
end;

//=============================================================================
//  TApplicationEvetnコンポのOnActivate
//=============================================================================
procedure TForm1.ApplicationEvents1Activate(Sender: TObject);
begin
  SL.Add('TApplicationEvent OnActivate');
end;

//=============================================================================
//  TApplicationEvetnコンポのOnDeactivate
//=============================================================================
procedure TForm1.ApplicationEvents1Deactivate(Sender: TObject);
begin
  SL.Add('TApplicationEvent OnDeactivate');
end;

//=============================================================================
//  TApplicationEvetnコンポのOnShortCut
//
//  ショートカット動作の検出
//  ここでは[Alt]+[F4]だけを調べる
//=============================================================================
procedure TForm1.ApplicationEvents1ShortCut(var Msg: TWMKey;
  var Handled: Boolean);
var
     SS : TShiftState;
Begin
  SS := KeyDataToShiftState(Msg.KeyData);
  if (Msg.CharCode=VK_F4) and (ssAlt in SS) then begin
    SL.Add('[Alt]+[F4]');
  end;
end;

//=============================================================================
//  WM_SYSCOMMANDメッセージ
//  [Close]ボタンで終了した時は発生しない
//=============================================================================
procedure TForm1.WMSysCommand(var Message: TWMSysCommand);
var
  AType : Integer;
begin
   AType :=Message.CmdType and $FFF0;

   if AType = SC_CLOSE then begin
     SL.Add('SC_CLOSE');
   end else
   if AType = SC_MOUSEMENU then begin
     SL.Add('SC_MOUSEMENU');
   end else
   if AType = SC_KEYMENU then begin
     SL.Add('SC_KEYMENU');
   end;

   inherited;
end;

//=============================================================================
//  WM_QUERYENDSESSIONメッセージ
//  Windowsを終了する時に発生するメッセージ
//=============================================================================
procedure TForm1.WMQueryEndSession(var Message: TWMQueryEndSession);
begin
  SL.Add('WM_QUERYENDSESSION');

  //終了させる
  Message.Result := 1;
end;

end.




07_[ Alt ] + [ F4 ] で閉じないフォーム


フォーム、ウインドウはシステムメニューにあるように、[Alt] + [F4] で閉じることができます。これを禁止するコードです。単にメッセージを処理するだけですが、メッセージを検出したら、このメッセージを送らないような値なりプロパティをセットします。
このサンプルは TApplication の OnMessage イベントでメッセージ処理を行う例です。


図23
設計時画面

リスト7
[Alt] + [F4] で閉じないウィンドウ
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, AppEvnts;

type
  TForm1 = class(TForm)
    ApplicationEvents1: TApplicationEvents;
    procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  [Alt]+[F4]でフォームを閉じるのを禁止
//  TApplicationのOnMessageでメッセージ処理する場合
//  Handled := Trueにするとメッセージ送信しないことを利用
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
  if  (Msg.message = WM_SYSKEYDOWN) then begin
    if (Msg.wParam = VK_F4) and (GetKeyState(VK_MENU) < 0) then begin
      Handled := True;
    end;
  end;
end;

end.


メッセージは、いろいろな方法で捕捉することができます。下のコードは WM_SYSKEYDOWN メッセージで [Alt} + [F4] を検出して、フォームが閉じるのを禁止します。
検出したキーボードのメッセージ データを KeyDataToShiftState 関数で TShiftState 型に変換して [Shift] または [Ctrl] キーの場合は通常のメッセージ処理とします。


リスト8
[Alt] + [F4] で閉じないウィンドウ
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
  private
    { Private 宣言 }
    procedure WMSysKeyDown(var Msg : TWMSysKeyDown); Message WM_SYSKEYDOWN;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  [Alt]+[F4]でフォームを閉じるのを禁止
//  WM_SYSKEYDOWNを使用した例
//=============================================================================
procedure TForm1.WMSysKeyDown(var Msg: TWMSysKeyDown);
begin
  //Alt+F4が押された場合
  if (Msg.CharCode = VK_F4) then begin
    //[Shift]と[Ctril]キーが同時に押されている場合は通常(inherited)動作とする
    if (ssShift in KeyDataToShiftState(Msg.KeyData)) or
       (ssCtrl in KeyDataToShiftState(Msg.KeyData)) then begin
      inherited
    end;
  end else begin
    inherited;
  end;
end;

end.




08_フォームのアイコン・最小化・最大化・閉じるボタンのメッセージ


システムメニューのアイコン (アプリケーションアイコン)、最小化ボタン、最大化ボタン、閉じるボタン (× ボタン) クリックの検出サンプルです。
これらのボタンをクリックした時の専用のイベントはありません。WindowProc や WndProc 等のメソッドあるいは TApplicationEvents の OnMessage イベント等を使用します。
ここでは非クライアント領域のメッセージである WM_NC 系のメッセージで捕捉します。

本サンプルでは WM_NCLBUTTONDOWN メッセージを使用していますから、実際にはマウスの左ボタンを押した時を検出しています。マウスの右ボタンを押した時を検出するには WM_NCRBUTTONDOWNメッセージを使用します。



図24
設計時画面
図25
実行して閉じるボタン (×ボタン) クリック
図26
最小化ボタンをクリック
図27
システムメニューのアイコン (アプリケーションアイコン) をクリック

リスト9
システムボタンのメッセージ処理テスト
unit Unit1;

interface

uses
  {$IF CompilerVersion > 22.99}
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, 
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, 
  Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.AppEvnts;
  {$ELSE}
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, AppEvnts;
  {$IFEND}

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  protected
    procedure WMNCLButtonDown(var Message: TWMNCLButtonDown);
      message WM_NCLBUTTONDOWN;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  [Close]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Close;
end;

//=============================================================================
//  WM_NCLBUTTONDOWNメッセージの処理
//  WM_NCLBUTTONDOWNは非クライアント領域でのマウスの左ボタンダウンメッセージ
//  マウスのボタンダウンの検出はWM_NCRBUTTONDOWNメッセージを使用する
//=============================================================================
procedure TForm1.WMNCLButtonDown(var Message: TWMNCLButtonDown);
var
  LHitTest : Integer;
  LText    : String;
begin
  LHitTest := Message.HitTest;

  if LHitTest = HTCLOSE then begin
    LText := 'x ボタンが押されました' + sLineBreak + '本当に閉じますか';
    if MessageBox(Handle, PChar(LText), '確認', MB_YESNO) = IDYES then begin
      //inherited;だとダイアログのWM_NCLBUTTONDOWNメッセージが発生するので閉じない
      Close;
    end else begin
      Message.Result := 0;
    end;
  end else
  if LHitTest = HTMINBUTTON then begin
    ShowMessage('最小化ボタンは機能しません');
    Message.Result := 0;
  end else
  if LHitTest = HTMAXBUTTON then begin
    Width    := 300;
    Height   := 300;
    Position := poDesktopCenter;
    Message.Result := 0;
  end else
  if LHitTest = HTSYSMENU then begin
    LText := 'システムメニュー (アプリケーション) アイコンが押されました' + sLineBreak
           + 'マウスの左ボタンではメニューを表示しないようにしています' + sLineBreak
           + 'システムメニューを表示するにはマウスの右ボタンを押してください';
    MessageBox(Handle, PChar(LText), '確認', MB_OK);
    Message.Result := 0;
  end else begin
    inherited;
  end;
end;

end.


TApplicationEvents コンポーネントを使用することもできます。TApplicationEvents コンポーネントの OnMessage イベントを使用する場合は、例えば次のようにします。
イベントの Handled 引数の値を True にすれば、ここに書いたコード以外の処理は実行しません。つまり、処理を終了したという意味になります。


//=============================================================================
//  [Close]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Close;
end;

//=============================================================================
//  ApplicationEvents1 の OnMessage イベント処理
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
var
  LMsg     : Cardinal;
  LHitTest : Integer;
begin
  LMsg := Msg.message;

  // マウスの右と左のどちらのボタン押下でもシステムメニューを表示しない
  if (LMsg = WM_NCLBUTTONDOWN) or (LMsg = WM_NCRBUTTONDOWN) then begin
    LHitTest := Msg.wParam;
    if LHitTest = HTSYSMENU then begin
      Handled := True;
    end;
  end;

  // [Alt] キーと同時に VKF4 が押されている時は無効にする
  if (LMsg = WM_SYSKEYDOWN) then begin
    if (Msg. wParam = VK_F4) then begin
      Handled := True;
    end;
  end;

  // 閉じるボタンでは閉じないようにする
  // 最小化ボタンで最小化できないようにする
  // 最大化ボタンで最大化できないようにする
  if LMsg = WM_NCLBUTTONDOWN then begin
    LHitTest := Msg.wParam;
    if LHitTest = HTCLOSE then begin
      Handled := True;
    end else
    if (LHitTest = HTMINBUTTON) or (LHitTest = HTMAXBUTTON) then begin
      Handled := True;
    end;
  end;

  // Caption バーのダブルクリックの操作を無効にする
  // つまり最大化できない
  if LMsg = WM_NCLBUTTONDBLCLK then begin
    LHitTest := Msg.wParam;
    if LHitTest = HTCAPTION then begin
      Handled := True;
    end;
  end;
end;


フォームを閉じる時に確認するだけであれば、次のようなコードで十分です。



//=============================================================================
//  [Close]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Close;
end;

//=============================================================================
//  フォームのOnCloseQueryイベント処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if MessageBox(Handle, '本当に閉じますか', '確認', MB_YESNO) = IDYES then begin
    CanClose := True;
  end else begin
    CanClose := False;
  end;
end;




09_常に最前面に表示するフォーム


常に Z オーダーのトップ、つまり最前面に表示するウィンドウにするための 1 つの方法です。
他のアプリケーションの開発者が同じことを考えて、そのアプリケーションのウィンドウを常に再前面に表示するようにするかも知れません。その場合、動作保証はありません。
最前面に表示できるウィンドウは常に 1 つだけです。


対象のフォームを最前面にして、アクティブにするだけであれば、以下のコードで可能です。必要に応じて引数のフラグの値を変更します。

  //このフォームを最前面にする
  SetWindowPos(Self.Handle,
               HWND_TOPMOST,
               0,
               0,
               0,
               0,
               SWP_NOMOVE or SWP_NOSIZE);


[備考]
ShellExecuteEx 関数や CreateProcess 関数で他のアプリを起動し、そのアプリが終了した時に、起動元の自アプリのフォームを最前面に表示する方法については、以下の記事のサンプルを参考にしてください。



図28
設計時画面

リスト10
常に最前面 ( Z オーダーのトップ) に表示するフォーム
unit TopMostFormUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    OriginalProc:TWndMethod;
    procedure SubClassProc(var Message:TMessage);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  元のWindowsProcを退避して新しいWindowProcを定義
//  この設定は用途によってはOnShowで設定する必要があるかも知れない
//  OnShow設定の場合は、OnCloseでWindowProcを元に戻しておいた方がいいだろう
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  OriginalProc    := Self.WindowProc;
  Self.WindowProc := SubClassProc;
end;
//=============================================================================
//  WindowsProcを元に戻しておく
//  このサンプルではOnCreateでWindowProcを設定しているので実際には不要
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  Self.WindowProc := OriginalProc;
end;
//=============================================================================
//  WindowProcのサブクラス関数
//  このフォームがZオーダのトップかを監視して設定
//=============================================================================
procedure TForm1.SubClassProc(var Message: TMessage);

  //Targetが最前面かを判定する関数
  function IsTopMost(Target: HWND): boolean;
  begin
    Result := ((GetWindowLong(Target, GWL_EXSTYLE) and WS_EX_TOPMOST) <> 0);
  end;
  //---------------------------------------------------------------------------

begin
  //元のWindowProcを実行
  OriginalProc(Message);

  //このフォームが最前面でなければ最前面にする
  if IsTopMost(Self.Handle)=False then begin
    SetWindowPos(Self.Handle,
                 HWND_TOPMOST,
                 0,
                 0,
                 0,
                 0,
                 SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOOWNERZORDER);
  end;
end;

end.




10_オーバーラップなウィンドウ


メインフォームである Form1 にオーバラップして Form2 を表示します。Form2 はフォーム生成時に自動的に呼ばれる CreateParams を使用してウィンドウスタイルを変更します。
フォーカスが Form1 に移動しても、Form1 にオーバラップして表示されます。[126_コード補完ウィンドウもどき] でも似たような動作をするサンプルがあります。

本サンプルのコードのままでは、メインフォームを移動しても、オーバラップ表示しているフォームは移動しません。移動したら、閉じるとか、同時に移動するとかの処理が必要です。



図29
メインフォームの設計時画面
図30
オーバーラップするフォーム
図31
実行画面
  • [開く] ボタンでForm2表示
  • Edit1 にフォーカスしても Form1 の上に表示

リスト11
メインフォームのコード
位置を指定して Form2 を表示するだけ
unit OverLapMainUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses OverLapForm;

//=============================================================================
//  位置を指定してForm2を表示
//  Form2のPositionプロパティはpoDesigned
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  APos : TPoint;
begin
  APos := Point(Edit1.Left, Edit1.Top + Edit1.Height);
  APos := Form1.ClientToScreen(APos);

  Form2.Left := APos.X;
  Form2.Top  := APos.Y;

  Form2.Show;
end;

end.


リスト12
Form1 にオーバーラップして表示するフォーム
unit OverLapForm;

interface

uses
  {$IF CompilerVersion > 22.99}
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, 
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
  {$ELSE}
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
  {$IFEND}

type
  TForm2 = class(TForm)
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form2: TForm2;

implementation

{$R *.DFM}

uses OverLapMainUnit;

//=============================================================================
//  オーバーラップなフローティングウィンドウにする
//  このフォームを表示した後、Form1にフォーカス移動してもこのフォームはForm1の
//  前面に表示される
//
//  このフォームのPositionプロパティはpoDesigned
//=============================================================================
procedure TForm2.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);

  Params.Style     := Params.Style or WS_OVERLAPPED;
  Params.WndParent := Form1.Handle;
end;

end.


オーバラップなフォームのスタイルを次のようにすると、下図のようになります。
必要な時に項目選択用のオーバラップウィンドウを表示するようにします。そして、オーバラップするフォームに、TListBox 等を配置して、[↓] [↑] キーで項目を選択したら、Edit1 にその選択項目を表示するという用途に使えます。
オーバラップなウィンドウを開く時と閉じる時のキー操作の処理が必要かも知れませんが、閉じる方は [ESC] キーでもいいかも知れません。


procedure TForm2.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);

  Params.Style     := WS_OVERLAPPED or WS_POPUP or WS_THICKFRAME;
  Params.WndParent := Form1.Handle;
end;


図32
オーバーラップなウィンドウの例
  • キャプションバーがない場合

オーバラップなフォームのスタイルを次のようにすると、下図のようになります。
こちらの方がフォームに配置した時の TListBox のデザインに近いかも知れません。


procedure TForm2.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);

  Params.Style     := WS_OVERLAPPED or WS_POPUP or WS_BORDER;
  Params.WndParent := Form1.Handle;
end;


図33
オーバーラップなウィンドウの例
  • フレーム枠がない場合




11_実行時にフォームのスタイルを変更  -  フレーム枠なしウィンドウ


フォームのスタイルは BorderStype、BorderIcons で変更できます。これらは実行時にも変更することができます。この他、SetWindowLong 関数を使用すると、第2引数に GWL_STYLE または GWL_EXSTYLE を指定することで詳細なスタイルの変更が可能です。その例です。スタイルに指定可能な値は Halbow 資料館にもあります。

Windows XP までは、スタイルを変更すると、画面に表示するサイズが変わりましたが、Vista 以降では表示サイズが変わらなくなったようです。



図34
設計時画面
図35
WS_CAPTION のチェックを外した場合
図36
同じ設定で Windows XP の場合
  • Vista 以降はサイズ変更枠等のデザインが変更となっている
図37
Windows 7 で上と同じスタイルにする
  • [WS_THICKFRAME] のチェックを外す
  • Windows XP とはサイズが違うことに注意

Windows XP までは BorderStype を bsNone にするとフレームのないフォーム (ウィンドウ) を作成できました。Widows Vista 以降では、上の図のように、フレームが表示されてしまいます。

Windows Vista, 7 でフレーム枠のないフォームを作成するには、スタイルを WS_POPUP にします。
WS_POPUP というウインドウスタイルは、タイトルバーや枠を持たないウインドウ、つまり、クライアント領域だけで構成されるウインドウのことです。このようなウインドウは、ポップアップウインドウと呼ばれています。

ポップアップウィンドウで全画面 (フルスクリーン) 表示する時は、そのウィンドウを Z オーダーの最前面しないと、タスクバーが表示されてしまいます。
ポップアップウィンドウは、タイトルバー (キャプションバー) がないので、そのままでは終了することができません。何らかのキー操作を検出して処理する方法を考える必要があります。



図38
フレーム枠が完全にないウィンドウ
  • [WS_POPUP] にする
図39
細線枠のウィンドウ
  • WS_POPUP にして [WS_BORDER] を含めると、フラットな細線のフレームとなる

リスト13
実行時にフォームのスタイルを変更
unit Unit1;

interface

uses
  {$IF CompilerVersion > 22.99}
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
  {$ELSE}
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
  {$IFEND}

type
  TForm1 = class(TForm)
    GroupBox1: TGroupBox;
    CheckBox1: TCheckBox;
    CheckBox2: TCheckBox;
    CheckBox4: TCheckBox;
    CheckBox5: TCheckBox;
    CheckBox3: TCheckBox;
    CheckBox6: TCheckBox;
    CheckBox7: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム作成時の処理
//  現在の設定状態をチェックボックスの状態に反映
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  WindowStyle : Longint;
begin
  //現在のスタイルの設定値を取得
  WindowStyle := GetWindowLong(Form1.Handle, GWL_STYLE);

  //スタイル値に応じてチェックボックスのチェック状態を変更
  if (WindowStyle and WS_CAPTION) > 0     then CheckBox1.Checked := True;
  if (WindowStyle and WS_BORDER) > 0      then CheckBox2.Checked := True;
  if (WindowStyle and WS_THICKFRAME) > 0  then CheckBox3.Checked := True;
  if (WindowStyle and WS_POPUP) > 0       then CheckBox4.Checked := True;
  if (WindowStyle and WS_SYSMENU) > 0     then CheckBox5.Checked := True;
  if (WindowStyle and WS_MAXIMIZEBOX) > 0 then CheckBox6.Checked := True;
  if (WindowStyle and WS_MINIMIZEBOX) > 0 then CheckBox7.Checked := True;

  //全てのチェックボックスのOnClickは共通して使用
  //実際にはオブジェクト・インスペクタで設定した方が良
  CheckBox1.OnClick := CheckBox1Click;
  CheckBox2.OnClick := CheckBox1Click;
  CheckBox3.OnClick := CheckBox1Click;
  CheckBox4.OnClick := CheckBox1Click;
  CheckBox5.OnClick := CheckBox1Click;
  CheckBox6.OnClick := CheckBox1Click;
  CheckBox7.OnClick := CheckBox1Click;
end;

//=============================================================================
//  チェックボックスのチェック状態によってフォームのスタイルを変更
//  最小化・最大化・閉じるボタンはキャプションバーが表示されていなければ表示さ
//  ないことに注意
//=============================================================================
procedure TForm1.CheckBox1Click(Sender: TObject);
var
  WindowStyle : Longint;
begin
  //現在のスタイルの設定値を取得
  WindowStyle := GetWindowLong(Form1.Handle, GWL_STYLE);

  //WS_CAPTIONたけを設定してもキャプションバー(タイトルバー)は表示されない
  //WS_BORDERの値も必要
  if (CheckBox1.Checked) then begin
    WindowStyle := WindowStyle or WS_CAPTION;
  end else begin
    WindowStyle := WindowStyle and not WS_CAPTION;
  end;

  if (CheckBox2.Checked) then begin
    WindowStyle := WindowStyle or WS_BORDER;
  end else begin
    WindowStyle := WindowStyle and not WS_BORDER;
  end;

  if (CheckBox3.Checked) then begin
    WindowStyle := WindowStyle or WS_THICKFRAME;
  end else begin
    WindowStyle := WindowStyle and not WS_THICKFRAME
  end;

  if (CheckBox4.Checked) then begin
    WindowStyle := WindowStyle or WS_POPUP;
  end else begin
    WindowStyle := WindowStyle and not WS_POPUP;
  end;

  if (CheckBox5.Checked) then begin
    WindowStyle := WindowStyle or WS_SYSMENU;
  end else begin
    WindowStyle := WindowStyle and not WS_SYSMENU;
  end;

  if (CheckBox6.Checked) then begin
    WindowStyle := WindowStyle or WS_MAXIMIZEBOX;
  end else begin
    WindowStyle := WindowStyle and not WS_MAXIMIZEBOX;
  end;

  if (CheckBox7.Checked) then begin
    WindowStyle := WindowStyle or WS_MINIMIZEBOX;
  end else begin
    WindowStyle := WindowStyle and not WS_MINIMIZEBOX;
  end;

  //スタイル変更を実行
  SetWindowLong(Form1.Handle, GWL_STYLE, WindowStyle);
  //再描画のため必要(非クライアント領域を含めてウィンドウを再描画)
  SetWindowPos(Handle, 0, 0, 0, 0, 0,
               SWP_DRAWFRAME  or
               SWP_NOACTIVATE or
               SWP_NOMOVE     or
               SWP_NOSIZE     or
               SWP_NOZORDER);
end;

end.




12_タスクバーへフォーム別のアイコンを表示


通常のアプリケーションでは、いくつフォームを表示しても、タスクバーにはそのアプリケーションのメインフォームのアイコンのアイコンだけが表示されるようになっています。
以下のコードは、メインフォーム以外のフォームのアイコンをタスクバーに表示する例です。


図40
設計時画面
図41
別のフォームを表示
  • タスクバーに [Form1] [Form2] の両方のアイコンが表示される
図42
[Form2] の [Button1] クリックでアイコン変更
  • タスクバーの [Form2] のアイコンも変わる

リスト14
タスクバーにフォームのアイコンを表示
unit ShowTaskBarIconUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses AnotherForm;

{$R *.dfm}

//=============================================================================
//  Form2を表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2.Left := Self.Left - Self.Width - 20;
  Form2.Top  := Self.Top - Self.Height - 20;
  Form2.Show;
end;

//=============================================================================
//  [終了]ボタン
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  Close;
end;

end.


リスト15
表示する別のフォームのコード
unit AnotherForm;

interface

uses
  {$IF CompilerVersion > 22.99}
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, 
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
  {$ELSE}
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
  {$IFEND}

type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  protected
    procedure CreateParams(var Params : TCreateParams); override;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

{ TForm2 }

//=============================================================================
//  フォームのCreateParams処理
//=============================================================================
procedure TForm2.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);

  //このフォームの親はDesktopとする
  //タスクバーにForm1とは別にアイコン表示される
  Params.WndParent := GetDesktopWindow;
end;

//=============================================================================
//  このフォームのアイコンを変更
//  当然タスクバーのアイコンも変わる
//=============================================================================
procedure TForm2.Button1Click(Sender: TObject);
begin
  Self.Icon.LoadFromFile('32_8bit.ico');
  SetClassLong(Handle, GCL_HICON, Self.Icon.Handle);
end;

end.




13_OnCreate イベントまたは OnShow イベントでフォームを閉じる


フォームの OnCreate イベントまたは OnShow イベント内で自フォームを閉じるメッセージをポストする例です。PostMessage で WM_CLOSE メッセージを自分自身にポストします。
イベントの実行が終了してから閉じるので、ほんのわずかですが一瞬だけフォームが表示されます。


図43
設計時の画面
  • 実際にはほんの一瞬しか表示されない


リスト16
フォームの OnCreate イベント内で自フォームを閉じるメッセージをポスト
//=============================================================================
//  このフォームを閉じる
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  PostMessage(Handle, WM_CLOSE, 0, 0);
end;


リスト17
フォームの OnShow イベント内でフォームを閉じるメッセージをポスト
//=============================================================================
//  このフォームを閉じる
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  PostMessage(Handle, WM_CLOSE, 0, 0);
end;